From dd985b3cd9c5caf38d59de481c3d58288d0d2d4c Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Wed, 27 Dec 2023 16:30:58 -0500 Subject: [PATCH 01/69] [PCT-472]: Working on vignette overview and drafted first vignette --- R/vignettes/README.md | 24 + R/vignettes/Vignette_01_Introduction.Rmd | 65 +++ R/vignettes/Vignette_01_Introduction.html | 552 ++++++++++++++++++ R/vignettes/Vignette_02_SimulateData.Rmd | 36 ++ R/vignettes/images/ABISSMAL_localrepo.png | Bin 0 -> 50429 bytes .../images/Figure4_ComputationalAnalyses.png | Bin 0 -> 570266 bytes R/vignettes/images/RStudio_01.png | Bin 0 -> 182459 bytes R/vignettes/images/RStudio_02.png | Bin 0 -> 231685 bytes 8 files changed, 677 insertions(+) create mode 100644 R/vignettes/README.md create mode 100644 R/vignettes/Vignette_01_Introduction.Rmd create mode 100644 R/vignettes/Vignette_01_Introduction.html create mode 100644 R/vignettes/Vignette_02_SimulateData.Rmd create mode 100644 R/vignettes/images/ABISSMAL_localrepo.png create mode 100644 R/vignettes/images/Figure4_ComputationalAnalyses.png create mode 100644 R/vignettes/images/RStudio_01.png create mode 100644 R/vignettes/images/RStudio_02.png diff --git a/R/vignettes/README.md b/R/vignettes/README.md new file mode 100644 index 0000000..c685cc2 --- /dev/null +++ b/R/vignettes/README.md @@ -0,0 +1,24 @@ +

ABISSMAL R vignettes +

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

Overview

+ +The goal of these tutorials is to disseminate basic R coding skills in a biological context through the ABISSMAL behavioral tracking system. I want to have 5 short tutorials in English and Spanish. I want an Rmd file per tutorial in each language, plus an accompanying video of the screen as I work through each one (ideally I'll have a script of what I'll say per video too). + +1. Introduction to RStudio, download ABISSMAL GitHub repository, introduction to the ABISSMAL data processing / analysis workflow + +2. Create simulated data and working directories + +3. Combine the raw data, detect perching events and pre-process the raw data + +4. Detect clusters, score clusters + +5. Run steps 2-4 with simulated data from 3 experimental replicates + +6. Make a visualization with results from the 3 simulated replicates + +Note that code for processing and analzying biological data collected from zebra finches, and code to make figures, is published online as part of a submitted manuscript for the ABISSMAL tracking system. \ No newline at end of file diff --git a/R/vignettes/Vignette_01_Introduction.Rmd b/R/vignettes/Vignette_01_Introduction.Rmd new file mode 100644 index 0000000..4f3ebe8 --- /dev/null +++ b/R/vignettes/Vignette_01_Introduction.Rmd @@ -0,0 +1,65 @@ +--- +title: "Vignette 01: Introduction" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: html_document +--- + +

Vignette Overview and Learning Objectives

+ +This first vignette will take you through a brief introduction to RStudio, downloading a local version of the ABISSMAL GitHub repository, and an introduction to the data analysis workflow provided by ABISSMAL. Through this tutorial, you will learn: + +1. How to configure your RStudio session +2. How to create a local version of a GitHub repository +3. The data processing and analysis steps provided by the ABISSMAL R functions + +

1. Configure your RStudio session

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

2. Create a local version of an existing GitHub repository

+ +If you are new to using GitHub, then I recommend downloading [GitHub Desktop](https://desktop.github.com/). This software is a graphical user interface for the GitHub version control platform and makes GitHub a more accessible tool. 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're already familiar with GitHub and using Git through the command line, then check out the installation instructions on the [ABISSMAL README](https://github.com/lastralab/ABISSMAL). + +Once you have installed GitHub Desktop, open the software by clicking on the icon. Then 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. Go back to the GitHub Desktop window, go to the top menu and select "File", then "Clone repository", then select the tab "URL". Paste the web URL for ABISSMAL into the text box under "Repository URL or GitHub username and repository", and 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 clone a local version of the remote ABISSMAL repository to 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": + +
+![A screenshot of the directory that is the local ABISSMAL repository](/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R/vignettes/images/ABISSMAL_localrepo.png) + +The files that we'll be working with throughout the following vignettes are inside of the "R" folder. This folder contains 6 R files (extension ".R"), a README file that contains more information about each R file, and folders with the R vignettes as well as automated unit testing scripts. + +

3. ABISSMAL data processing and analysis

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

+Vignette Overview and Learning Objectives +

+

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

+
    +
  1. How to configure your RStudio session
  2. +
  3. How to create a local version of a GitHub repository
  4. +
  5. The data processing and analysis steps provided by the ABISSMAL R +functions
  6. +
+

+
    +
  1. Configure your RStudio session +

+ +

If you are new to using R (a programming language for statistical +analysis) and RStudio (a graphical user interface for R), then I +recommend checking out the introductory lesson R for +Reproducible Scientific Analysis provided by 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:

+


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

+

This pane layout makes it difficult to see the code that you’re +writing and saving in a physical file in the source pane, and the output +of that code in the console pane. You can reconfigure the panes so that +the source and console panes are horizontally adjacent to one another, +which makes it easier to immediately check the output of any code that +you run. To reconfigure the pane layout, go to the menu along the top +bar of the RStudio window and select the option “Tools”, then “Global +Options” in the pop-up menu. In the next pop-up menu, select the option +“Pane Layout” along the lefthand side, then use the dropdown menus to +select “Source” as the top right pane and “Console” as the top left +pane. You can select “Apply” to apply those changes, then “Ok” to exit +this window. The RStudio pane layout should now look like this:

+


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

+

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

+

You can also play with 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.

+

+
    +
  1. Create a local version of an existing GitHub repository +

+ +

If you are new to using GitHub, then I recommend downloading GitHub Desktop. This software is +a graphical user interface for the GitHub version control platform and +makes GitHub a more accessible tool. 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’re 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 the software by clicking +on the icon. Then 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. Go back to the GitHub Desktop window, go to the top menu +and select “File”, then “Clone repository”, then select the tab “URL”. +Paste the web URL for ABISSMAL into the text box under “Repository URL +or GitHub username and repository”, and 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 +clone a local version of the remote ABISSMAL repository to 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”:

+


A screenshot of the directory that is the local ABISSMAL repository

+

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

+

+
    +
  1. ABISSMAL data processing and analysis +

+ +

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

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

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

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

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

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

  10. +
+


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

+

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

+ + + + +
+ + + + + + + + + + + + + + + diff --git a/R/vignettes/Vignette_02_SimulateData.Rmd b/R/vignettes/Vignette_02_SimulateData.Rmd new file mode 100644 index 0000000..fded5c8 --- /dev/null +++ b/R/vignettes/Vignette_02_SimulateData.Rmd @@ -0,0 +1,36 @@ +--- +title: "Vignette_01_Introduction_SimulateData" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: html_document +--- + +

Vignette Overview and Learning Objectives

+ +This first vignette will take you through a brief introduction to RStudio, an introduction to the data analysis workflow provided by ABISSMAL, and downloading a local version of the ABISSMAL GitHub repository. + +and creating simulated data for the following vignettes. Through this tutorial, you will learn more about how to configure your RStudio session, the different data processing and analysis steps provided by each ABISSMAL R function, and how to write and read data in R. + +```{r} + +rm(list = ls()) + +library(tidyverse) +library(data.table) +library(pbapply) +library(tidyquant) + +path <- "/home/gsvidaurre/Desktop" +tmp_path <- file.path(path, "tmp") + +code_path <- "/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R" +code <- list.files(code_path, pattern = ".R$", full.names = TRUE) + +invisible(lapply(1:length(code), function(i){ + source(code[i]) +})) + +``` + + + diff --git a/R/vignettes/images/ABISSMAL_localrepo.png b/R/vignettes/images/ABISSMAL_localrepo.png new file mode 100644 index 0000000000000000000000000000000000000000..6ad0fbc0ce798917cbf377afbab390311b7e0283 GIT binary patch literal 50429 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 WEB(_|AW*p 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

0_;En(bu8>r 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%0DCL~sYUKnCrY7FNDGdMU&oigGw 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�|w0_u!v0H)sK1+zxnW*>dT0wZI_Ese-x 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%0DdVnod#%wiCYbZ42R8hcw}hEsXD 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 literal 0 HcmV?d00001 diff --git a/R/vignettes/images/RStudio_02.png b/R/vignettes/images/RStudio_02.png new file mode 100644 index 0000000000000000000000000000000000000000..5be5084239c4e8149945c43693642fa2f551e3bf GIT binary patch literal 231685 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

>akoyfV~*ufbF?@5W?fw^)u!shE* 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�Zk`g;Xa^>o4MFjw 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 Date: Thu, 28 Dec 2023 13:47:19 -0500 Subject: [PATCH 02/69] [PCT-472]: Drafted a second vignette, see README notes for splitting and updates --- R/vignettes/.Rhistory | 10 + R/vignettes/README.md | 12 +- R/vignettes/Vignette_01_Introduction.Rmd | 48 +- R/vignettes/Vignette_01_Introduction.html | 61 ++- R/vignettes/Vignette_02_SimulateData.Rmd | 141 +++++- R/vignettes/Vignette_02_SimulateData.html | 587 ++++++++++++++++++++++ 6 files changed, 813 insertions(+), 46 deletions(-) create mode 100644 R/vignettes/.Rhistory create mode 100644 R/vignettes/Vignette_02_SimulateData.html diff --git a/R/vignettes/.Rhistory b/R/vignettes/.Rhistory new file mode 100644 index 0000000..612c535 --- /dev/null +++ b/R/vignettes/.Rhistory @@ -0,0 +1,10 @@ +rm(list = ls()) +rm(list = ls()) +rm(list = ls()) +?ls +?remove +?rm +?ls +?rm +?ls +ls() diff --git a/R/vignettes/README.md b/R/vignettes/README.md index c685cc2..fa72253 100644 --- a/R/vignettes/README.md +++ b/R/vignettes/README.md @@ -11,14 +11,22 @@ The goal of these tutorials is to disseminate basic R coding skills in a biologi 1. Introduction to RStudio, download ABISSMAL GitHub repository, introduction to the ABISSMAL data processing / analysis workflow +TKTK to the Intro vignette I need to add more information about Rmd files and knitted output... +Also shortcuts for running code in Rmarkdown files +And tips for following along with these vignettes (run code inside the Rmd files themselves, or start fresh with a new Rmd writing the code yourself and your own commments, which is preferred) + 2. Create simulated data and working directories +TKTK add searching for errors... + 3. Combine the raw data, detect perching events and pre-process the raw data 4. Detect clusters, score clusters -5. Run steps 2-4 with simulated data from 3 experimental replicates +5. Run steps 2-4 with simulated data from 3 experimental replicates TKTK consider dropping...and split 2 into 2 vignettes 6. Make a visualization with results from the 3 simulated replicates -Note that code for processing and analzying biological data collected from zebra finches, and code to make figures, is published online as part of a submitted manuscript for the ABISSMAL tracking system. \ No newline at end of file +Note that code for processing and analzying biological data collected from zebra finches, and code to make figures, is published online as part of a submitted manuscript for the ABISSMAL tracking system. + +Also note that the knitted output for these tutorials must be visible from the ABISSMAL website. This is especially important for accessibility of the Introduction for people who haven't downloaded the ABISSMAL repo yet \ No newline at end of file diff --git a/R/vignettes/Vignette_01_Introduction.Rmd b/R/vignettes/Vignette_01_Introduction.Rmd index 4f3ebe8..cbf56fe 100644 --- a/R/vignettes/Vignette_01_Introduction.Rmd +++ b/R/vignettes/Vignette_01_Introduction.Rmd @@ -17,15 +17,27 @@ This first vignette will take you through a brief introduction to RStudio, downl If you are new to using R (a programming language for statistical analysis) and RStudio (a graphical user interface for R), then I recommend checking out the introductory lesson [R for Reproducible Scientific Analysis](https://swcarpentry.github.io/r-novice-gapminder/) provided by [Software Carpentry](https://software-carpentry.org/). -Once you have R and RStudio installed on your computer, you can open RStudio by clicking on the software icon. The default pane configuration for RStudio should look like this: +Once you have R and RStudio installed on your computer, you can open RStudio by clicking on the software icon. The default pane configuration for RStudio should look like this (the background color will probably be different):
-![A screenshot of the default RStudio pane configuration, with the console pane below the source pane](/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R/vignettes/images/RStudio_01.png) +![A screenshot of the default RStudio pane configuration, with the console pane below the source pane](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/RStudio_01.png) -This pane layout makes it difficult to see the code that you're writing and saving in a physical file in the source pane, and the output of that code in the console pane. You can reconfigure the panes so that the source and console panes are horizontally adjacent to one another, which makes it easier to immediately check the output of any code that you run. To reconfigure the pane layout, go to the menu along the top bar of the RStudio window and select the option "Tools", then "Global Options" in the pop-up menu. In the next pop-up menu, select the option "Pane Layout" along the lefthand side, then 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: +This pane layout makes it difficult to see the code that you're writing and saving in a physical file in the source pane, and the output of that code in the console pane. You can reconfigure the panes so that the source and console panes are horizontally adjacent to one another, which makes it easier to immediately check the output of any code that you run. To reconfigure the pane layout: + +* Go to the menu along the top bar of the RStudio window and select the option "Tools" + +* Select "Global Options" in the pop-up menu + +* In the next pop-up menu, select the option "Pane Layout" along the lefthand side + +* Use the dropdown menus to select "Source" as the top right pane and "Console" as the top left pane + +* You can select "Apply" to apply those changes, then "Ok" to exit this window + +The RStudio pane layout should now look like this:
-![A screenshot of the updated RStudio pane configuration, with the console pane to the right of the source pane](/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R/vignettes/images/RStudio_02.png) +![A screenshot of the updated RStudio pane configuration, with the console pane to the right of the source pane](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/RStudio_02.png) Another useful change to the RStudio configuration is soft-wrapping lines of text and code, so that you don't have to scroll horizontally to see long lines of text in your Source pane. To do this, go to "Tools", then "Global Options", then "Code", and check the box next to the option "Soft-wrap R source files", then select "Apply" and "Ok". @@ -33,12 +45,32 @@ You can also play with the font size, font color, and background color of your R

2. Create a local version of an existing GitHub repository

-If you are new to using GitHub, then I recommend downloading [GitHub Desktop](https://desktop.github.com/). This software is a graphical user interface for the GitHub version control platform and makes GitHub a more accessible tool. 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're already familiar with GitHub and using Git through the command line, then check out the installation instructions on the [ABISSMAL README](https://github.com/lastralab/ABISSMAL). +If you are new to using GitHub, then I recommend downloading [GitHub Desktop](https://desktop.github.com/). This software is a graphical user interface for the GitHub version control platform. We will work through how to download the GitHub repository for ABISSMAL so that you can access the ABISSMAL R functions on your local computer. If you are already familiar with GitHub and using Git through the command line, then check out the installation instructions on the [ABISSMAL README](https://github.com/lastralab/ABISSMAL). + +Once you have installed GitHub Desktop: + +* Open GitHub Desktop by clicking on the icon + +* Open your default Internet browser and navigate to the ABISSMAL GitHub repository: https://github.com/lastralab/ABISSMAL + +* Click on the green "Code" button and copy the web URL under the HTTPS option in the dropdown menu + +* In the GitHub Desktop window, go to the top menu and select "File" + +* Select "Clone repository", + +* Select the tab "URL" + +* Paste the web URL for ABISSMAL into the text box under "Repository URL or GitHub username and repository" + +* Check that the directory in the text box under "Local path" is the correct place to install the ABISSMAL GitHub repository on your computer. For instance, if "Local path" is "/home/User/Desktop/ABISSMAL", then the ABISSMAL repository will be installed directly to your Desktop + +* Select "Clone" once you're ready to create a local version of the remote ABISSMAL repository on your computer -Once you have installed GitHub Desktop, open the software by clicking on the icon. Then 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. Go back to the GitHub Desktop window, go to the top menu and select "File", then "Clone repository", then select the tab "URL". Paste the web URL for ABISSMAL into the text box under "Repository URL or GitHub username and repository", and 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 clone a local version of the remote ABISSMAL repository to 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": +Once the repository has been installed on your computer, you should see the following directory and file structure inside the folder labeled "ABISSMAL" that looks similar to this:
-![A screenshot of the directory that is the local ABISSMAL repository](/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R/vignettes/images/ABISSMAL_localrepo.png) +![A screenshot of the directory that is the local ABISSMAL repository](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/ABISSMAL_localrepo.png) The files that we'll be working with throughout the following vignettes are inside of the "R" folder. This folder contains 6 R files (extension ".R"), a README file that contains more information about each R file, and folders with the R vignettes as well as automated unit testing scripts. @@ -57,7 +89,7 @@ ABISSMAL provides 5 different R functions for data processing and analysis. Thes 5. `score_clusters` uses the output of `detect_clusters` as the main input. This function can also use the output of `detect_perching_events` to integrate perching events detected in the raw RFID or infrared beam breaker datasets. It can also use the output of `preprocess_detections` to integrate video recording events that were not detected by `detect_clusters`. This function serves to make behavioral inferences about each movement event, including the direction of movement, the magnitude of movement, individual identity when RFID data was present in a cluster, and where the beginning of the movement event likely occurred (at the container entrance or inside of the container). This function returns a spreadsheet of behavioral inferences and other metadata about each movement event that can be used for subsequent analyses and visualization
-![Figure 4 from the ABISSMAL methods manuscript under review, that shows a general workflow across these 5 functions](/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R/vignettes/images/Figure4_ComputationalAnalyses.png) +![Figure 4 from the ABISSMAL methods manuscript under review, that shows a general workflow across these 5 functions](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/Figure4_ComputationalAnalyses.png) * Figure from Smith-Vidaurre, G., Molina, T., Jarvis, E.D., and E.A. Hobson. 2023. Automated tracking of avian parental care. Under review. In the next vignette, we will create simulated datasets of raw movement data to learn how to use the different functions described above. diff --git a/R/vignettes/Vignette_01_Introduction.html b/R/vignettes/Vignette_01_Introduction.html index 31e3ffb..18a06c7 100644 --- a/R/vignettes/Vignette_01_Introduction.html +++ b/R/vignettes/Vignette_01_Introduction.html @@ -381,20 +381,27 @@

Reproducible Scientific Analysis provided by 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:

+for RStudio should look like this (the background color will probably be +different):


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

This pane layout makes it difficult to see the code that you’re writing and saving in a physical file in the source pane, and the output of that code in the console pane. You can reconfigure the panes so that the source and console panes are horizontally adjacent to one another, which makes it easier to immediately check the output of any code that -you run. To reconfigure the pane layout, go to the menu along the top -bar of the RStudio window and select the option “Tools”, then “Global -Options” in the pop-up menu. In the next pop-up menu, select the option -“Pane Layout” along the lefthand side, then 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:

+you run. To reconfigure the pane layout:

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

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

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

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

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

  • +
+

The RStudio pane layout should now look like this:


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

Another useful change to the RStudio configuration is soft-wrapping lines of text and code, so that you don’t have to scroll horizontally to @@ -416,22 +423,30 @@

ABISSMAL R functions on your local computer. If you’re 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 the software by clicking -on the icon. Then 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. Go back to the GitHub Desktop window, go to the top menu -and select “File”, then “Clone repository”, then select the tab “URL”. -Paste the web URL for ABISSMAL into the text box under “Repository URL -or GitHub username and repository”, and 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 +

Once you have installed GitHub Desktop:

+
    +
  • Open GitHub Desktop by clicking on the icon

  • +
  • Then 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”

  • +
  • Then 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 -clone a local version of the remote ABISSMAL repository to 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”:

    +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”:


A screenshot of the directory that is the local ABISSMAL repository

The files that we’ll be working with throughout the following vignettes are inside of the “R” folder. This folder contains 6 R files diff --git a/R/vignettes/Vignette_02_SimulateData.Rmd b/R/vignettes/Vignette_02_SimulateData.Rmd index fded5c8..057b46c 100644 --- a/R/vignettes/Vignette_02_SimulateData.Rmd +++ b/R/vignettes/Vignette_02_SimulateData.Rmd @@ -1,36 +1,151 @@ --- -title: "Vignette_01_Introduction_SimulateData" +title: "Vignette 02: Simulate Data" author: "Grace Smith-Vidaurre" date: "2023-12-27" output: html_document --- +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = FALSE) + +``` +

Vignette Overview and Learning Objectives

-This first vignette will take you through a brief introduction to RStudio, an introduction to the data analysis workflow provided by ABISSMAL, and downloading a local version of the ABISSMAL GitHub repository. +In this second vignette, we will create simulated datasets of animal movements detected by different sensors. Throughout the process of creating these simulated datasets, you will learn basic R programming skills and tips for writing code for open science, including: + +1. Cleaning your global environment +2. Running code inside an RMarkdown chunk +3. Learning about R functions +4. Searching for R function documentation +5. Installing and loading packages +6. Commenting code +7. Setting and checking your working directory +8. Creating objects like vectors and data frames +9. Data types +10. Manipulating objects (e.g. adding columns to a data frame) +11. Piping expressions through the tidyverse +12. Writing out data to physical files and reading these files back into R +13. Troubleshooting and fixing errors + +

Clean your global environment

+ +Your global environment is your virtual workspace in R, and can hold different packages and objects to facilitate your data analysis and programming objectives. You can see the different packages and objects currently loaded in your environment by clicking on the tab "Environment" in the pane that also includes the "History" and "Connections" tab. + +If you just 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). -and creating simulated data for the following vignettes. Through this tutorial, you will learn more about how to configure your RStudio session, the different data processing and analysis steps provided by each ABISSMAL R function, and how to write and read data in R. +You can also clean your global environment by running the code below. You can run the code inside this chunk in different ways: +* Click on the small green arrow icon on the top right side of the chunk to run the code in this chunk only + +* Place your cursor anywhere within the line of code, then go up to the "Run" icon on the top right of the Source pane that has a white square and green arrow. In the dropdown menu that appears, select "Run Selected Lines" or "Run Current Chunk" (see the keyboard shortcuts next to each) + +* Place your cursor anywhere within the line of code and use the keyboard shortcut Ctrl + Enter to run the current line of code + +* Place your cursor anywhere within the line of code and use the keyboard shortcut Ctrl + Shift + Enter to run all code in the current chunk + +The first keyboard shortcut to run one line of code at a time can be helpful to see the output of each line of code at a time and check for errors. ```{r} rm(list = ls()) +``` + +The code above to clean your global environment is a nested expression with 2 functions: `rm()` and `ls()`. The `()` notation is used for functions. 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. + +

Access R function documentation

+ +You can access the documentation for these functions by clicking on the "Help" tab in the pane that also has "Files" and "Plots", and typing the name of the function in the search bar. You can also access function documentation by running the following code: +```{r} + +?rm + +``` + +```{r} + +?ls + +``` + +For each function, the documentation holds specific sections that can be useful for understanding how the function works, especially the function's arguments. A function's arguments are the values that it expects the user to provide in order to 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 instance, the function `rm()` has an argument called `list` (followed by an `=`). 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 lists the names of all of the objects in your global environment. + +

Install and load packages

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

Set your working directory

+ +The next important step for setting up your virtual workspace is to set your working directory. A directory is a location (like a folder) on your computer, and a working directory is the directory where R will search for files to read in, and also where the program will save files that you write out. -code_path <- "/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R" -code <- list.files(code_path, pattern = ".R$", full.names = TRUE) +You can use the function `getwd()` to check your current working directory. +```{r eval = TRUE} -invisible(lapply(1:length(code), function(i){ - source(code[i]) -})) +getwd() ``` +My working directory is set by default to the folder on my computer where I saved this Rmd file. To work in a separate directory that holds only data generated while working on these vignettes, you can create a new directory or folder on your computer: + +```{r} + +?dir.create + +dir.create("/home/gsvidaurre/Desktop/ABISSMAL_vignettes") + +``` + +In the code above, you'll need to replace the path (or the list of folder names) to reflect where you want to save the new folder called `ABISSMAL_vignettes` (or another name of your choice) on your own computer. For those using Windows, if you copy and paste a path into R directly from your computer then you may need to change the direction of the slashes. + +In basic R coding courses, it's very 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 in the spirit of open science (e.g. accompanying a publication or 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. There are other ways in which you can specify your working directory throughout your code without using `setwd()`. +For instance, let's say we want to make a copy of the first vignette and save that copy inside the directory we just created above. We can use some base R functions to copy and save the file to the correct working directory (you will need to update these paths to reflect where the vignettes are saved on your computer as well as your working directory): +```{r} + +file.copy( + from = "/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/Vignette_01_Introduction.Rmd", + to = "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Vignette_01_Introduction_copy.Rmd" + ) + +``` + +In the code above, we're specifying 2 arguments to the function `file.copy`. The first argument specifies the location of the file 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"). When you run this line of code, 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. We can also use another base function to check whether the copied file exists in the working directory. The output of this function is a list of all of the files contained within the new directory: +```{r} + +list.files(path = "/home/gsvidaurre/Desktop/ABISSMAL_vignettes") + +``` + +This is just 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 continue specifying your working directory in a similar way. Before moving on to the next vignette, you can remove the file that you just copied to your working directory: + +```{r} + +file.remove("/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Vignette_01_Introduction_copy.Rmd") + +``` +In the next vignette, you will start creating and manipulating objects in R that can be used in the ABISSMAL data processing and analysis pipeline. \ No newline at end of file diff --git a/R/vignettes/Vignette_02_SimulateData.html b/R/vignettes/Vignette_02_SimulateData.html new file mode 100644 index 0000000..79db253 --- /dev/null +++ b/R/vignettes/Vignette_02_SimulateData.html @@ -0,0 +1,587 @@ + + + + + + + + + + + + + + + +Vignette 02: Simulate Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +

+Vignette Overview and Learning Objectives +

+

In this second vignette, we will create simulated datasets of animal +movements detected by different sensors. Throughout the process of +creating these simulated datasets, you will learn basic R programming +skills and tips for writing code for open science, including:

+
    +
  1. Cleaning your global environment
  2. +
  3. Running code inside an RMarkdown chunk
  4. +
  5. Learning about R functions
  6. +
  7. Searching for R function documentation
  8. +
  9. Installing and loading packages
  10. +
  11. Commenting code
  12. +
  13. Setting and checking your working directory
  14. +
  15. Creating objects like vectors and data frames
  16. +
  17. Data types
  18. +
  19. Manipulating objects (e.g. adding columns to a data frame)
  20. +
  21. Piping expressions through the tidyverse
  22. +
  23. Writing out data to physical files and reading these files back into +R
  24. +
  25. Troubleshooting and fixing errors
  26. +
+

+Clean your global environment +

+

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

+

If you just 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. 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.

+

+Access R function documentation +

+

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

+
?rm
+
?ls
+

For each function, the documentation holds specific sections that can +be useful for understanding how the function works, especially the +function’s arguments. A function’s arguments are the values that it +expects the user to provide in order to 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 instance, the function rm() has an argument called +list (followed by an =). 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 lists +the names of all of the objects in your global environment.

+

+Install and load packages +

+

After cleaning the global environment, you still need to set up your +virtual workspace for the upcoming coding session. One important setup +step 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 load the package into your +global environment in order to access functions across the tidyverse +packages:

+
library(tidyverse)
+

+Set your working directory +

+

The next important step for setting up your virtual workspace is to +set your working directory. A directory is a location (like a folder) on +your computer, and a working directory is the directory where R will +search for files to read in, and also where the program will save files +that you write out.

+

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 Rmd file. To work in a separate directory that holds +only data generated while working on 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 basic R coding courses, it’s very 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 in the spirit of open science (e.g. accompanying a +publication or 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. There are other ways in which you +can specify your working directory throughout your code without using +setwd().

+

For instance, let’s say we want to make a copy of the first vignette +and save that copy inside the directory we just created above. We can +use some base R functions to copy and save the file to the correct +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 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”). When you run +this line of code, 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. We can also use another +base function to check whether the copied file exists in the working +directory. The output of this function is a list of all of the files +contained within the new directory:

+
list.files(path = "/home/gsvidaurre/Desktop/ABISSMAL_vignettes")
+ + + + +
+ + + + + + + + + + + + + + + From 229c2fc46a9fe134e40fc45315ddd0be056f772b Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Thu, 28 Dec 2023 15:59:21 -0500 Subject: [PATCH 03/69] [PCT-472]: Drafted third vignette, added more structural updates to the README --- R/vignettes/README.md | 18 +- R/vignettes/Vignette_01_Introduction.html | 567 ------------------ ...SimulateData.Rmd => Vignette_02_Setup.Rmd} | 51 +- ...mulateData.html => Vignette_02_Setup.html} | 141 +++-- R/vignettes/Vignette_03_SimulateData.Rmd | 144 +++++ 5 files changed, 273 insertions(+), 648 deletions(-) delete mode 100644 R/vignettes/Vignette_01_Introduction.html rename R/vignettes/{Vignette_02_SimulateData.Rmd => Vignette_02_Setup.Rmd} (56%) rename R/vignettes/{Vignette_02_SimulateData.html => Vignette_02_Setup.html} (99%) create mode 100644 R/vignettes/Vignette_03_SimulateData.Rmd diff --git a/R/vignettes/README.md b/R/vignettes/README.md index fa72253..2099179 100644 --- a/R/vignettes/README.md +++ b/R/vignettes/README.md @@ -11,22 +11,16 @@ The goal of these tutorials is to disseminate basic R coding skills in a biologi 1. Introduction to RStudio, download ABISSMAL GitHub repository, introduction to the ABISSMAL data processing / analysis workflow -TKTK to the Intro vignette I need to add more information about Rmd files and knitted output... -Also shortcuts for running code in Rmarkdown files -And tips for following along with these vignettes (run code inside the Rmd files themselves, or start fresh with a new Rmd writing the code yourself and your own commments, which is preferred) +2. Setup a virtual workspace (global environment, package installation, working directory, RMarkdown files) -2. Create simulated data and working directories +3. Create simulated data, including entrances, exits, and perching events -TKTK add searching for errors... +4. Combine the raw data and pre-process the raw data, detect perching events -3. Combine the raw data, detect perching events and pre-process the raw data +5. Make barcode style visualizations of the raw and per-processed data -4. Detect clusters, score clusters +6. Detect clusters, score clusters, generate summary statistics -5. Run steps 2-4 with simulated data from 3 experimental replicates TKTK consider dropping...and split 2 into 2 vignettes - -6. Make a visualization with results from the 3 simulated replicates - -Note that code for processing and analzying biological data collected from zebra finches, and code to make figures, is published online as part of a submitted manuscript for the ABISSMAL tracking system. +Note that code for processing and analyzing biological data collected from zebra finches, and code to make figures, is published online as part of a submitted manuscript for the ABISSMAL tracking system. Also note that the knitted output for these tutorials must be visible from the ABISSMAL website. This is especially important for accessibility of the Introduction for people who haven't downloaded the ABISSMAL repo yet \ No newline at end of file diff --git a/R/vignettes/Vignette_01_Introduction.html b/R/vignettes/Vignette_01_Introduction.html deleted file mode 100644 index 18a06c7..0000000 --- a/R/vignettes/Vignette_01_Introduction.html +++ /dev/null @@ -1,567 +0,0 @@ - - - - - - - - - - - - - - - -Vignette 01: Introduction - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - -

-Vignette Overview and Learning Objectives -

-

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

-
    -
  1. How to configure your RStudio session
  2. -
  3. How to create a local version of a GitHub repository
  4. -
  5. The data processing and analysis steps provided by the ABISSMAL R -functions
  6. -
-

-
    -
  1. Configure your RStudio session -

- -

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

-

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

-


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

-

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

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

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

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

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

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

  • -
-

The RStudio pane layout should now look like this:

-


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

-

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

-

You can also play with 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.

-

-
    -
  1. Create a local version of an existing GitHub repository -

- -

If you are new to using GitHub, then I recommend downloading GitHub Desktop. This software is -a graphical user interface for the GitHub version control platform and -makes GitHub a more accessible tool. 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’re 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

  • -
  • Then 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”

  • -
  • Then 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”:

-


A screenshot of the directory that is the local ABISSMAL repository

-

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

-

-
    -
  1. ABISSMAL data processing and analysis -

- -

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

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

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

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

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

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

  10. -
-


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

-

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

- - - - -
- - - - - - - - - - - - - - - diff --git a/R/vignettes/Vignette_02_SimulateData.Rmd b/R/vignettes/Vignette_02_Setup.Rmd similarity index 56% rename from R/vignettes/Vignette_02_SimulateData.Rmd rename to R/vignettes/Vignette_02_Setup.Rmd index 057b46c..f7511ad 100644 --- a/R/vignettes/Vignette_02_SimulateData.Rmd +++ b/R/vignettes/Vignette_02_Setup.Rmd @@ -1,5 +1,5 @@ --- -title: "Vignette 02: Simulate Data" +title: "Vignette 02: Setup" author: "Grace Smith-Vidaurre" date: "2023-12-27" output: html_document @@ -13,21 +13,29 @@ knitr::opts_chunk$set(echo = TRUE, eval = FALSE)

Vignette Overview and Learning Objectives

-In this second vignette, we will create simulated datasets of animal movements detected by different sensors. Throughout the process of creating these simulated datasets, you will learn basic R programming skills and tips for writing code for open science, including: +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 for widespread sharing, including: -1. Cleaning your global environment -2. Running code inside an RMarkdown chunk -3. Learning about R functions -4. Searching for R function documentation +1. How to use RMarkdown files +2. Cleaning your global environment +3. Running code inside an RMarkdown chunk +4. Learning about R functions and documentation 5. Installing and loading packages 6. Commenting code -7. Setting and checking your working directory -8. Creating objects like vectors and data frames -9. Data types -10. Manipulating objects (e.g. adding columns to a data frame) -11. Piping expressions through the tidyverse -12. Writing out data to physical files and reading these files back into R -13. Troubleshooting and fixing errors +7. Creating and using a working directory + +

Using RMarkdown files

+ +Each vignette in this series is available as an RMarkdown file (extension .Rmd) and as an HTML file that can be viewed in your default Internet browser. Each HTML file was generated by knitting the output of an RMarkdown file. You can check out the [RMarkdown documentation](https://rmarkdown.rstudio.com/lesson-1.html) for more information about how to use this file format to write code and knit the code output into reports. + +RMarkdown files facilitate sharing your code and the output of your code with others. If you're new to using RMarkdown, there are some different ways that you can use these files to work through the series of vignettes. First, 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. + +However, 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 yourself and also write out 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 have the vignette RMarkdown file and your own RMarkdown file side by side in RStudio by adding a third column in the pane layout: + +* Tools +* Global Options +* Pane Layout +* Select the Add Column option to add another Source pane in a third column +* Open the original vignette RMarkdown file in one source pane, and your own RMarkdown file in the other source pane

Clean your global environment

@@ -96,9 +104,9 @@ library(tidyverse) ``` -

Set your working directory

+

Create your working directory

-The next important step for setting up your virtual workspace is to set your working directory. A directory is a location (like a folder) on your computer, and a working directory is the directory where R will search for files to read in, and also where the program will save files that you write out. +The next important step for setting up your virtual workspace is to create (or decide on) your working directory. A directory is a location (like a folder) on your computer. A working directory is the location on your computer where R will search for files to read in, and also where the program will save files that you write out. You can use the function `getwd()` to check your current working directory. ```{r eval = TRUE} @@ -107,8 +115,7 @@ getwd() ``` -My working directory is set by default to the folder on my computer where I saved this Rmd file. To work in a separate directory that holds only data generated while working on these vignettes, you can create a new directory or folder on your computer: - +My working directory is set by default to the folder on my computer where I saved this RMarkdown file. To work in a separate directory that holds only data generated from these vignettes, you can create a new directory or folder on your computer: ```{r} ?dir.create @@ -119,9 +126,9 @@ 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 basic R coding courses, it's very 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 in the spirit of open science (e.g. accompanying a publication or 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. There are other ways in which you can specify your working directory throughout your code without using `setwd()`. +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. There are other ways in which you can specify your working directory throughout your code without using `setwd()`. -For instance, let's say we want to make a copy of the first vignette and save that copy inside the directory we just created above. We can use some base R functions to copy and save the file to the correct working directory (you will need to update these paths to reflect where the vignettes are saved on your computer as well as your working directory): +For instance, let's say that we want to make a copy of the first vignette and then save that copy inside the directory 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 these paths to reflect where the vignettes are saved on your computer, as well as your own working directory: ```{r} file.copy( @@ -131,16 +138,16 @@ file.copy( ``` -In the code above, we're specifying 2 arguments to the function `file.copy`. The first argument specifies the location of the file 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"). When you run this line of code, the output in the console should read "[1] TRUE" if the file was successfully copied and saved. +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 string (a formal term for text characters in R code). -You can check that the function worked correctly by opening a file window on your computer in this new directory. We can also use another base function to check whether the copied file exists in the working directory. The output of this function is a list of all of the files contained within the new directory: +When you run the line of code above, the output in the console should read "[1] TRUE" if the file was successfully copied and saved. You can check that the function worked correctly by opening a file window on your computer in this new directory. You can also use the base R function `list.files()` to check whether the copied file exists in the working directory. The output of `list.files()` is a list of all of the files contained within the new directory, and that list should contain a single file: Vignette_01_Introduction_copy.Rmd". ```{r} list.files(path = "/home/gsvidaurre/Desktop/ABISSMAL_vignettes") ``` -This is just 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 continue specifying your working directory in a similar way. Before moving on to the next vignette, you can remove the file that you just copied to your working directory: +This 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 just copied to your working directory: ```{r} diff --git a/R/vignettes/Vignette_02_SimulateData.html b/R/vignettes/Vignette_02_Setup.html similarity index 99% rename from R/vignettes/Vignette_02_SimulateData.html rename to R/vignettes/Vignette_02_Setup.html index 79db253..f3b5527 100644 --- a/R/vignettes/Vignette_02_SimulateData.html +++ b/R/vignettes/Vignette_02_Setup.html @@ -13,7 +13,7 @@ -Vignette 02: Simulate Data +Vignette 02: Setup + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +

+Vignette Overview and Learning Objectives +

+

In this third vignette, we will create simulated datasets of animal +movements detected by different sensors. Throughout the process of +creating these simulated datasets, you will continue to use coding +skills that you learned in vignette 02, and you will learn additional +skills that include:

+
    +
  1. Creating objects like vectors and data frames
  2. +
  3. Data types in R
  4. +
  5. Indexing and manipulating objects
  6. +
  7. Using conditional statements
  8. +
  9. Piping expressions through the tidyverse
  10. +
  11. Saving objects manipulated in R as physical files on your +computer
  12. +
+

+Load packages +

+

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

+

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

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

+Create a path object +

+

The next setup step is to specify your working directory. In vignette +02, you used a string (text contained in double quotes to indicate +character information) to specify your working directory while using +different functions. Using the same long character string over and over +is not very efficient, so you can instead save the character string for +your working directory inside a new object in R.

+

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

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

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

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

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

+

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

+

+Create timestamps of simulated detections of animal movements +

+

The primary data collected by the ABISSMAL tracking system are +timestamps indicating the moment of time when a movement sensor +triggered. 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 and 1 pair of infrared beam breakers is mounted in +front of the RFID antenna. In this simulated setup, the infrared beam +breakers will trigger first when a bird enters the nest container, and +the RFID antenna should trigger first when a bird leaves the nest +container.

+

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

+

These timestamps are supplied to the function c(). This +function 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 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 events. We can +choose timestamps for the infrared beam breakers that precede the RFID +timestamps to simulate an entrance event, and infrared beam breaker +timestamps that follow the RFID timestamps to simulate an exit event. We +will offset detections from each sensor within the entrance and exit +events by 1 second.

+
# Simulate timestamps for an entrance, an exit, and then another entrance and exit
+irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01")
+

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 trigger when birds leave nesting material hanging in +the entrance of the container. In both of these cases, the infrared beam +breakers should trigger when the RFID antenna does not.

+
# Simulate some RFID detection failures
+irbb_ts <- c(irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25")
+
+glimpse(irbb_ts)
+
##  chr [1:8] "09:59:59" "10:05:01" "10:59:59" "11:05:01" "06:05:05" ...
+
# Simulate some stray beam breaker detections
+irbb_ts <- c(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(irbb_ts)
+
##  chr [1:19] "09:59:59" "10:05:01" "10:59:59" "11:05:01" "06:05:05" ...
+

You’ve created simulated datasets of detections of animal movements, +but currently, these datasets are in vector format only 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 identity of each PIT tag detected.

+

Right now, the data for both sensors is in vector format. 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
+c("1", 1, TRUE, FALSE)
+
## [1] "1"     "1"     "TRUE"  "FALSE"
+
# Create a vector with numeric and binary data types
+c(1, 1, TRUE, FALSE)
+
## [1] 1 1 1 0
+

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

+

+Create data frames with primary data and metadata +

+

You need to be able to create metadata of different data types that +can accompany the timestamps for each sensor. To do this, 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 different data types in the same data frame. You can also write +out data frames to spreadsheets that exist as physical files on your +computer.

+

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. You’ll also use +the function length() to get the length of +rfid_ts, and then feed that result to rep() to +set the number of times to repeat the information.

+
# 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 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 run code like this 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: +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 this conditional statement, you’re using the symbols +== to ask if the two vectors 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
+length(rfid_ts) != length(exp_rep)
+
## [1] FALSE
+

The vectors rfid_ts and exp_rep must be the +same length in order to combine these vectors into a data frame. To +check that this is true, you can use square bracket index to filter out +4 elements of the vector rfid_ts so that this vector is 10 +elements long.

+
# 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
+
# 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"
+

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(): “rep("2023", length(rfid_ts))”. The +slashes are symbols automatically used by R to ignore special symbols +(here those special symbols are the double quotes, which are used as +part of the column name rather than symbols to indicate the character +data type). You can use the function names() and square +bracket indexing to change the column name:

+
# 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) # 3 columns in this data frame
+
## [1] 3
+
# This expression gets you the name of the last column
+names(sim_dats)[ncol(sim_dats)]
+
## [1] "rep(2023, length(rfid_ts))"
+
# Then you can overwrite the last column name with a new name
+names(sim_dats)[ncol(sim_dats)] <- "year"
+
+# Confirm that the name was changed correctly
+names(sim_dats)
+
## [1] "replicate"  "timestamps" "year"
+
glimpse(sim_dats)
+
## Rows: 14
+## Columns: 3
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+

+Create data frames using the tidyverse +

+

In the chunk above, you used base R code 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 a better name in fewer lines of +code here. 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. +Sometimes I use other packages that also have functions called +mutate(), and if I don’t specify which package I want to +use, then I end up with immediate errors (the code fails to run) or +worse, running 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 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
+
## 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 learned a new way to repeat a value within a +piping operation by using the notation ‘.’ inside 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 sim_dats
+  dplyr::mutate(
+    year = 2023
+  ) %>%
+  glimpse() %>% # See the structure of 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 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.

+

In the chunks of code above that inserted glimpse() +between piping operations, you did not save the new sim_dats as an +object. You can create the updated data frame now, and also add another +column with 2 unique PIT tag identifiers (1 for each simulated +individual):

+
# Make a vector for the experimental replicate
+exp_rep <- rep(x = "Pair_01", times = length(rfid_ts))
+
+# Make the data frame with the experimental replicate metadata and the timestamps
+sim_dats_rfid <- data.frame(replicate = 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
+  ) %>%
+  # Allocate the first half of the detections to the first individual using nrow(.)/2, and the second half of the detctions to the second individual using nrow(.)/2 again. Both of the rep() expressions are combined into a single vector using the c() function
+  dplyr::mutate(
+    PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2))
+  )
+
+glimpse(sim_dats_rfid)
+
## Rows: 14
+## Columns: 6
+## $ 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
+## $ PIT_tag    <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabbcc", "13…
+

Next, repeat this process of creating a data frame with metadata for +the infrared beam breaker dataset. Since the beam breakers do not +collect unique individual identity information, you will add columns for +the year, month, and day only.

+
# Overwrite the vector exp_rep with a new vector the same length as irbb_ts
+exp_rep <- rep(x = "Pair_01", times = length(irbb_ts))
+
+sim_dats_irbb <- data.frame(replicate = exp_rep, timestamps = irbb_ts)
+
+sim_dats_irbb <- sim_dats_irbb %>%
+  dplyr::mutate(
+    year = 2023,
+    month = 08,
+    day = 01
+  )
+
+glimpse(sim_dats_irbb)
+
## Rows: 19
+## Columns: 5
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ 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
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+

+Save each data frame as a physical file +

+

Now that you made data frames of the timestamps and metadata per +sensor, you can write out or save these data frames as physical files in +your working directory. You have many different file type options, but I +recommend using .csv format for data frames since this file type is +compatible with R as well as Microsoft Word and other programs like +LibreOffice.

+
?write.csv
+
+# For the RFID data, create a vector of the working directory and the file name that you will use for the argument file in write.csv()
+# The function file.path() will combine both pieces of information into a single file path
+rfid_file <- file.path(path, "simulated_rfid_data.csv")
+rfid_file
+
+# Write out the data frame as a .csv spreadsheet. Do not include row names
+write.csv(x = sim_dats_rfid, file = rfid_file, row.names = FALSE)
+

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

+

You can also write out a spreadsheet for the infrared beam breaker +dataset.

+
irbb_file <- file.path(path, "simulated_irbb_data.csv")
+irbb_file
+
+write.csv(sim_dats_irbb, irbb_file, row.names = FALSE)
+

Check that these 2 files now exist in your working directory.

+
list.files(path)
+

In the next vignette, you will use these spreadsheets of simulated +data in the ABISSMAL data processing and analysis workflow.

+ + + + +
+ + + + + + + + + + + + + + + From b6bfa2858d90bb6368f6837702b683171e717d3b Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Fri, 29 Dec 2023 16:44:44 -0500 Subject: [PATCH 05/69] [PCT-472]: Started a draft of the 4th vignette, added more notes to the README about next steps --- R/vignettes/.Rhistory | 348 ++++++++++++++++++++++++ R/vignettes/README.md | 9 +- R/vignettes/Vignette_04_ProcessData.Rmd | 58 ++++ 3 files changed, 414 insertions(+), 1 deletion(-) create mode 100644 R/vignettes/Vignette_04_ProcessData.Rmd diff --git a/R/vignettes/.Rhistory b/R/vignettes/.Rhistory index 86834a1..c0f094c 100644 --- a/R/vignettes/.Rhistory +++ b/R/vignettes/.Rhistory @@ -40,3 +40,351 @@ rfid_ts # run the object name to see its contents is.vector(rfid_ts) # a binary value indicating that rfid_ts is a vector class(rfid_ts) # a vector with character data length(rfid_ts) # this vector has 4 elements +rm(list = ls()) # Clean global environment +library(tidyverse) # Load the set of tidyverse packages +path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" +path +# 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") +rfid_ts # run the object name to see its contents +is.vector(rfid_ts) # a binary value indicating that rfid_ts is a vector +class(rfid_ts) # a vector with character data +length(rfid_ts) # this vector has 4 elements +# Simulate timestamps for an entrance, an exit, and then another entrance and exit +irbb_ts <- c("09:59:55", "10:05:05", "10:59:55", "11:05:05") +irbb_ts +rfid_ts +# Simulate timestamps for an entrance, an exit, and then another entrance and exit +irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") +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) +length(rfid_ts) +# 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) +irbb_ts +# Simulate some RFID detection failures +irbb_ts <- c(irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") +glimpse(irbb_ts) +# Simulate some stray beam breaker detections +irbb_ts <- c(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(irbb_ts) +c("1", 1) +# 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) +c(1, 1, TRUE) +# Create a vector with numeric and binary data types +c(1, 1, TRUE, FALSE) +# 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) +?data.frame() +rfid_ts +?rep +# Create a vector with information about the experimental replicate +# The argument x contains the information that will be repeated +# The argument times specifies the number of times that the information will be repeated +exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) +glimpse(exp_rep) +# You can run code like this 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) +length(rfid_ts) == length(exp_rep) +# This statement should yield FALSE +length(rfid_ts) != length(exp_rep) +sim_dats <- data.frame(replicate = exp_rep, rfid_timestamps = rfid_ts) +glimpse(sim_dats) +sim_dats <- data.frame(exp_rep, rfid_ts) +glimpse(sim_dats) +sim_dats <- data.frame(exp_rep, rfid_ts) +glimpse(sim_dats) +sim_dats <- data.frame(replicate = exp_rep, rfid_timestamps = rfid_ts) +glimpse(sim_dats) +?cbind +sim_dats <- cbind(sim_dats, rep("2023")) +glimpse(sim_dats) +# You can use : to create a sequence of numbers to filter elements from indices 5 to 10, which will drop the firsrt 4 elements +rfid_ts[5:10] +length(rfid_ts) +# You can use : to create a sequence of numbers to filter elements from indices 5 to the length of rfid_ts, which will drop the first 4 elements +5:length(rfid_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 a sequence of numbers +seq(froom = 5, to = 10, by = 1) +# You can also use the function `seq()` to create a sequence of numbers +seq(from = 5, to = 10, by = 1) +# You can also use the function `seq()` to create a sequence of numbers +seq(from = 5, to = length(rfid_ts), by = 1) +# 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)] +# If you want to filter out non-consecutive elements, you can create a vector of indices with the function `c()` +c(1, 3, 6, 8) +# 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, 6, 8)] +# 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, 6, 8)] +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[-1:4] # the numbers must be +# 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 +sim_dats <- data.frame(exp_rep, rfid_ts[-c(1:4)]) +glimpse(sim_dats) +sim_dats <- data.frame(exp_rep, rfid_ts[-c(1:4)]) +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) +names(sim_dats) +# Use square bracket indexing and the function `ncol()` to find the last column name +ncol(sim_dats) +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" +# You can confirm that the name was changed correctly +names(sim_dats) +glimpse(sim_dats) +# 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) +?mutate +# Use the tidyverse to add the year as the 3rd column +sim_dats %>% +glimpse() %>% +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() %>% +# Also add columns for the month and day +dplyr::mutate( +month = 08, +day = 01 +) %>% +glimpse() +# 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 +# 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 = 2023 +) %>% +glimpse() %>% # See the structure of 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 sim_dats with the additional new columns month and day +sim_dats <- cbind(sim_dats, rep(2023, length(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) +sim_dats <- cbind(sim_dats, rep(2023, length(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) +# 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 +# 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 = 2023 +) %>% +glimpse() %>% # See the structure of 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 sim_dats with the additional new columns month and day +sim_dats %>% +glimpse() %>% +dplyr::mutate( +year = c(2021, 2022, 2023) +) %>% +glimpse() +# Make the data frame again with only 2 columns +sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) +sim_dats <- sim_dats %>% +dplyr::mutate( +year = 2023 +) %>% +dplyr::mutate( +month = 08, +day = 01 +) +glimpse(sim_dats) +# Make the data frame again with only 2 columns +sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) +sim_dats <- sim_dats %>% +dplyr::mutate( +year = 2023 +) %>% +dplyr::mutate( +month = 08, +day = 01 +) %>% +dplyr::mutate( +c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2)) +) +glimpse(sim_dats) +# Make the data frame again with only 2 columns +sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) +sim_dats <- sim_dats %>% +dplyr::mutate( +year = 2023 +) %>% +dplyr::mutate( +month = 08, +day = 01 +) %>% +dplyr::mutate( +PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2)) +) +glimpse(sim_dats) +exp_rep <- rep(x = "Pair_01", times = length(irbb_ts)) +# Overwrite the vector exp_rep with a new vector the same length as irbb_ts +exp_rep <- rep(x = "Pair_01", times = length(irbb_ts)) +sim_dats_irbb <- data.frame(replicate = exp_rep, timestamps = irbb_ts) +# Overwrite the vector exp_rep with a new vector the same length as irbb_ts +exp_rep <- rep(x = "Pair_01", times = length(irbb_ts)) +sim_dats_irbb <- data.frame(replicate = exp_rep, timestamps = irbb_ts) +sim_dats_irbb <- sim_dats_irbb %>% +dplyr::mutate( +year = 2023, +month = 08, +day = 01 +) +glimpse(sim_dats_irbb) +?write.csv +?write.csv +# For the RFID data, create a vector of the working directory and the file name that you will use for the argument file in write.csv() +# The function file.path() will combine both pieces of information into a single file path +rfid_file <- file.path(path, "simulated_rfid_data.csv") +rfid_file +irbb_file <- file.path(path, "simulated_irbb_data.csv") +irbb_file +list.files(path) +# For the RFID data, create a vector of the working directory and the file name that you will use for the argument file in write.csv() +# The function file.path() will combine both pieces of information into a single file path +rfid_file <- file.path(path, "simulated_rfid_data.csv") +rfid_file +# Write out the data frame as a .csv spreadsheet. Do not include row names +write.csv(x = sim_dats_rfid, file = rfid_file, row.names = FALSE) +sim_dats_rfid <- data.frame(replicate = exp_rep, timestamps = rfid_ts) +# Overwrite the data frame with the modified version that has more columns +sim_dats_rfid <- sim_dats_rfid %>% +dplyr::mutate( +year = 2023 +) %>% +dplyr::mutate( +month = 08, +day = 01 +) %>% +# Allocate the first half of the detections to the first individual using nrow(.)/2, and the second half of the detctions to the second individual using nrow(.)/2 again. Both of the rep() expressions are combined into a single vector using the c() function +dplyr::mutate( +PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2)) +) +exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) +sim_dats_rfid <- data.frame(replicate = exp_rep, timestamps = rfid_ts) +# Overwrite the data frame with the modified version that has more columns +sim_dats_rfid <- sim_dats_rfid %>% +dplyr::mutate( +year = 2023 +) %>% +dplyr::mutate( +month = 08, +day = 01 +) %>% +# Allocate the first half of the detections to the first individual using nrow(.)/2, and the second half of the detctions to the second individual using nrow(.)/2 again. Both of the rep() expressions are combined into a single vector using the c() function +dplyr::mutate( +PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2)) +) +glimpse(sim_dats_rfid) +# Make a vector for the experimental replicate +exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) +# Make the data frame with the experimental replicate metadata and the timestamps +sim_dats_rfid <- data.frame(replicate = 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 +) %>% +# Allocate the first half of the detections to the first individual using nrow(.)/2, and the second half of the detctions to the second individual using nrow(.)/2 again. Both of the rep() expressions are combined into a single vector using the c() function +dplyr::mutate( +PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2)) +) +glimpse(sim_dats_rfid) +# Overwrite the vector exp_rep with a new vector the same length as irbb_ts +exp_rep <- rep(x = "Pair_01", times = length(irbb_ts)) +sim_dats_irbb <- data.frame(replicate = exp_rep, timestamps = irbb_ts) +sim_dats_irbb <- sim_dats_irbb %>% +dplyr::mutate( +year = 2023, +month = 08, +day = 01 +) +glimpse(sim_dats_irbb) +# For the RFID data, create a vector of the working directory and the file name that you will use for the argument file in write.csv() +# The function file.path() will combine both pieces of information into a single file path +rfid_file <- file.path(path, "simulated_rfid_data.csv") +rfid_file +# Write out the data frame as a .csv spreadsheet. Do not include row names +write.csv(x = sim_dats_rfid, file = rfid_file, row.names = FALSE) +irbb_file <- file.path(path, "simulated_irbb_data.csv") +irbb_file +write.csv(sim_dats_irbb, rfid_file, row.names = FALSE) +list.files(path) +irbb_file <- file.path(path, "simulated_irbb_data.csv") +irbb_file +write.csv(sim_dats_irbb, rfid_file, row.names = FALSE) +# For the RFID data, create a vector of the working directory and the file name that you will use for the argument file in write.csv() +# The function file.path() will combine both pieces of information into a single file path +rfid_file <- file.path(path, "simulated_rfid_data.csv") +rfid_file +# Write out the data frame as a .csv spreadsheet. Do not include row names +write.csv(x = sim_dats_rfid, file = rfid_file, row.names = FALSE) +irbb_file <- file.path(path, "simulated_irbb_data.csv") +irbb_file +write.csv(sim_dats_irbb, irbb_file, row.names = FALSE) +list.files(path) diff --git a/R/vignettes/README.md b/R/vignettes/README.md index b3c71b2..a37f5a9 100644 --- a/R/vignettes/README.md +++ b/R/vignettes/README.md @@ -11,6 +11,9 @@ The goal of these tutorials is to disseminate basic R coding skills in a biologi 1. Introduction to RStudio, download ABISSMAL GitHub repository, introduction to the ABISSMAL data processing / analysis workflow +- Need to add to the Intro how to report bugs in vignettes as GitHub issues +- Also add how to troubleshoot when an expression isn't complete in the console + 2. Setup a virtual workspace (global environment, package installation, working directory, RMarkdown files) 3. Create simulated data, including entrances, exits, and perching events @@ -19,12 +22,16 @@ The goal of these tutorials is to disseminate basic R coding skills in a biologi 5. Make barcode style visualizations of the raw and per-processed data -6. Detect clusters, score clusters, generate summary statistics. +- For vignette 05, I want to make 3 barcode style visuals: 1) the raw combined RFID data, perching events and RFID pre-processed,2) IRBB raw combined and pre-processed, then 3) pre-processed RFID and IRBB together. All of these will be faceted plots with ggplot + +6. Detect clusters, score clusters, generate summary statistics Things that would be important to add: - Loops - Errors and troubleshooting online +I want to make a pre- and post-vignette Google form to asses the content and style of the vignettes for disseminating basic R coding skills in a biological context + Note that code for processing and analyzing biological data collected from zebra finches, and code to make figures, is published online as part of a submitted manuscript for the ABISSMAL tracking system. Also note that the knitted output for these tutorials must be visible from the ABISSMAL website. This is especially important for accessibility of the Introduction for people who haven't downloaded the ABISSMAL repo yet \ No newline at end of file diff --git a/R/vignettes/Vignette_04_ProcessData.Rmd b/R/vignettes/Vignette_04_ProcessData.Rmd new file mode 100644 index 0000000..e50786d --- /dev/null +++ b/R/vignettes/Vignette_04_ProcessData.Rmd @@ -0,0 +1,58 @@ +--- +title: "Vignette 04: Process Data" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: html_document +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = TRUE) + +``` + +

Vignette Overview and Learning Objectives

+ +In this fourth vignette, you will pre-process the simulated datasets of animal movements detected by different sensors. Throughout the process of data pre-processing, you will continue to use coding skills that you learned in the previous vignettes, and you will learn additional skills that include: + +1. Sourcing custom functions +2. Using custom functions +2. Reading files from your computer into R + +

Load packages and your working directory path

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

Load ABISSMAL functions

+ +In this vignette, you will use the the R functions available through the ABISSMAL GitHub repository. These functions are stored in physical files (extension .R) inside the local version of the ABISSMAL repository on your computer. You need to load these files into R so that the functions you want to use are available in your global environment. + +TKTK In the previous script I really should make data across days...so that combining the raw data actually does something +```{r} + +# Load the function that combines raw data +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/combine_raw_data.R") + +# Load the function that detects perching events in the raw data +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_perching_events.R") + +# Load the function that pre-processes raw data +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/preprocess_detections.R") + +# Load a script with utility functions that each function above requires +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") + +``` + +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 main functions above (`combine_raw_data`, `detect_perching_events`, `preprocess_detections`) are all loaded in your global environment. 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. + From 8cccb29a3fecba2a38b4c796e36fb2ee455613e0 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Sat, 30 Dec 2023 17:51:37 -0500 Subject: [PATCH 06/69] [PCT-472]: Updated the third vignette to have simulate data over two days, added more notes --- R/vignettes/.Rhistory | 210 +++- R/vignettes/README.md | 2 + R/vignettes/Vignette_03_SimulateData.Rmd | 168 ++- R/vignettes/Vignette_03_SimulateData.html | 1368 ++++++++++++++++++++- 4 files changed, 1668 insertions(+), 80 deletions(-) diff --git a/R/vignettes/.Rhistory b/R/vignettes/.Rhistory index c0f094c..293c29e 100644 --- a/R/vignettes/.Rhistory +++ b/R/vignettes/.Rhistory @@ -1,47 +1,3 @@ -rm(list = ls()) -rm(list = ls()) -rm(list = ls()) -?ls -?remove -?rm -?ls -?rm -?ls -ls() -getwd() -getwd() -?dir.create -dir.create("/home/gsvidaurre/Desktop/ABISSMAL_vignettes") -?file.copy -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" -) -list.files(path = "/home/gsvidaurre/Desktop/ABISSMAL_vignettes") -file.remove("/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Vignette_01_Introduction_copy.Rmd") -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" -) -list.files(path = "/home/gsvidaurre/Desktop/ABISSMAL_vignettes") -file.remove("/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Vignette_01_Introduction_copy.Rmd") -path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" -path -rm(list = "path") -?c -rfid_ts # run the object name to see its contents -# Create a vector of 4 RFID timestamps in HH:MM:SS format -rfid_ts <- c("01:00:00", "02:00:00", "01:05:00", "02:05:00") -rfid_ts # run the object name to see its contents -class(rfid_ts) -length(rfid_ts) -is.vector(rfid_ts) -rfid_ts # run the object name to see its contents -is.vector(rfid_ts) # a binary value indicating that rfid_ts is a vector -class(rfid_ts) # a vector with character data -length(rfid_ts) # this vector has 4 elements -rm(list = ls()) # Clean global environment -library(tidyverse) # Load the set of tidyverse packages path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" path # Create a vector of 4 RFID timestamps in HH:MM:SS format @@ -388,3 +344,169 @@ irbb_file <- file.path(path, "simulated_irbb_data.csv") irbb_file write.csv(sim_dats_irbb, irbb_file, row.names = FALSE) list.files(path) +# Load the function that combines raw data +source("/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R/combine_raw_data.R") +# Load the function that combines raw data +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/combine_raw_data.R") +# Load the function that detects perching events in the raw data +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_perching_events.R") +# Load the function that pre-processes raw data +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/preprocess_detections.R") +# Load a script with utility functions that each function above requires +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") +View(detect_perching_events) +sim_dats_rfid <- sim_dats_rfid %>% +bind_rows( +sim_dats_rfid %>% +dplyr::mutate( +day = 02 +) +) +rm(list = ls()) # Clean global environment +library(tidyverse) # Load the set of tidyverse packages +path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" +# Make a vector for the experimental replicate +exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) +# 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") +# Make the data frame again with only 2 columns +sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) +# Make a vector for the experimental replicate +exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) +# Make the data frame with the experimental replicate metadata and the timestamps +sim_dats_rfid <- data.frame(replicate = 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 +) %>% +# Allocate the first half of the detections to the first individual using nrow(.)/2, and the second half of the detctions to the second individual using nrow(.)/2 again. Both of the rep() expressions are combined into a single vector using the c() function +dplyr::mutate( +PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2)) +) +glimpse(sim_dats_rfid) +sim_dats_rfid <- sim_dats_rfid %>% +bind_rows( +sim_dats_rfid %>% +dplyr::mutate( +day = 02 +) +) +glimpse(sim_dats_rfid) +# Make a vector for the experimental replicate +exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) +# Make the data frame with the experimental replicate metadata and the timestamps +sim_dats_rfid <- data.frame(replicate = 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 +) %>% +# Allocate the first half of the detections to the first individual using nrow(.)/2, and the second half of the detctions to the second individual using nrow(.)/2 again. Both of the rep() expressions are combined into a single vector using the c() function +dplyr::mutate( +PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2)) +) +glimpse(sim_dats_rfid) +# 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") +# Simulate timestamps for an entrance, an exit, and then another entrance and exit +irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") +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") +# Simulate some RFID detection failures +irbb_ts <- c(irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") +glimpse(irbb_ts) +# Simulate some stray beam breaker detections +irbb_ts <- c(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(irbb_ts) +# Make a vector for the experimental replicate +exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) +# Make the data frame with the experimental replicate metadata and the timestamps +sim_dats_rfid <- data.frame(replicate = 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 +) %>% +# Allocate the first half of the detections to the first individual using nrow(.)/2, and the second half of the detctions to the second individual using nrow(.)/2 again. Both of the rep() expressions are combined into a single vector using the c() function +dplyr::mutate( +PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2)) +) +glimpse(sim_dats_rfid) +sim_dats_rfid <- sim_dats_rfid %>% +bind_rows( +sim_dats_rfid %>% +dplyr::mutate( +day = 02 +) +) +glimpse(sim_dats_rfid) +unique(sim_dats_rfid) +unique(sim_dats_rfid$day) +# 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 +sim_dats_rfid$day +# 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 +sim_dats[["day"]] +sim_dats_rfid[["day"]] +# The name of a data frame object, followed by a $ sign and then the name of a column allows you to pull out one column at a time from a data frame. A data frame column is a vector, so when you run this code you will see a vector of values print to the console +sim_dats_rfid$day +# You can also access a column in a data frame by using double square brackets after the name of the data frame, and placing the column name in quotes inside of the inner brackets +sim_dats_rfid[["day"]] +# The name of a data frame object, followed by a $ sign and then the name of a column allows you to pull out one column at a time from a data frame. A data frame column is a vector, so when you run this code you will see a vector of values print to the console +sim_dats_rfid$day +# You can also access a column in a data frame by using double square brackets after the name of the data frame, and placing the column name in quotes inside of the inner brackets +sim_dats_rfid[["day"]] +# You can use the function unique() to see the unique values of any vector, including a column of a data frame +unique(sim_dats_rfid$day) +unique(sim_dats_rfid[["day"]]) +sim_dats_rfid %>% +pull(day) %>% +unique() +sim_dats_rfid %>% +pull(day) %>% +unique() +# Overwrite the vector exp_rep with a new vector the same length as irbb_ts +exp_rep <- rep(x = "Pair_01", times = length(irbb_ts)) +sim_dats_irbb <- data.frame(replicate = exp_rep, timestamps = irbb_ts) +sim_dats_irbb <- sim_dats_irbb %>% +dplyr::mutate( +year = 2023, +month = 08, +day = 01 +) +glimpse(sim_dats_irbb) +sim_dats_irbb <- sim_dats_irbb %>% +bind_rows( +sim_dats_irbb %>% +dplyr::mutate( +day = 02 +) +) +glimpse(sim_dats_irbb) # Double the number of rows, looks good +19*2 +# Two days, looks good +sim_dats_irbb %>% +pull(day) %>% +unique() +# For the RFID data, create a vector of the working directory and the file name that you will use for the argument file in write.csv() +# The function file.path() will combine both pieces of information into a single file path +rfid_file <- file.path(path, "simulated_rfid_data.csv") +rfid_file +# Write out the data frame as a .csv spreadsheet. Do not include row names +write.csv(x = sim_dats_rfid, file = rfid_file, row.names = FALSE) +irbb_file <- file.path(path, "simulated_irbb_data.csv") +irbb_file +write.csv(sim_dats_irbb, irbb_file, row.names = FALSE) +list.files(path) diff --git a/R/vignettes/README.md b/R/vignettes/README.md index a37f5a9..65d56b1 100644 --- a/R/vignettes/README.md +++ b/R/vignettes/README.md @@ -13,6 +13,7 @@ The goal of these tutorials is to disseminate basic R coding skills in a biologi - Need to add to the Intro how to report bugs in vignettes as GitHub issues - Also add how to troubleshoot when an expression isn't complete in the console +- Tab completion, keyboard shortcuts 2. Setup a virtual workspace (global environment, package installation, working directory, RMarkdown files) @@ -29,6 +30,7 @@ The goal of these tutorials is to disseminate basic R coding skills in a biologi Things that would be important to add: - Loops - Errors and troubleshooting online +- Color code chunks that are for testing code versus chunks that are for creating necessary objects (https://bookdown.org/yihui/rmarkdown-cookbook/chunk-styling.html) I want to make a pre- and post-vignette Google form to asses the content and style of the vignettes for disseminating basic R coding skills in a biological context diff --git a/R/vignettes/Vignette_03_SimulateData.Rmd b/R/vignettes/Vignette_03_SimulateData.Rmd index 9b4cb8d..283dfd0 100644 --- a/R/vignettes/Vignette_03_SimulateData.Rmd +++ b/R/vignettes/Vignette_03_SimulateData.Rmd @@ -2,7 +2,12 @@ title: "Vignette 03: Simulate Data" author: "Grace Smith-Vidaurre" date: "2023-12-27" -output: html_document +output: + html_document: + toc: true + toc_float: true + toc_depth: 4 + theme: lumen --- ```{r setup, include = FALSE} @@ -11,7 +16,81 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE) ``` -

Vignette Overview and Learning Objectives

+```{css, echo = FALSE} + +/* Header 1 */ +h1 { + + font-size: 25px; + font-weight: bold; + +} + +/* Header 2 */ +h2 { + + font-size: 24px; + font-weight: bold; + +} + +/* Header 3 */ +h3 { + + font-size: 22px; + font-weight: bold; + +} + +/* Header 4 */ +h4 { + + font-size: 20px; + font-weight: bold; + +} + +/*Body text */ +body { + + font-size: 16px; + +} + +/* Code inside of chunks */ +pre code { + + font-size: 16px; + color: #708090; + background-color: #F8F8FF; + +} + +/* Code inside of text (wrapped in ``)*/ +code { + + font-size: 16px; + +} + +/* Unordered list */ +ul { + + font-size: 16px; + +} + +/* Ordered list */ +ol { + + font-size: 16px; + +} + +``` + + +

Vignette Overview and Learning Objectives

In this third vignette, we will create simulated datasets of animal movements detected by different sensors. Throughout the process of creating these simulated datasets, you will continue to use coding skills that you learned in vignette 02, and you will learn additional skills that include: @@ -22,7 +101,7 @@ In this third vignette, we will create simulated datasets of animal movements de 5. Piping expressions through the tidyverse 6. Saving objects manipulated in R as physical files on your computer -

Load packages

+

Load packages

In the previous vignette, you installed a set of data science packages called the `tidyverse`. You also learned about working directories and created a directory on your computer to save data that you generate from this and subsequent vignettes. @@ -35,7 +114,7 @@ library(tidyverse) # Load the set of tidyverse packages ``` -

Create a path object

+

Create a path object

The next setup step is to specify your working directory. In vignette 02, you used a string (text contained in double quotes to indicate character information) to specify your working directory while using different functions. Using the same long character string over and over is not very efficient, so you can instead save the character string for your working directory inside a new object in R. @@ -57,7 +136,7 @@ You can also see the contents of `path` by clicking on the "Environment" tab and You can practice removing the `path` object from your global environment by writing and running the code `rm(list = "path")`. After doing this, make sure to recreate `path` using the code above. -

Create timestamps of simulated detections of animal movements

+

Create timestamps of simulated detections of animal movements

The primary data collected by the ABISSMAL tracking system are timestamps indicating the moment of time when a movement sensor triggered. 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. @@ -140,7 +219,7 @@ c(1, 1, TRUE, FALSE) When you try to combine character, numeric, and binary data types in the same vector, all vector elements will be forced to the character data type. A similar thing happens when you try to combine numeric and binary data types, but the vector is forced to numeric. Note that the binary values TRUE and FALSE are equivalent to the numeric values 1 and 0, respectively. -

Create data frames with primary data and metadata

+

Create data frames with primary data and metadata

You need to be able to create metadata of different data types that can accompany the timestamps for each sensor. To do this, 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 different data types in the same data frame. You can also write out data frames to spreadsheets that exist as physical files on your computer. @@ -268,7 +347,7 @@ glimpse(sim_dats) ``` -

Create data frames using the tidyverse

+

Create data frames using the tidyverse

In the chunk above, you used base R code add a new column and to update the column name. It took more than a few lines of code to carry out these operations. You could reduce the amount of code needed for these steps by removing the lines included to check your work. But another way that you can reduce the amount of code you're writing for these operations is to use tidyverse notation and functions: ```{r} @@ -330,7 +409,9 @@ sim_dats %>% 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. -In the chunks of code above that inserted `glimpse()` between piping operations, you did not save the new sim_dats as an object. You can create the updated data frame now, and also add another column with 2 unique PIT tag identifiers (1 for each simulated individual): +

Create a final data frame for the RFID data

+ +In the chunks of code above that inserted `glimpse()` between piping operations, you did not save the modifications that you made to `sim_dats` in an object (the output was printed to the console only). You can create an object with the updated data frame now. You'll also add another column with 2 unique PIT tag identifiers (1 for each simulated individual): ```{r} # Make a vector for the experimental replicate @@ -357,7 +438,54 @@ glimpse(sim_dats_rfid) ``` -Next, repeat this process of creating a data frame with metadata for the infrared beam breaker dataset. Since the beam breakers do not collect unique individual identity information, you will add columns for the year, month, and day only. +You can simulate data collection over more than one day as well. 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 object to which you want to append rows. Then the code inside of `bind_rows()` indicates the data frame (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 you're using to modify the `day` column to reflect a subsequent day of data collection. +```{r} + +sim_dats_rfid <- sim_dats_rfid %>% + bind_rows( + sim_dats_rfid %>% + dplyr::mutate( + day = 02 + ) + ) + +glimpse(sim_dats_rfid) # Double the number of rows, looks good + +``` + +

Check data frame columns with base R

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

Check data frame columns with the tidyverse

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

Create a final data frame for the beam breaker data

+ +Next, repeat this process of creating a data frame with metadata for the infrared beam breaker dataset. Since the beam breakers do not collect unique individual identity information, you will add columns for the year, month, and day only. You will also simulate data collection for the beam breakers over the same two days as the RFID system. ```{r} # Overwrite the vector exp_rep with a new vector the same length as irbb_ts @@ -374,9 +502,24 @@ sim_dats_irbb <- sim_dats_irbb %>% glimpse(sim_dats_irbb) +sim_dats_irbb <- sim_dats_irbb %>% + bind_rows( + sim_dats_irbb %>% + dplyr::mutate( + day = 02 + ) + ) + +glimpse(sim_dats_irbb) # Double the number of rows, looks good + +# Two days, looks good +sim_dats_irbb %>% + pull(day) %>% + unique() + ``` -

Save each data frame as a physical file

+

Save each data frame as a physical file

Now that you made data frames of the timestamps and metadata per sensor, you can write out or save these data frames as physical files in your working directory. You have many different file type options, but I recommend using .csv format for data frames since this file type is compatible with R as well as Microsoft Word and other programs like LibreOffice. ```{r eval = FALSE} @@ -393,7 +536,7 @@ write.csv(x = sim_dats_rfid, file = rfid_file, row.names = FALSE) ``` -As specified in the function documentation for `write.csv()`, by default the function will include column names in the resulting spreadsheet. The function will also not append new information to the .csv if it already exists. If you already created this file then it will be overwritten when you run the function again. +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. If you already created this file then it will be overwritten when you run the function again. You can also write out a spreadsheet for the infrared beam breaker dataset. ```{r eval = FALSE} @@ -412,5 +555,4 @@ list.files(path) ``` -In the next vignette, you will use these spreadsheets of simulated data in the ABISSMAL data processing and analysis workflow. - +In the next vignette, you will use these spreadsheets of simulated data in the ABISSMAL data processing and analysis workflow. \ No newline at end of file diff --git a/R/vignettes/Vignette_03_SimulateData.html b/R/vignettes/Vignette_03_SimulateData.html index 54ab989..0367bd0 100644 --- a/R/vignettes/Vignette_03_SimulateData.html +++ b/R/vignettes/Vignette_03_SimulateData.html @@ -32,7 +32,31 @@ !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +

+Vignette Overview and Learning Objectives +

+

In this fourth vignette, you will write out spreadsheets of simulated +detections of animal movements to your computer. You’ll begin using this +data in the ABISSMAL data processing and analysis workflow, including +combining raw data across days and pre-processing the raw data. You will +continue to use coding skills that you learned in the previous +vignettes, and you will learn additional skills that include:

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

+Load packages and your working directory path +

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

+Create the simulated data +

+

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

+
# Create a vector of 4 RFID timestamps in HH:MM:SS format
+rfid_ts <- c("10:00:00", "10:05:00", "11:00:00", "11:05:00")
+
+# Add perching events to the RFID data
+rfid_ts <- c(rfid_ts, "08:00:00", "08:00:01", "08:00:02", "08:00:03", "11:30:00", "11:30:01", "11:30:02", "11:30:03", "11:30:04", "11:30:05")
+
+glimpse(rfid_ts)
+
##  chr [1:14] "10:00:00" "10:05:00" "11:00:00" "11:05:00" "08:00:00" ...
+
# For the beam breakers, simulate timestamps for an entrance, an exit, and then another entrance and exit
+irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01")
+
+# Simulate some RFID detection failures for the beam breaker data
+irbb_ts <- c(irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25")
+
+# Simulate some stray beam breaker detections
+irbb_ts <- c(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(irbb_ts)
+
##  chr [1:19] "09:59:59" "10:05:01" "10:59:59" "11:05:01" "06:05:05" ...
+

+Simulate 2 days of RFID data collection +

+

In the code below, you’ll use 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 = "Pair_01", times = length(rfid_ts))
+
+# Make the data frame with the experimental replicate metadata and the timestamps
+sim_dats_rfid <- data.frame(replicate = 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
+  ) %>%
+  # Allocate the first half of the detections to the first individual using nrow(.)/2, and the second half of the detctions to the second individual using nrow(.)/2 again. Both of the rep() expressions are combined into a single vector using the c() function
+  dplyr::mutate(
+    PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2))
+  ) %>% 
+  dplyr::mutate(
+    sensor_id = "RFID"
+  )
+
+glimpse(sim_dats_rfid)
+
## Rows: 14
+## Columns: 7
+## $ 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
+## $ 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 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.

+
sim_dats_rfid <- sim_dats_rfid %>% 
+  bind_rows(
+    sim_dats_rfid %>% 
+      dplyr::mutate(
+        day = 02
+      )
+  )
+
+glimpse(sim_dats_rfid) # Double the number of rows, looks good
+
## Rows: 28
+## Columns: 7
+## $ 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, 8, 8, 8, 8, 8, 8,…
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2,…
+## $ PIT_tag    <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabbcc", "13…
+## $ sensor_id  <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+

+Check data frame columns with base R +

+

You checked that simulated data frame has data collected over two +days 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
+
# 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
+
# 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) # Two days, looks good
+
## [1] 1 2
+
unique(sim_dats_rfid[["day"]]) # Two days, looks good
+
## [1] 1 2
+

+Check data frame columns with the tidyverse +

+

You can also check the unique values in a column using functions from +the tidyverse. In the expression below, you’re piping +sim_dats_rfid into the function pull(), which +lets you pull the column day out of the data frame as a +vector, and then that vector is piped into the function +unique() to check the unique values contained in the +vector. 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.

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

+Simulate 2 days of beam breaker data collection +

+

Next, repeat this process of creating a data frame with metadata for +the infrared beam breaker dataset. 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 two days as the RFID +system.

+
# Overwrite the vector exp_rep with a new vector the same length as irbb_ts
+exp_rep <- rep(x = "Pair_01", times = length(irbb_ts))
+
+sim_dats_irbb <- data.frame(replicate = exp_rep, timestamps = irbb_ts)
+
+sim_dats_irbb <- sim_dats_irbb %>%
+  dplyr::mutate(
+    year = 2023,
+    month = 08,
+    day = 01,
+    sensor_id = "Beam breakers"
+  )
+
+glimpse(sim_dats_irbb)
+
## Rows: 19
+## Columns: 6
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ 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
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+## $ sensor_id  <chr> "Beam breakers", "Beam breakers", "Beam breakers", "Beam br…
+
sim_dats_irbb <- sim_dats_irbb %>% 
+  bind_rows(
+    sim_dats_irbb %>% 
+      dplyr::mutate(
+        day = 02
+      )
+  )
+
+glimpse(sim_dats_irbb) # Double the number of rows, looks good
+
## Rows: 38
+## Columns: 6
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ 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, 2,…
+## $ sensor_id  <chr> "Beam breakers", "Beam breakers", "Beam breakers", "Beam br…
+
# Two days, looks good
+sim_dats_irbb %>% 
+  pull(day) %>% 
+  unique()
+
## [1] 1 2
+

+Save a data frame as a physical file +

+

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")
+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 the function will use 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. 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. In the second usage of list.files() below, +you’re using a new argument called pattern that makes it +possible to customize a search. Using the argument pattern +here is similar to searching for a specific word inside of a text +document. Below, 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 all files in the given path
+list.files(path)
+
+# List only files that end in the pattern ".csv" in the given path
+list.files(path, pattern = ".csv$")
+

+Read in a spreadsheet +

+

% glimpse()

+

+Now that you've practiced using `write.csv()` and `read.csv()`, you can delete the temporary file that you created by feeding the `rfid_file` object to the function `file.remove()`.
+
+```r
+rfid_file <- file.path(path, "test_file.csv")
+rfid_file
+
+file.remove(rfid_file)
+

+Save simulated data for the ABISSMAL workflow +

+

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

+

+Filter a data frame +

+

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

+
# Pipe the data frame into the filter() function
+sim_dats_rfid %>% 
+  # Filter the data frame by pulling out all rows in which the day column was equal to 1
+  dplyr::filter(day == 1) %>%
+  glimpse()
+
## Rows: 14
+## Columns: 7
+## $ 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
+## $ 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 can similar results by inverting the conditional statement +inside of dplyr::filter() to remove days that are not the +first day of data collection.

+
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) %>%
+  glimpse()
+
## Rows: 14
+## Columns: 7
+## $ 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
+## $ 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) %>%
+  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 the raw RFID data
+file.path(path, "RFID") # Check the new path
+dir.create(file.path(path, "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, "RFID", "test.csv")
+rfid_file
+
+sim_dats_rfid %>% 
+  dplyr::filter(day == 1) %>% 
+  # Write out the 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 by day 1
+  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, "RFID"), pattern = ".csv$")
+
+rfid_file <- file.path(path, "RFID", "test.csv")
+rfid_file
+
+file.remove(rfid_file)
+

+Practicing writing a loop +

+

In order to write out a data frame for each day of data collection +per sensor, you could repeat the code above 4 times (twice per sensor). +But it’s good to avoid repeating 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, "RFID", "test1.csv"), file.path(path, "RFID", "test2.csv"))
+
+files
+
## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/RFID/test1.csv"
+## [2] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/RFID/test2.csv"
+
length(files)
+
## [1] 2
+
# 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 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 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
+

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 and +setting x as the iterating variable will not affect other +lines of code outside of the loop.

+

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/RFID/test1.csv"
+## 
+## [[2]]
+## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/RFID/test2.csv"
+

You can also modify the code inside 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){
+  
+  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 .csv files were written out:

+
list.files(file.path(path, "RFID"))
+
## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.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 add the data frame filtering that +you learned above to the loop. In the code below, you’ll create another +vector object called days, and then use the iterating +variable to filter sim_dats_rfid by each day in +days.

+
days <- c(1, 2)
+
+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.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. The string that +you passed 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, "RFID"), pattern = "^test", full.names = TRUE)
+rem_files
+
+file.remove(rem_files)
+

+Use a loop to save RFID spreadsheets +

+

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

+
# Make a vector of the custom file names to write out
+files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv")
+
+# Add the file path for the correct directory
+files <- file.path(path, "RFID", files) 
+files
+
+# Make a vector of the days to write out (1 day per iteration of the loop)
+days <- c(1, 2)
+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.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 as an object in R. 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.

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

You can remove these files.

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

You can continue practicing loops by repeating this process for the +infrared beam breaker dataset.

+

+Write a nested loop to save spreadsheets +

+

If you want to cut down on the amount of code that you’re writing, +you could write out both the RFID and beam breaker data in the same +loop. First you’ll need to create another directory for the beam breaker +data:

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

To carry out the file filtering and writing, you’ll use a type of +object called a list, and then use these lists inside of a +nested loop:

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

Lists are useful objects to use in R because they’re very flexible. +Unlike vectors, a single list can hold many different types of data. +Unlike a data frame, the elements of a list do not need to be the same +dimension. The elements of a list can also be different data structures +or object types. For instance, lists can contain vectors, data frames, +and other lists all inside of the same object. The list that you created +above has 2 elements, and each element is a vector of 2 strings +containing 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"
+
glimpse(files[1])
+
## List of 1
+##  $ : chr [1:2] "RFID_simulated_Pair-01_2023_08_01.csv" "RFID_simulated_Pair-01_2023_08_02.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"
+
glimpse(files[[1]])
+
##  chr [1:2] "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"),
+  `IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv")
+)
+
+glimpse(files)
+
## List of 2
+##  $ RFID: chr [1:2] "RFID_simulated_Pair-01_2023_08_01.csv" "RFID_simulated_Pair-01_2023_08_02.csv"
+##  $ IRBB: chr [1:2] "IRBB_simulated_Pair-01_2023_08_01.csv" "IRBB_simulated_Pair-01_2023_08_02.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"
+
# 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"
+
files[["RFID"]]
+
## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+

Lists are useful data structures for nested loops. For instance, if +you want to write out a spreadsheet per day per sensor type, you need a +loop to 1) iterate over sensor types, and then a loop to 2) iterate over +days per sensor. You can use lists to create nested data structures to +supply to a nested loop to 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 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"),
+  `IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv")
+)
+
+files
+
## $RFID
+## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## 
+## $IRBB
+## [1] "IRBB_simulated_Pair-01_2023_08_01.csv"
+## [2] "IRBB_simulated_Pair-01_2023_08_02.csv"
+
# Make a list of file paths per sensor that will be used inside of the loop
+file_dirs <- list(
+  `RFID` = file.path(path, "RFID"),
+  `IRBB` = file.path(path, "IRBB") 
+)
+
+file_dirs
+
## $RFID
+## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/RFID"
+## 
+## $IRBB
+## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/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),
+  `IRBB` = c(1, 2)
+)
+
+days
+
## $RFID
+## [1] 1 2
+## 
+## $IRBB
+## [1] 1 2
+
# 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, and you only need to specify a data frame per sensor type here
+dats <- list(
+  `RFID` = sim_dats_rfid,
+  `IRBB` = sim_dats_irbb
+)
+
+glimpse(dats)
+
## List of 2
+##  $ RFID:'data.frame':    28 obs. of  7 variables:
+##   ..$ replicate : chr [1:28] "Pair_01" "Pair_01" "Pair_01" "Pair_01" ...
+##   ..$ timestamps: chr [1:28] "10:00:00" "10:05:00" "11:00:00" "11:05:00" ...
+##   ..$ year      : num [1:28] 2023 2023 2023 2023 2023 ...
+##   ..$ month     : num [1:28] 8 8 8 8 8 8 8 8 8 8 ...
+##   ..$ day       : num [1:28] 1 1 1 1 1 1 1 1 1 1 ...
+##   ..$ PIT_tag   : chr [1:28] "1357aabbcc" "1357aabbcc" "1357aabbcc" "1357aabbcc" ...
+##   ..$ sensor_id : chr [1:28] "RFID" "RFID" "RFID" "RFID" ...
+##  $ IRBB:'data.frame':    38 obs. of  6 variables:
+##   ..$ replicate : chr [1:38] "Pair_01" "Pair_01" "Pair_01" "Pair_01" ...
+##   ..$ timestamps: chr [1:38] "09:59:59" "10:05:01" "10:59:59" "11:05:01" ...
+##   ..$ year      : num [1:38] 2023 2023 2023 2023 2023 ...
+##   ..$ month     : num [1:38] 8 8 8 8 8 8 8 8 8 8 ...
+##   ..$ day       : num [1:38] 1 1 1 1 1 1 1 1 1 1 ...
+##   ..$ sensor_id : chr [1:38] "Beam breakers" "Beam breakers" "Beam breakers" "Beam breakers" ...
+

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

+

After writing out this loop, but before running it, 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 (here, the first iteration for each loop layer).

+

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.

+

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:

+
# Testing
+x <- 1
+y <- 1
+
+# 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
+  
+  # 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 each sensor, iterate over days
+  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 and single bracket filtering to pull out the right file name
+      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 4 files now exist inside +each directory per sensor within your working directory.

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

You learned more about filtering data frames, writing them out to +spreadsheets, and writing 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.

+ + + + +
+ + + + + + + + + + + + + + + From f1b5f4c2fd5e22623d2d195f9651dcfce8c0e0d7 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Tue, 2 Jan 2024 11:29:41 -0500 Subject: [PATCH 14/69] [PCT-472]: Added more notes for next two vignettes --- R/vignettes/README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/R/vignettes/README.md b/R/vignettes/README.md index 2980d1a..92bbe26 100644 --- a/R/vignettes/README.md +++ b/R/vignettes/README.md @@ -20,18 +20,15 @@ The goal of these tutorials is to disseminate basic R coding skills in a biologi 3. Create simulated data, including entrances, exits, and perching events -4. Combine the raw data and pre-process the raw data, detect perching events +4. Save data and practice writing loops -TKTK this is turning into save data only...vignette 04 - -5. Make barcode style visualizations of the raw and per-processed data +5. Combine the raw data, detect perching events and pre-process the raw data, make plots (barcode style visualizations of the raw and per-processed data) - For vignette 05, I want to make 3 barcode style visuals: 1) the raw combined RFID data, perching events and RFID pre-processed,2) IRBB raw combined and pre-processed, then 3) pre-processed RFID and IRBB together. All of these will be faceted plots with ggplot 6. Detect clusters, score clusters, generate summary statistics Things that would be important to add: -- Loops - Errors and troubleshooting online - Color code chunks that are for testing code versus chunks that are for creating necessary objects (https://bookdown.org/yihui/rmarkdown-cookbook/chunk-styling.html) - an explanation about how simulating the data replaces the process that ABISSMAL does to collect data from live animals From f09a9bb567796b12d9f5e2577eeb0ac45508dd4a Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Tue, 2 Jan 2024 18:07:02 -0500 Subject: [PATCH 15/69] [PCT-472]: Started drafting a fifth vignette; continue with making barcode plots --- R/vignettes/.Rhistory | 978 ++++++++++++------------ R/vignettes/Vignette_04_SaveData.Rmd | 46 +- R/vignettes/Vignette_05_ProcessData.Rmd | 154 +++- 3 files changed, 661 insertions(+), 517 deletions(-) diff --git a/R/vignettes/.Rhistory b/R/vignettes/.Rhistory index a4acf26..00572fd 100644 --- a/R/vignettes/.Rhistory +++ b/R/vignettes/.Rhistory @@ -1,512 +1,512 @@ -dplyr::filter(day == 1) %>% -# By default, write.csv() will write out the object that is piped in, so you don't need to specify "x = ." -write.csv(irbb_file, row.names = FALSE) -irbb_file <- file.path(path, "simulated_irbb_data_2023_08_02.csv") -irbb_file -sim_dats_irbb %>% -dplyr::filter(day == 2) %>% -# By default, write.csv() will write out the object that is piped in, so you don't need to specify "x = ." -write.csv(irbb_file, row.names = FALSE) -# Make a vector for the experimental replicate -exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) -# Make the data frame with the experimental replicate metadata and the timestamps -sim_dats_rfid <- data.frame(replicate = 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 -) %>% -# Allocate the first half of the detections to the first individual using nrow(.)/2, and the second half of the detctions to the second individual using nrow(.)/2 again. Both of the rep() expressions are combined into a single vector using the c() function -dplyr::mutate( -PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2)) -) %>% -dplyr::mutate( -sensor_id = "RFID" +files +length(files) +# The argument X is an iterating variable, or 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. The loop will run twice, and each value in X will be used per iteration of the loop 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 of the function() call is the iterating variable, or the variable that will take on a different value in each iteration of the loop +lapply(X = 1:length(files), FUN = function(x){ +x +}) +# 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 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 +}) +x +# 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 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(mmmm){ +mmm +}) +# 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 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(m){ +m +}) +# 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 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(pm){ +pm +}) +# 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 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(pmm){ +pmm +}) +# 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 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(mmm){ +mmm +}) +?function +class(function) +function() +?make.names +# 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 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(pm2){ +pm2 +}) +# 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 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 +}) +# 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 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){ +x_x +}) +# 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 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(2x){ +lapply(X = 1:length(files), FUN = function(x){ +files[x] +}) +lapply(X = 1:length(files), FUN = function(x){ +sim_dats_rfid %>% +write.csv(file = files[x], row.names = FALSE) +}) +list.files(file.path(path, "RFID")) +# Pull out one day at a time +days[x] +days <- c(1, 2) +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.csv(file = files[x], row.names = FALSE) +}) +sim_dats_rfid %>% +# Filter the data frame by one day at a time +dplyr::filter(day == days[x]) %>% +write.csv(file = files[x], row.names = FALSE) +days <- c(1, 2) +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.csv(file = files[x], row.names = FALSE) +}) +# Make a vector of the files to write out +files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv") +files +length(files) +files <- file.path(path, "RFID", files) +files +# 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") +# Add the file path for the correct directory +files <- file.path(path, "RFID", files) +files +list.files(file.path(path, "RFID")) +rem_files <- list.files(file.path(path, "RFID"), pattern = "^test") +rem_files <- list.files(file.path(path, "RFID"), pattern = "^test") +rem_files +rem_files <- list.files(file.path(path, "RFID"), pattern = "^test") +rem_files +rem_files <- list.files(file.path(path, "RFID"), pattern = "^test", full.names = TRUE) +rem_files +rem_files <- list.files(file.path(path, "RFID"), pattern = "^test", full.names = TRUE) +rem_files +file.remove(rem_files) +# 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") +# Add the file path for the correct directory +files <- file.path(path, "RFID", files) +files +# Make a vector of the days to write out (1 day per iteration of the loop) +days <- c(1, 2) +days +# You can drop the lapply() argument names, since you're supplying the arguments in the order that the function expects +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.csv(file = files[x], row.names = FALSE) +}) +# 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.csv(file = files[x], row.names = FALSE) +})) +list.files(file.path(path, "RFID")) +# Make a vector 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"), +`IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") ) -glimpse(sim_dats_rfid) -sim_dats_rfid <- sim_dats_rfid %>% -bind_rows( -sim_dats_rfid %>% -dplyr::mutate( -day = 02 +glimpse(files) +# Make a vector 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"), +`IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") ) +glimpse(files) +# Make a list of the custom file names to write out +files <- list( +c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv"), +c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") ) -glimpse(sim_dats_rfid) # Double the number of rows, looks good -# Two days, looks good -sim_dats_rfid %>% -pull(day) %>% -unique() -# Overwrite the vector exp_rep with a new vector the same length as irbb_ts -exp_rep <- rep(x = "Pair_01", times = length(irbb_ts)) -sim_dats_irbb <- data.frame(replicate = exp_rep, timestamps = irbb_ts) -sim_dats_irbb <- sim_dats_irbb %>% -dplyr::mutate( -year = 2023, -month = 08, -day = 01, -sensor_id = "Beam breakers" +glimpse(files) +files[1] +# Using a single square bracket to filter a list returns the first list element in list format +files[1] +glimpse(files[1]) +# Using double square brackets returns the first list element only, so it removes the list structure and shows the original data structure of that element (here a vector) +files[[1]] +glimpse(files[[1]]) +# 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"), +`IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") ) -glimpse(sim_dats_irbb) -sim_dats_irbb <- sim_dats_irbb %>% -bind_rows( -sim_dats_irbb %>% -dplyr::mutate( -day = 02 +glimpse(files) +files$RFID +files$RFID +files[["RFID"]] +files["RFID"] +# 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"), +`IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") ) +# Add the file path for the correct directory +files <- file.path(path, "RFID", files) +files +# Make a list of file paths per sensor that will be used inside of the loop +file_dirs <- list( +`RFID` = file.path(path, "RFID"), +`IRBB` = file.path(path, "IRBB") ) -glimpse(sim_dats_irbb) # Double the number of rows, looks good -# Two days, looks good -sim_dats_irbb %>% -pull(day) %>% -unique() -# 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() -# One day, looks good -sim_dats_rfid %>% -pull(day) %>% -unique() -# 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() -# One day, looks good -sim_dats_rfid %>% -pull(day) %>% -unique() -# One day, looks good -sim_dats_rfid %>% -dplyr::filter(day == 1) %>% -pull(day) %>% -unique() -# For the RFID data, create a vector of the working directory and the file name that you will use for the argument file in write.csv() -# The function file.path() will combine both pieces of information into a single file path -rfid_file <- file.path(path, "simulated_rfid_data_2023_08_01.csv") -rfid_file -sim_dats_rfid %>% -dplyr::filter(day == 1) %>% -# Write out the 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 by day 1 -write.csv(x = ., file = rfid_file, row.names = FALSE) -# For the RFID data, create a vector of the working directory and the file name that you will use for the argument file in write.csv() -# The function file.path() will combine both pieces of information into a single file path -rfid_file <- file.path(path, "RFID_simulated_Pair-01_2023_08_01.csv") -rfid_file -sim_dats_rfid %>% -dplyr::filter(day == 1) %>% -# Write out the 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 by day 1 -write.csv(x = ., file = rfid_file, row.names = FALSE) -rfid_file <- file.path(path, "RFID_simulated_Pair-01_2023_08_02.csv") -rfid_file -sim_dats_rfid %>% -dplyr::filter(day == 2) %>% -write.csv(x = ., file = rfid_file, row.names = FALSE) -irbb_file <- file.path(path, "IRBB_simulated_Pair-01_2023_08_01.csv") -irbb_file -sim_dats_irbb %>% -dplyr::filter(day == 1) %>% -# By default, write.csv() will write out the object that is piped in, so you don't need to specify "x = ." -write.csv(irbb_file, row.names = FALSE) -irbb_file <- file.path(path, "IRBB_simulated_Pair-01_2023_08_02.csv") -irbb_file -sim_dats_irbb %>% -dplyr::filter(day == 2) %>% -# By default, write.csv() will write out the object that is piped in, so you don't need to specify "x = ." -write.csv(irbb_file, row.names = FALSE) -list.files(path) -rm(list = ls()) # Clean global environment -library(tidyverse) # Load the set of tidyverse packages -path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Initialize an object with the path that is your working directory -# Create 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) -# For the beam breakers, simulate timestamps for an entrance, an exit, and then another entrance and exit -irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") -# Simulate some RFID detection failures for the beam breaker data -irbb_ts <- c(irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") -# Simulate some stray beam breaker detections -irbb_ts <- c(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(irbb_ts) -# Make a vector for the experimental replicate -exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) -# Make the data frame with the experimental replicate metadata and the timestamps -sim_dats_rfid <- data.frame(replicate = 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 -) %>% -# Allocate the first half of the detections to the first individual using nrow(.)/2, and the second half of the detctions to the second individual using nrow(.)/2 again. Both of the rep() expressions are combined into a single vector using the c() function -dplyr::mutate( -PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2)) -) %>% -dplyr::mutate( -sensor_id = "RFID" +file_dirs +# 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"), +`IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") ) -glimpse(sim_dats_rfid) -sim_dats_rfid <- sim_dats_rfid %>% -bind_rows( -sim_dats_rfid %>% -dplyr::mutate( -day = 02 -) -) -glimpse(sim_dats_rfid) # Double the number of rows, looks good -# The name of a data frame object, followed by a $ sign and then the name of a column allows you to pull out one column at a time from a data frame. A data frame column is a vector, so when you run this code you will see a vector of values print to the console -sim_dats_rfid$day -# You can also access a column in a data frame by using double square brackets after the name of the data frame, and placing the column name in quotes inside of the inner brackets -sim_dats_rfid[["day"]] -# You can use the function unique() to see the unique values of any vector, including a column of a data frame -unique(sim_dats_rfid$day) # Two days, looks good -unique(sim_dats_rfid[["day"]]) # Two days, looks good -# Two days, looks good -sim_dats_rfid %>% -pull(day) %>% -unique() -# Overwrite the vector exp_rep with a new vector the same length as irbb_ts -exp_rep <- rep(x = "Pair_01", times = length(irbb_ts)) -sim_dats_irbb <- data.frame(replicate = exp_rep, timestamps = irbb_ts) -sim_dats_irbb <- sim_dats_irbb %>% -dplyr::mutate( -year = 2023, -month = 08, -day = 01, -sensor_id = "Beam breakers" +files +# Make a list of file paths per sensor that will be used inside of the loop +file_dirs <- list( +`RFID` = file.path(path, "RFID"), +`IRBB` = file.path(path, "IRBB") ) -glimpse(sim_dats_irbb) -sim_dats_irbb <- sim_dats_irbb %>% -bind_rows( -sim_dats_irbb %>% -dplyr::mutate( -day = 02 +file_dirs +# Make a list of the days to write out for each sensor +# This could be a single vector rather than a list since you want to write out the same days per sensor, but a list is useful in case you wanted to change the days (and number of days) to write out per sensor +days <- list( +`RFID` = c(1, 2), +`IRBB` = c(1, 2) ) +days +# Make a vector of sensors +sensors <- c("RFID", "IRBB") +sensors +# 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, and you only need to specify a data frame per sensor type here +dats <- list( +`RFID` = sim_dats_rfid, +`IRBB` = sim_dats_irbb ) -glimpse(sim_dats_irbb) # Double the number of rows, looks good -# Two days, looks good -sim_dats_irbb %>% -pull(day) %>% -unique() -# 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(dats) +# Testing +x <- 1 +y <- 1 +# Testing +x <- 1 +y <- 1 +# Testing +x <- 1 +y <- 1 +# 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 <- sensors[x] +days_tmp +# 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 <- sensors[x] +days_tmp +# 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 <- sensors[x] +days_tmp +# 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_tmp[x] +days_tmp +# 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_tmp[x] +days_tmp +# 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[x] +days_tmp +days_tmp <- days[[sensors[x]]] +days_tmp +# 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] +days_tmp <- days[[sensors[x]]] +days_tmp +# 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] +# Testing +x <- 1 +y <- 1 +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 +# 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() -# One day, looks good -sim_dats_rfid %>% -dplyr::filter(day == 1) %>% -pull(day) %>% -unique() -# For the RFID data, create a vector of the working directory and the file name that you will use for the argument file in write.csv() -# The function file.path() will combine both pieces of information into a single file path -rfid_file <- file.path(path, "RFID_simulated_Pair-01_2023_08_01.csv") -rfid_file -sim_dats_rfid %>% -dplyr::filter(day == 1) %>% -# Write out the 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 by day 1 -write.csv(x = ., file = rfid_file, row.names = FALSE) -rfid_file <- file.path(path, "RFID_simulated_Pair-01_2023_08_02.csv") -rfid_file -sim_dats_rfid %>% -dplyr::filter(day == 2) %>% -write.csv(x = ., file = rfid_file, row.names = FALSE) -irbb_file <- file.path(path, "IRBB_simulated_Pair-01_2023_08_01.csv") -irbb_file -sim_dats_irbb %>% -dplyr::filter(day == 1) %>% -# By default, write.csv() will write out the object that is piped in, so you don't need to specify "x = ." -write.csv(irbb_file, row.names = FALSE) -irbb_file <- file.path(path, "IRBB_simulated_Pair-01_2023_08_02.csv") -irbb_file -sim_dats_irbb %>% -dplyr::filter(day == 2) %>% -# By default, write.csv() will write out the object that is piped in, so you don't need to specify "x = ." -write.csv(irbb_file, row.names = FALSE) -list.files(path) -read.csv(file.path(path, "IRBB_simulated_Pair-01_2023_08_02.csv")) -read.csv(file.path(path, "IRBB_simulated_Pair-01_2023_08_02.csv")) %>% -glimpse() -# 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") -rm(list = ls()) # Clean global environment -library(tidyverse) # Load the set of tidyverse packages -path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Initialize an object with the path that is your working directory -# Create 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) -# For the beam breakers, simulate timestamps for an entrance, an exit, and then another entrance and exit -irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") -# Simulate some RFID detection failures for the beam breaker data -irbb_ts <- c(irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") -# Simulate some stray beam breaker detections -irbb_ts <- c(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(irbb_ts) -# Make a vector for the experimental replicate -exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) -# Make the data frame with the experimental replicate metadata and the timestamps -sim_dats_rfid <- data.frame(replicate = 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 -) %>% -# Allocate the first half of the detections to the first individual using nrow(.)/2, and the second half of the detctions to the second individual using nrow(.)/2 again. Both of the rep() expressions are combined into a single vector using the c() function -dplyr::mutate( -PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2)) -) %>% -dplyr::mutate( -sensor_id = "RFID" +# 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] +# 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 <- sensors[x] +# For each sensor, iterate over days +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 and single bracket filtering to pull out the right file name +write.csv(file = files[[x]][y], row.names = FALSE) +}) +})) +# Make a vector of sensor labels +sensors <- c("RFID", "IRBB") +sensors +# Make a named list of the custom file names to write out +files <- list( +`RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv"), +`IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") ) -glimpse(sim_dats_rfid) -sim_dats_rfid <- sim_dats_rfid %>% -bind_rows( -sim_dats_rfid %>% -dplyr::mutate( -day = 02 +files +# Make a list of file paths per sensor that will be used inside of the loop +file_dirs <- list( +`RFID` = file.path(path, "RFID"), +`IRBB` = file.path(path, "IRBB") ) +file_dirs +# Make a list of the days to write out for each sensor +# This could be a single vector rather than a list since you want to write out the same days per sensor, but a list is useful in case you wanted to change the days (and number of days) to write out per sensor +days <- list( +`RFID` = c(1, 2), +`IRBB` = c(1, 2) ) -glimpse(sim_dats_rfid) # Double the number of rows, looks good -# Overwrite the vector exp_rep with a new vector the same length as irbb_ts -exp_rep <- rep(x = "Pair_01", times = length(irbb_ts)) -sim_dats_irbb <- data.frame(replicate = exp_rep, timestamps = irbb_ts) -sim_dats_irbb <- sim_dats_irbb %>% -dplyr::mutate( -year = 2023, -month = 08, -day = 01, -sensor_id = "Beam breakers" +days +# Finally, you need to make a list of the data frames that you want to write out. Since you already know how to filter data frames by day, that code can go inside of the loop, and you only need to specify a data frame per sensor type here +dats <- list( +`RFID` = sim_dats_rfid, +`IRBB` = sim_dats_irbb ) -glimpse(sim_dats_irbb) -sim_dats_irbb <- sim_dats_irbb %>% -bind_rows( -sim_dats_irbb %>% -dplyr::mutate( -day = 02 +glimpse(dats) +# 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 <- sensors[x] +# For each sensor, iterate over days +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 and single bracket filtering to pull out the right file name +write.csv(file = files[[x]][y], row.names = FALSE) +}) +})) +# Testing +x <- 2 +y <- 1 +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 +# 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] +# 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 <- sensors[x] +# 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 and single bracket filtering to pull out the right file name +write.csv(file = files[[x]][y], row.names = FALSE) +dats[[x]] %>% +# Filter the data frame by one day at a time +dplyr::filter(day == days_tmp[y]) +# 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 <- sensors[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]]] +# 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 and single bracket filtering to pull out the right file name +write.csv(file = files[[x]][y], row.names = FALSE) +dats[[x]] %>% +# Filter the data frame by one day at a time +dplyr::filter(day == days_tmp[y]) +files[[x]][y] +file.path(path, "IRBB") +dir.create(file.path(path, "IRBB")) +# Testing +x <- 1 +y <- 1 +# 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 each sensor, iterate over days +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 and single bracket filtering to pull out the right file name +write.csv(file = files[[x]][y], row.names = FALSE) +}) +})) +# Make a vector of sensor labels +sensors <- c("RFID", "IRBB") +sensors +# Make a named list of the custom file names to write out +files <- list( +`RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv"), +`IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") ) +files +# Make a list of file paths per sensor that will be used inside of the loop +file_dirs <- list( +`RFID` = file.path(path, "RFID"), +`IRBB` = file.path(path, "IRBB") ) -glimpse(sim_dats_irbb) # Double the number of rows, looks good -# Two days, looks good -sim_dats_irbb %>% -pull(day) %>% -unique() -# 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() -# One day, looks good -sim_dats_rfid %>% -dplyr::filter(day == 1) %>% -pull(day) %>% -unique() -?write.csv -# For the RFID data, create a vector of the working directory and the file name that you will use for the argument file in write.csv() -# The function file.path() will combine both pieces of information into a single file path -rfid_file <- file.path(path, "RFID_simulated_Pair-01_2023_08_01.csv") -rfid_file -sim_dats_rfid %>% -dplyr::filter(day == 1) %>% -# Write out the 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 by day 1 -write.csv(x = ., file = rfid_file, row.names = FALSE) -rfid_file <- file.path(path, "RFID_simulated_Pair-01_2023_08_02.csv") -rfid_file -sim_dats_rfid %>% -dplyr::filter(day == 2) %>% -write.csv(x = ., file = rfid_file, row.names = FALSE) -irbb_file <- file.path(path, "IRBB_simulated_Pair-01_2023_08_01.csv") -irbb_file -sim_dats_irbb %>% -dplyr::filter(day == 1) %>% -# By default, write.csv() will write out the object that is piped in, so you don't need to specify "x = ." -write.csv(irbb_file, row.names = FALSE) -irbb_file <- file.path(path, "IRBB_simulated_Pair-01_2023_08_02.csv") -irbb_file -sim_dats_irbb %>% -dplyr::filter(day == 2) %>% -# By default, write.csv() will write out the object that is piped in, so you don't need to specify "x = ." -write.csv(irbb_file, row.names = FALSE) -list.files(path) -read.csv(file.path(path, "IRBB_simulated_Pair-01_2023_08_02.csv")) %>% -glimpse() -# 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") -rm(list = ls()) # Clean global environment -library(tidyverse) # Load the set of tidyverse packages -path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Initialize an object with the path that is your working directory -# Create 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) -# For the beam breakers, simulate timestamps for an entrance, an exit, and then another entrance and exit -irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") -# Simulate some RFID detection failures for the beam breaker data -irbb_ts <- c(irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") -# Simulate some stray beam breaker detections -irbb_ts <- c(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(irbb_ts) -# Make a vector for the experimental replicate -exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) -# Make the data frame with the experimental replicate metadata and the timestamps -sim_dats_rfid <- data.frame(replicate = 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 -) %>% -# Allocate the first half of the detections to the first individual using nrow(.)/2, and the second half of the detctions to the second individual using nrow(.)/2 again. Both of the rep() expressions are combined into a single vector using the c() function -dplyr::mutate( -PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2)) -) %>% -dplyr::mutate( -sensor_id = "RFID" +file_dirs +# Make a list of the days to write out for each sensor +# This could be a single vector rather than a list since you want to write out the same days per sensor, but a list is useful in case you wanted to change the days (and number of days) to write out per sensor +days <- list( +`RFID` = c(1, 2), +`IRBB` = c(1, 2) ) -glimpse(sim_dats_rfid) -sim_dats_rfid <- sim_dats_rfid %>% -bind_rows( -sim_dats_rfid %>% -dplyr::mutate( -day = 02 +days +# Finally, you need to make a list of the data frames that you want to write out. Since you already know how to filter data frames by day, that code can go inside of the loop, and you only need to specify a data frame per sensor type here +dats <- list( +`RFID` = sim_dats_rfid, +`IRBB` = sim_dats_irbb ) +glimpse(dats) +x <- 2 +y <- 1 +# 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]]] +days_tmp +# 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]]] +# 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 and single bracket filtering to pull out the right file name +write.csv(file = files[[x]][y], row.names = FALSE) +dats[[x]] +dats[[x]] %>% +# Filter the data frame by one day at a time +dplyr::filter(day == days_tmp[y]) %>% +dats[[x]] %>% +# Filter the data frame by one day at a time +dplyr::filter(day == days_tmp[y]) +files[[x]][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 and single bracket filtering to pull out the right file name +write.csv(file = file.path(file_dirs, files[[x]][y]), row.names = FALSE) +# 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 and single bracket filtering to pull out the right file name +write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) +# Testing +x <- 1 +y <- 1 +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 +# 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]) +files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv") +# Add the file path for the correct directory +files <- file.path(path, "RFID", files) +files +file.remove(files) +# Make a vector of sensor labels +sensors <- c("RFID", "IRBB") +sensors +# Make a named list of the custom file names to write out +files <- list( +`RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv"), +`IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") ) -glimpse(sim_dats_rfid) # Double the number of rows, looks good -# The name of a data frame object, followed by a $ sign and then the name of a column allows you to pull out one column at a time from a data frame. A data frame column is a vector, so when you run this code you will see a vector of values print to the console -sim_dats_rfid$day -# You can also access a column in a data frame by using double square brackets after the name of the data frame, and placing the column name in quotes inside of the inner brackets -sim_dats_rfid[["day"]] -# You can use the function unique() to see the unique values of any vector, including a column of a data frame -unique(sim_dats_rfid$day) # Two days, looks good -unique(sim_dats_rfid[["day"]]) # Two days, looks good -# Overwrite the vector exp_rep with a new vector the same length as irbb_ts -exp_rep <- rep(x = "Pair_01", times = length(irbb_ts)) -sim_dats_irbb <- data.frame(replicate = exp_rep, timestamps = irbb_ts) -sim_dats_irbb <- sim_dats_irbb %>% -dplyr::mutate( -year = 2023, -month = 08, -day = 01, -sensor_id = "Beam breakers" +files +# Make a list of file paths per sensor that will be used inside of the loop +file_dirs <- list( +`RFID` = file.path(path, "RFID"), +`IRBB` = file.path(path, "IRBB") ) -glimpse(sim_dats_irbb) -sim_dats_irbb <- sim_dats_irbb %>% -bind_rows( -sim_dats_irbb %>% -dplyr::mutate( -day = 02 +file_dirs +# Make a list of the days to write out for each sensor +# This could be a single vector rather than a list since you want to write out the same days per sensor, but a list is useful in case you wanted to change the days (and number of days) to write out per sensor +days <- list( +`RFID` = c(1, 2), +`IRBB` = c(1, 2) ) +days +# Finally, you need to make a list of the data frames that you want to write out. Since you already know how to filter data frames by day, that code can go inside of the loop, and you only need to specify a data frame per sensor type here +dats <- list( +`RFID` = sim_dats_rfid, +`IRBB` = sim_dats_irbb ) -glimpse(sim_dats_irbb) # Double the number of rows, looks good -# Two days, looks good -sim_dats_irbb %>% -pull(day) %>% -unique() -# 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() -# One day, looks good -sim_dats_rfid %>% -dplyr::filter(day == 1) %>% -pull(day) %>% -unique() -# 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") -rfid_file -sim_dats_rfid %>% -# Write out the data frame as a .csv spreadsheet. Do not include row names -#The "." below means the function will use the object that is piped in, which here is the data frame sim_dats_rfid -write.csv(x = ., file = rfid_file, row.names = FALSE) -# List all files in the given path -list.files(path) -# List only -list.files(path, pattern = ".csv$") -# List only -list.files(path, pattern = "test_file.csv$") -# List only files that end in the pattern ".csv" in the given path -list.files(path, pattern = ".csv$") -rfid_file <- file.path(path, "test_file.csv") -rfid_file -file.remove(rfid_file) -# Make the new file name with your working directory path -rfid_file <- file.path(path, "test.csv") -rfid_file -sim_dats_rfid %>% -dplyr::filter(day == 1) %>% -# Write out the 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 by day 1 -write.csv(x = ., file = rfid_file, row.names = FALSE) -list.files(path, pattern = ".csv$") -rfid_file <- file.path(path, "test_file.csv") -rfid_file -file.remove(rfid_file) -rfid_file <- file.path(path, "test.csv") -rfid_file -file.remove(rfid_file) -# Pipe the data frame into the filter() function -sim_dats_rfid %>% -# Filter the data frame by pulling out all rows in which the day column was equal to 1 -dplyr::filter(day == 1) %>% -glimpse() -# Check that the filtering was done correctly by getting the unique values in the day column. Looks good -sim_dats_rfid %>% -dplyr::filter(day == 1) %>% -pull(day) %>% -unique() -# 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 != 2) %>% -glimpse() -# Check that the filtering was done correctly by getting the unique values in the day column -sim_dats_rfid %>% -dplyr::filter(day != 2) %>% -pull(day) %>% -unique() # One day, looks good -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) %>% -glimpse() -# Check that the filtering was done correctly by getting the unique values in the day column. Looks good -sim_dats_rfid %>% -dplyr::filter(day != 2) %>% -pull(day) %>% -unique() +glimpse(dats) +# 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]]] +# 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 each sensor, iterate over days +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 and single bracket filtering to pull out the right file name +write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) +}) +})) +list.files(file.path(path, "RFID")) +list.files(file.path(path, "IRBB")) diff --git a/R/vignettes/Vignette_04_SaveData.Rmd b/R/vignettes/Vignette_04_SaveData.Rmd index 6c7bc88..fd497f3 100644 --- a/R/vignettes/Vignette_04_SaveData.Rmd +++ b/R/vignettes/Vignette_04_SaveData.Rmd @@ -1,5 +1,5 @@ --- -title: "Vignette 04: Process Data" +title: "Vignette 04: Save Data" author: "Grace Smith-Vidaurre" date: "2023-12-27" output: html_document @@ -69,10 +69,10 @@ In the code below, you'll use combine the vector of RFID timestamps that you mad ```{r} # Make a vector for the experimental replicate -exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) +exp_rep <- rep(x = "Nest_01", times = length(rfid_ts)) # Make the data frame with the experimental replicate metadata and the timestamps -sim_dats_rfid <- data.frame(replicate = exp_rep, timestamps = rfid_ts) +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 %>% @@ -148,9 +148,9 @@ Next, repeat this process of creating a data frame with metadata for the infrare ```{r} # Overwrite the vector exp_rep with a new vector the same length as irbb_ts -exp_rep <- rep(x = "Pair_01", times = length(irbb_ts)) +exp_rep <- rep(x = "Nest_01", times = length(irbb_ts)) -sim_dats_irbb <- data.frame(replicate = exp_rep, timestamps = irbb_ts) +sim_dats_irbb <- data.frame(chamber_id = exp_rep, timestamps = irbb_ts) sim_dats_irbb <- sim_dats_irbb %>% dplyr::mutate( @@ -283,13 +283,17 @@ sim_dats_rfid %>% Now that you've practiced filtering a data frame, you can combine this filtering step with writing out a .csv file that contains the filtered data frame with data collected on a single day. ```{r eval = FALSE} -# Make a new directory inside of your working directory for the raw RFID data +# 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, "RFID")) # Create 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, "RFID", "test.csv") +rfid_file <- file.path(path, "Data/RFID", "test.csv") rfid_file sim_dats_rfid %>% @@ -303,9 +307,9 @@ sim_dats_rfid %>% Check that the test file was created inside of the new RFID folder, and then delete it. ```{r eval = FALSE} -list.files(file.path(path, "RFID"), pattern = ".csv$") +list.files(file.path(path, "Data/RFID"), pattern = ".csv$") -rfid_file <- file.path(path, "RFID", "test.csv") +rfid_file <- file.path(path, "Data/RFID", "test.csv") rfid_file file.remove(rfid_file) @@ -322,7 +326,7 @@ 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, "RFID", "test1.csv"), file.path(path, "RFID", "test2.csv")) +files <- c(file.path(path, "Data/RFID", "test1.csv"), file.path(path, "Data/RFID", "test2.csv")) files length(files) @@ -370,7 +374,7 @@ lapply(X = 1:length(files), FUN = function(x){ 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 .csv files were written out: ```{r} -list.files(file.path(path, "RFID")) +list.files(file.path(path, "Data/RFID")) ``` @@ -393,7 +397,7 @@ lapply(X = 1:length(files), FUN = function(x){ You can delete these files that you created for testing. In the code below, you're seeing another pattern searching example. The string that you passed to the argument `pattern` starts with the symbol `^`, which is a symbol that indicates you're searching for all files that *start* with the pattern "test". You're also telling the function to return the full location of each file along with the file name, so that `file.remove()` knows exactly where to look for each file. ```{r eval = FALSE} -rem_files <- list.files(file.path(path, "RFID"), pattern = "^test", full.names = TRUE) +rem_files <- list.files(file.path(path, "Data/RFID"), pattern = "^test", full.names = TRUE) rem_files file.remove(rem_files) @@ -409,7 +413,7 @@ Now you can put all of these pieces together and use the loop to write out a spr files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv") # Add the file path for the correct directory -files <- file.path(path, "RFID", files) +files <- file.path(path, "Data/RFID", files) files # Make a vector of the days to write out (1 day per iteration of the loop) @@ -433,7 +437,7 @@ In the code above, you'll see one additional change that we made to the loop by ```{r eval = FALSE} # Both new .csv files for each day of RFID data are present, looks good -list.files(file.path(path, "RFID")) +list.files(file.path(path, "Data/RFID")) ``` @@ -443,7 +447,7 @@ You can remove these files. files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv") # Add the file path for the correct directory -files <- file.path(path, "RFID", files) +files <- file.path(path, "Data/RFID", files) files file.remove(files) @@ -457,7 +461,7 @@ You can continue practicing loops by repeating this process for the infrared bea If you want to cut down on the amount of code that you're writing, you could write out both the RFID and beam breaker data in the same loop. First you'll need to create another directory for the beam breaker data: ```{r eval = FALSE} -dir.create(file.path(path, "IRBB")) +dir.create(file.path(path, "Data", "IRBB")) ``` @@ -533,8 +537,8 @@ files # Make a list of file paths per sensor that will be used inside of the loop file_dirs <- list( - `RFID` = file.path(path, "RFID"), - `IRBB` = file.path(path, "IRBB") + `RFID` = file.path(path, "Data/RFID"), + `IRBB` = file.path(path, "Data/IRBB") ) file_dirs @@ -636,9 +640,9 @@ invisible(lapply(1:length(sensors), function(x){ This loop should have created 1 file per sensor and day in the correct directory per sensor. Check that these 4 files now exist inside each directory per sensor within your working directory. ```{r eval = FALSE} -list.files(file.path(path, "RFID")) +list.files(file.path(path, "Data/RFID")) -list.files(file.path(path, "IRBB")) +list.files(file.path(path, "Data/IRBB")) ``` diff --git a/R/vignettes/Vignette_05_ProcessData.Rmd b/R/vignettes/Vignette_05_ProcessData.Rmd index fdd3c63..8a8ec46 100644 --- a/R/vignettes/Vignette_05_ProcessData.Rmd +++ b/R/vignettes/Vignette_05_ProcessData.Rmd @@ -1,5 +1,5 @@ --- -title: "Vignette 04: Process Data" +title: "Vignette 05: Process Data" author: "Grace Smith-Vidaurre" date: "2023-12-27" output: html_document @@ -15,8 +15,9 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE) In this fourth vignette, you will write out spreadsheets of simulated detections of animal movements to your computer. You'll begin using this data in the ABISSMAL data processing and analysis workflow, including combining raw data across days and pre-processing the raw data. You will continue to use coding skills that you learned in the previous vignettes, and you will learn additional skills that include: -1. Sourcing custom functions -2. Using custom functions in an analytical pipeline +1. Accessing custom functions +2. Using custom functions +3. Plotting data with ggplot

Load packages and your working directory path

@@ -25,8 +26,11 @@ In this fourth vignette, you will write out spreadsheets of simulated 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 +library(ggplot2) # Load a package for making plots -path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Initialize an object with the path that is your working directory +# Initialize an object with the path that is your working directory +path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" ``` @@ -57,12 +61,148 @@ To get more information about each of these three main functions, you can click

Combine raw data

-Once you've loaded the ABISSMAL functions, you can start using the first function, `combine_raw_data()`, to combine data collected across days per sensor into a single spreadsheet per sensor. +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. -TKTK Need a new directory per sensor... +Here goes more information about the arguments you are supplying to the function below: + +* `sensors` is a vector containing the labels of the sensors for which you want to combine raw data. Below you're specifying RFID as a single sensor + +* `path` is your general working directory + +* `data_dir` is the folder that holds data inside of your working directory + +* `out_dir` is the folder where you want to save the combined raw data spreadsheet. The function will create this folder if it does not already exist + +* `tz` is the timezone for converting timestamps to POSIXct format. The default is "America/New York", and you can check out the "Time zones" section in the documentation for DateTimeClasses in R for more information (`?DateTimeClasses`) + +* `POSIXct_format` is a string containing the POSIX formatting information for how dates and timestamps should be combined into a single column. The default is to encode the year as a 4 digit number and the month and day as 2 digit numbers, separated by dashes. The date is followed by a space, then the 2-digit hour, minute, and decimal second (separated by colons) +```{r} + +combine_raw_data(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 single spreadsheet of raw RFID data to the new directory `raw_combined`: +```{r} + +list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") + +``` + +You can read this combined .csv file (called "combined_raw_data_RFID.csv") back into R to check out the structure of this spreadsheet: +```{r} + +rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) + +glimpse(rfid_data) + +``` + +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 hace a unique identifier in `sensor_id`). + +The function created a new timestamps column in POSIXct format for downsteam processing and analysis, but it kept the original timestamps column. The function also 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 not removed 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`: +```{r} + +combine_raw_data(sensors = c("RFID", "IRBB"), path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +``` + +The RFID data will be overwritten, and you should see an additional spreadsheet with the raw IRBB data: +```{r} + +list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") + +``` + +

Detect perching events

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

Pre-process raw data

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

Plot raw and pre-processed data

+ +Now that you've combined the raw data per sensor, detected perching events in the raw data, and pre-processed the raw data, it's time to visualize these different datasets. Making visualizations in R is important for generating high-quality figures for publications and presentations, but it's also important for checking your work as you process and analyze data. + +In the code below, you'll learn how to use functions from the `ggplot2` package to make barcode style visualizations of the raw and processed detection datasets. +```{r} + + + +``` + + From 0704a35f275df97825963527b853085fa3137cab Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Wed, 3 Jan 2024 17:36:33 -0500 Subject: [PATCH 16/69] [PCT-472]: Working on plotting code in the fifth vignette --- R/vignettes/.Rhistory | 660 ++++++++++++------------ R/vignettes/Vignette_05_ProcessData.Rmd | 361 ++++++++++++- 2 files changed, 689 insertions(+), 332 deletions(-) diff --git a/R/vignettes/.Rhistory b/R/vignettes/.Rhistory index 00572fd..6825e7b 100644 --- a/R/vignettes/.Rhistory +++ b/R/vignettes/.Rhistory @@ -1,175 +1,47 @@ -files -length(files) -# The argument X is an iterating variable, or 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. The loop will run twice, and each value in X will be used per iteration of the loop 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 of the function() call is the iterating variable, or the variable that will take on a different value in each iteration of the loop -lapply(X = 1:length(files), FUN = function(x){ -x -}) -# 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 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 -}) -x -# 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 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(mmmm){ -mmm -}) -# 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 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(m){ -m -}) -# 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 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(pm){ -pm -}) -# 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 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(pmm){ -pmm -}) -# 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 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(mmm){ -mmm -}) -?function -class(function) -function() -?make.names -# 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 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(pm2){ -pm2 -}) -# 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 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 -}) -# 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 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){ -x_x -}) -# 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 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(2x){ -lapply(X = 1:length(files), FUN = function(x){ -files[x] -}) -lapply(X = 1:length(files), FUN = function(x){ -sim_dats_rfid %>% -write.csv(file = files[x], row.names = FALSE) -}) -list.files(file.path(path, "RFID")) -# Pull out one day at a time -days[x] -days <- c(1, 2) -lapply(X = 1:length(files), FUN = function(x){ -sim_dats_rfid %>% +# Use double and single bracket filtering to pull out the right file name +write.csv(file = files[[x]][y], row.names = FALSE) +dats[[x]] %>% # Filter the data frame by one day at a time -dplyr::filter(day == days[x]) -write.csv(file = files[x], row.names = FALSE) -}) -sim_dats_rfid %>% +dplyr::filter(day == days_tmp[y]) +# 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 <- sensors[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]]] +# 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[x]) %>% -write.csv(file = files[x], row.names = FALSE) -days <- c(1, 2) -lapply(X = 1:length(files), FUN = function(x){ -sim_dats_rfid %>% +dplyr::filter(day == days_tmp[y]) %>% +# Use double and single bracket filtering to pull out the right file name +write.csv(file = files[[x]][y], row.names = FALSE) +dats[[x]] %>% # Filter the data frame by one day at a time -dplyr::filter(day == days[x]) %>% -write.csv(file = files[x], row.names = FALSE) -}) -# Make a vector of the files to write out -files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv") -files -length(files) -files <- file.path(path, "RFID", files) -files -# 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") -# Add the file path for the correct directory -files <- file.path(path, "RFID", files) -files -list.files(file.path(path, "RFID")) -rem_files <- list.files(file.path(path, "RFID"), pattern = "^test") -rem_files <- list.files(file.path(path, "RFID"), pattern = "^test") -rem_files -rem_files <- list.files(file.path(path, "RFID"), pattern = "^test") -rem_files -rem_files <- list.files(file.path(path, "RFID"), pattern = "^test", full.names = TRUE) -rem_files -rem_files <- list.files(file.path(path, "RFID"), pattern = "^test", full.names = TRUE) -rem_files -file.remove(rem_files) -# 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") -# Add the file path for the correct directory -files <- file.path(path, "RFID", files) -files -# Make a vector of the days to write out (1 day per iteration of the loop) -days <- c(1, 2) -days -# You can drop the lapply() argument names, since you're supplying the arguments in the order that the function expects -lapply(1:length(files), function(x){ -sim_dats_rfid %>% +dplyr::filter(day == days_tmp[y]) +files[[x]][y] +file.path(path, "IRBB") +dir.create(file.path(path, "IRBB")) +# Testing +x <- 1 +y <- 1 +# 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 each sensor, iterate over days +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[x]) %>% -write.csv(file = files[x], row.names = FALSE) +dplyr::filter(day == days_tmp[y]) %>% +# Use double and single bracket filtering to pull out the right file name +write.csv(file = files[[x]][y], row.names = FALSE) }) -# 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.csv(file = files[x], row.names = FALSE) })) -list.files(file.path(path, "RFID")) -# Make a vector 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"), -`IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") -) -glimpse(files) -# Make a vector 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"), -`IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") -) -glimpse(files) -# Make a list of the custom file names to write out -files <- list( -c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv"), -c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") -) -glimpse(files) -files[1] -# Using a single square bracket to filter a list returns the first list element in list format -files[1] -glimpse(files[1]) -# Using double square brackets returns the first list element only, so it removes the list structure and shows the original data structure of that element (here a vector) -files[[1]] -glimpse(files[[1]]) -# 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"), -`IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") -) -glimpse(files) -files$RFID -files$RFID -files[["RFID"]] -files["RFID"] -# 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"), -`IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") -) -# Add the file path for the correct directory -files <- file.path(path, "RFID", files) -files -# Make a list of file paths per sensor that will be used inside of the loop -file_dirs <- list( -`RFID` = file.path(path, "RFID"), -`IRBB` = file.path(path, "IRBB") -) -file_dirs +# Make a vector of sensor labels +sensors <- c("RFID", "IRBB") +sensors # Make a named list of the custom file names to write out files <- list( `RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv"), @@ -189,58 +61,47 @@ days <- list( `IRBB` = c(1, 2) ) days -# Make a vector of sensors -sensors <- c("RFID", "IRBB") -sensors # 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, and you only need to specify a data frame per sensor type here dats <- list( `RFID` = sim_dats_rfid, `IRBB` = sim_dats_irbb ) glimpse(dats) -# Testing -x <- 1 -y <- 1 -# Testing -x <- 1 -y <- 1 -# Testing -x <- 1 +x <- 2 y <- 1 # 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 <- sensors[x] -days_tmp -# 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 <- sensors[x] -days_tmp -# 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 <- sensors[x] -days_tmp -# 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_tmp[x] -days_tmp -# 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_tmp[x] -days_tmp -# 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[x] -days_tmp days_tmp <- days[[sensors[x]]] days_tmp -# Index the named list of days to get the right days per sensor +# Index the list of days to get the right days per sensor # This indexing is important to set up the next loop correctly -sensors[x] days_tmp <- days[[sensors[x]]] -days_tmp -# 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]] +# 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 and single bracket filtering to pull out the right file name +write.csv(file = files[[x]][y], row.names = FALSE) +dats[[x]] +dats[[x]] %>% +# Filter the data frame by one day at a time +dplyr::filter(day == days_tmp[y]) %>% +dats[[x]] %>% +# Filter the data frame by one day at a time +dplyr::filter(day == days_tmp[y]) files[[x]][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 and single bracket filtering to pull out the right file name +write.csv(file = file.path(file_dirs, files[[x]][y]), row.names = FALSE) +# 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 and single bracket filtering to pull out the right file name +write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) # Testing x <- 1 y <- 1 @@ -256,21 +117,13 @@ 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] -# 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 <- sensors[x] -# For each sensor, iterate over days -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 and single bracket filtering to pull out the right file name -write.csv(file = files[[x]][y], row.names = FALSE) -}) -})) +# You'll also combine the file name with the right path: +file.path(file_dirs[[x]], files[[x]][y]) +files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv") +# Add the file path for the correct directory +files <- file.path(path, "RFID", files) +files +file.remove(files) # Make a vector of sensor labels sensors <- c("RFID", "IRBB") sensors @@ -299,69 +152,9 @@ dats <- list( `IRBB` = sim_dats_irbb ) glimpse(dats) -# 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 <- sensors[x] -# For each sensor, iterate over days -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 and single bracket filtering to pull out the right file name -write.csv(file = files[[x]][y], row.names = FALSE) -}) -})) -# Testing -x <- 2 -y <- 1 -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 -# 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] -# 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 <- sensors[x] -# 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 and single bracket filtering to pull out the right file name -write.csv(file = files[[x]][y], row.names = FALSE) -dats[[x]] %>% -# Filter the data frame by one day at a time -dplyr::filter(day == days_tmp[y]) -# 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 <- sensors[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]]] -# 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 and single bracket filtering to pull out the right file name -write.csv(file = files[[x]][y], row.names = FALSE) -dats[[x]] %>% -# Filter the data frame by one day at a time -dplyr::filter(day == days_tmp[y]) -files[[x]][y] -file.path(path, "IRBB") -dir.create(file.path(path, "IRBB")) -# Testing -x <- 1 -y <- 1 # Start by iterating over sensors invisible(lapply(1:length(sensors), function(x){ # Index the list of days to get the right days per sensor @@ -374,9 +167,105 @@ dats[[x]] %>% # Filter the data frame by one day at a time dplyr::filter(day == days_tmp[y]) %>% # Use double and single bracket filtering to pull out the right file name -write.csv(file = files[[x]][y], row.names = FALSE) +write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) }) })) +list.files(file.path(path, "RFID")) +list.files(file.path(path, "IRBB")) +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 +# 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") +path +View(combine_raw_data) +# Make a new directory inside of your working directory for saving data +file.path(path, "Data") +# 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 +file.path(path, "Data", "IRBB") +dir.create(file.path(path, "Data", "IRBB")) +rm(list = ls()) # Clean global environment +library(tidyverse) # Load the set of tidyverse packages +path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Initialize an object with the path that is your working directory +# Create 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) +# For the beam breakers, simulate timestamps for an entrance, an exit, and then another entrance and exit +irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") +# Simulate some RFID detection failures for the beam breaker data +irbb_ts <- c(irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") +# Simulate some stray beam breaker detections +irbb_ts <- c(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(irbb_ts) +# Make a vector for the experimental replicate +exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) +# Make the data frame with the experimental replicate metadata and the timestamps +sim_dats_rfid <- data.frame(replicate = 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 +) %>% +# Allocate the first half of the detections to the first individual using nrow(.)/2, and the second half of the detctions to the second individual using nrow(.)/2 again. Both of the rep() expressions are combined into a single vector using the c() function +dplyr::mutate( +PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2)) +) %>% +dplyr::mutate( +sensor_id = "RFID" +) +glimpse(sim_dats_rfid) +sim_dats_rfid <- sim_dats_rfid %>% +bind_rows( +sim_dats_rfid %>% +dplyr::mutate( +day = 02 +) +) +glimpse(sim_dats_rfid) # Double the number of rows, looks good +# Overwrite the vector exp_rep with a new vector the same length as irbb_ts +exp_rep <- rep(x = "Pair_01", times = length(irbb_ts)) +sim_dats_irbb <- data.frame(replicate = exp_rep, timestamps = irbb_ts) +sim_dats_irbb <- sim_dats_irbb %>% +dplyr::mutate( +year = 2023, +month = 08, +day = 01, +sensor_id = "Beam breakers" +) +glimpse(sim_dats_irbb) +sim_dats_irbb <- sim_dats_irbb %>% +bind_rows( +sim_dats_irbb %>% +dplyr::mutate( +day = 02 +) +) +glimpse(sim_dats_irbb) # Double the number of rows, looks good +# Two days, looks good +sim_dats_irbb %>% +pull(day) %>% +unique() +# 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") +rfid_file # Make a vector of sensor labels sensors <- c("RFID", "IRBB") sensors @@ -405,63 +294,130 @@ dats <- list( `IRBB` = sim_dats_irbb ) glimpse(dats) -x <- 2 -y <- 1 -# 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]]] -days_tmp +# 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 each sensor, iterate over days +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 and single bracket filtering to pull out the right file name -write.csv(file = files[[x]][y], row.names = FALSE) -dats[[x]] -dats[[x]] %>% -# Filter the data frame by one day at a time -dplyr::filter(day == days_tmp[y]) %>% -dats[[x]] %>% -# Filter the data frame by one day at a time -dplyr::filter(day == days_tmp[y]) -files[[x]][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 and single bracket filtering to pull out the right file name -write.csv(file = file.path(file_dirs, files[[x]][y]), row.names = FALSE) +write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) +}) +})) +# 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 +# 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 each sensor, iterate over days +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 and single bracket filtering to pull out the right file name write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) -# Testing -x <- 1 -y <- 1 -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 -# 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]) -files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv") -# Add the file path for the correct directory -files <- file.path(path, "RFID", files) -files -file.remove(files) +}) +})) +?POSIXct +? +DateTimeClasses +?DateTimeClasses +list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") +combine_raw_data(sensors = "RFID", path = path, data_dir = "Data", out_dir = "raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") +# 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") +combine_raw_data(sensors = "RFID", path = path, data_dir = "Data", out_dir = "raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") +library(data.table) # Load other packages that the ABISSMAL functions require +combine_raw_data(sensors = "RFID", path = path, data_dir = "Data", out_dir = "raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") +rm(list = ls()) # Clean global environment +library(tidyverse) # Load the set of tidyverse packages +path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Initialize an object with the path that is your working directory +# Create 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) +# For the beam breakers, simulate timestamps for an entrance, an exit, and then another entrance and exit +irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") +# Simulate some RFID detection failures for the beam breaker data +irbb_ts <- c(irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") +# Simulate some stray beam breaker detections +irbb_ts <- c(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(irbb_ts) +# Make a vector for the experimental replicate +exp_rep <- rep(x = "Nest_01", times = length(rfid_ts)) +# 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 +) %>% +# Allocate the first half of the detections to the first individual using nrow(.)/2, and the second half of the detctions to the second individual using nrow(.)/2 again. Both of the rep() expressions are combined into a single vector using the c() function +dplyr::mutate( +PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2)) +) %>% +dplyr::mutate( +sensor_id = "RFID" +) +glimpse(sim_dats_rfid) +sim_dats_rfid <- sim_dats_rfid %>% +bind_rows( +sim_dats_rfid %>% +dplyr::mutate( +day = 02 +) +) +glimpse(sim_dats_rfid) # Double the number of rows, looks good +# Overwrite the vector exp_rep with a new vector the same length as irbb_ts +exp_rep <- rep(x = "Nest_01", times = length(irbb_ts)) +sim_dats_irbb <- data.frame(chamber_id = exp_rep, timestamps = irbb_ts) +sim_dats_irbb <- sim_dats_irbb %>% +dplyr::mutate( +year = 2023, +month = 08, +day = 01, +sensor_id = "Beam breakers" +) +glimpse(sim_dats_irbb) +sim_dats_irbb <- sim_dats_irbb %>% +bind_rows( +sim_dats_irbb %>% +dplyr::mutate( +day = 02 +) +) +glimpse(sim_dats_irbb) # Double the number of rows, looks good +# Two days, looks good +sim_dats_irbb %>% +pull(day) %>% +unique() +# 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 +dir.create(file.path(path, "Data", "IRBB")) # Make a vector of sensor labels sensors <- c("RFID", "IRBB") sensors @@ -473,8 +429,8 @@ files <- list( files # Make a list of file paths per sensor that will be used inside of the loop file_dirs <- list( -`RFID` = file.path(path, "RFID"), -`IRBB` = file.path(path, "IRBB") +`RFID` = file.path(path, "Data/RFID"), +`IRBB` = file.path(path, "Data/IRBB") ) file_dirs # Make a list of the days to write out for each sensor @@ -490,9 +446,6 @@ dats <- list( `IRBB` = sim_dats_irbb ) glimpse(dats) -# 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]]] # Start by iterating over sensors invisible(lapply(1:length(sensors), function(x){ # Index the list of days to get the right days per sensor @@ -508,5 +461,52 @@ dplyr::filter(day == days_tmp[y]) %>% write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) }) })) -list.files(file.path(path, "RFID")) -list.files(file.path(path, "IRBB")) +list.files(file.path(path, "Data/RFID")) +list.files(file.path(path, "Data/IRBB")) +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 +path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Initialize an object with the path that is your working directory +# 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") +combine_raw_data(sensors = "RFID", path = path, data_dir = "Data", out_dir = "raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") +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") +list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") +file.path(path, "Data/raw_combined") +list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") +rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) +glimpse(rfid_data) +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") +list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") +detect_perching_events(file_nm = "combined_raw_data_RFID.csv", threshold = 2, run_length = 2, PIT_tag_col_nm = "PIT_tag", rfid_label = "RFID", path = data_path, 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(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", path = data_path, 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(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 = data_path, 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(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 = path, 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(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") +perching <- read.csv(file.path(path, "Data", "processed", "perching_events_RFID.csv")) +glimpse(perching) +perching$perching_start +perching$start +# The timestamps when each perching event started +perching$perching_start +perching$PIT_tag +View(preprocess_detections) +preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = path, data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") +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") +list.files(file.path(path, "Data/processed")) +rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) +glimpse(rfid_pp) +rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) +glimpse(rfid_data) +preprocess_detections(sensor = "IRBB", timestamps_col_nm = "timestamp_ms", group_col_nm = NULL, 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") +preprocess_detections(sensor = "IRBB", timestamps_col_nm = "timestamp_ms", group_col_nm = "sensor_id", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") +list.files(file.path(path, "Data/processed")) +irbb_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_IRBB.csv")) +glimpse(rfid_pp) +library(ggplot2) # Load a package for making plots diff --git a/R/vignettes/Vignette_05_ProcessData.Rmd b/R/vignettes/Vignette_05_ProcessData.Rmd index 8a8ec46..9c0f412 100644 --- a/R/vignettes/Vignette_05_ProcessData.Rmd +++ b/R/vignettes/Vignette_05_ProcessData.Rmd @@ -27,7 +27,6 @@ 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 -library(ggplot2) # Load a package for making plots # Initialize an object with the path that is your working directory path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" @@ -190,7 +189,7 @@ list.files(file.path(path, "Data/processed")) irbb_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_IRBB.csv")) -glimpse(rfid_pp) +glimpse(irbb_pp) ``` @@ -199,8 +198,366 @@ glimpse(rfid_pp) Now that you've combined the raw data per sensor, detected perching events in the raw data, and pre-processed the raw data, it's time to visualize these different datasets. Making visualizations in R is important for generating high-quality figures for publications and presentations, but it's also important for checking your work as you process and analyze data. In the code below, you'll learn how to use functions from the `ggplot2` package to make barcode style visualizations of the raw and processed detection datasets. + +Start by reading in the raw data for each sensor, and convert the timestamps to POSIX format. +```{r} + +rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) %>% + # The timestamps must be converted to POSIX format every time that the data is read back into R for plotting + dplyr::mutate( + timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) + ) + +# You should see that the timestamp_ms is in "dttm" format, which means that the POSIX conversion was done correctly +glimpse(rfid_raw) + +irbb_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_IRBB.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")) + ) + +glimpse(irbb_raw) + +``` + +Next, start plotting this raw data using functions from `ggplot2`. This package is part of the `tidyverse`, but can also be installed and used separately. You can check out this [link](https://ggplot2.tidyverse.org/) for more resources to learn how to use ggplot 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. + +Thde `ggplot2` package has a unique syntax for building plots, in which you start making a plot by calling the function `ggplot()`, and then add features by layering on other `ggplot2` functions with the `+` symbol. + +If you call `ggplot()`, you'll see that the function immediately draws a blank plot in your Plots pane in RStudio. +```{r} + +ggplot() + +``` + +The plot will still remain blank even if you supply information about your data in order to set up plot aesthetics. +```{r} + +ggplot(data = rfid_raw) + +``` + +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 (see a plot in the next vignette). + +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 x-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, information on the x-axis only). +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + geom_segment( + data = rfid_raw, + aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1), + color = "blue", + linewidth = 0.3 + ) + +``` + +In the code above, you used the argument `data` inside of `geom_segment()` to tell the function where to get data for drawing the line segments. + +In the argument `aes()`, which is a standard `ggplot2` function for encoding plot aesthetics, you specified that the data to plot on the x-axis are the timestamps for each detection, by providing the column name `timestamp_ms` to the inner argument `x`. You also specified what data should be plotted on the y-axis using the argument `y`. In this case, you can set `y` to any value that you want, since you aren't plotting data along this axis. + +The arguments `x` and `y` determine where the beginning of each line 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 `timestamp_ms` to the argument `xend`, you're indicating that you want the line segment to start and end on the same timestamp. On the y-axis, the number that you supply to `yend` will determine the height of each line. Since you're adding data from a single sensor here, the height of the line does not matter much. If you changed the code to read `yend = 0.1`, the plot would look very similar, but the y-axis would be scaled to have 0.1 as the maximum value instead of 1. + +The last two arguments inside of `geom_segment()` control the color of the line segments (`color`), as well as the width of ech line (`linewidth`). Specifying the width of each line is different than specifying where each line segment starts and ends. + +You can continue adding information to this plot by layering on the raw beam breaker detections: +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + geom_segment( + data = rfid_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 1), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + geom_segment( + data = irbb_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 1), + color = "orange", + linewidth = 0.3 + ) + +``` + +This plot has more information, but it's difficult to interpret. Some of the detections for each sensor type occurred so close in time that they're layered nearly directly over one another. You can change where the line segements per sensor start and end on the y-axis in order to have better temporal resolution for visualizng the datasets from both sensors. + +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + geom_segment( + data = rfid_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + geom_segment( + data = irbb_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + +``` + +This change to the start and end coordinates of how segments are drawn on the y-axis offset the two sets of line segments per sensor dataset. The plot is now less crowded and it's easier to visualize patterns over time and between datasets now. + +You can make some minor adjustments to the plot to make it easier to interpret, including changing the axis titles to be more informative, changing the background to be white, and removing the y-axis text and axis ticks (since the height of each segment does not reflect data that you want to interpret). +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + geom_segment( + data = rfid_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + geom_segment( + data = irbb_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Change the x and y axis labels + xlab("Date and time") + + + 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 + theme( + axis.text.y = element_blank(), + axis.ticks.y = element_blank() + ) + +``` + +The plot is looking a lot better, but there is at least one more aesthetic that you can change to make easier to interpret. The x-axis currently holds two types of information: 1) the day of data collection, and 2) the time of day. It's difficult to see when detections were recorded on each day, and whether detections were recorded over similar or different time periods between days. To make the x-axis more interpretable, you can change the plot to be "faceted", so that you have 1 panel per day of data collection. +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + geom_segment( + data = rfid_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + geom_segment( + data = irbb_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Facet the plot by day (e.g. create a panel per day) + facet_wrap(~ day, nrow = 2, strip.position = "left") + + + # Change the x and y axis labels + xlab("Time") + + + # Now you can add a y-axis label + ylab("Day of data collection") + + + # 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 + theme( + axis.text.y = element_blank(), + axis.ticks.y = element_blank() + ) + +``` + +The overall plot structure has improved a lot, but there are still some modifications that would help interpretability. 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. label each hour). + +You can start by modifying the data frame of raw data for each senor 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 2 days of data collection that need recoding. + +First you can practice using `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 day is the first day of the month), the value you want to add to the vector if the condition is true ("Day 1" of data collection), and then the value that you want to add if the condition is false ("Day 2" of data collection, since there are only 2 dates in each dataset). +```{r} + +ifelse(test = rfid_raw$day == 1, yes = "Day 1", no = "Day 2") + +``` + +Now you can create a new column per data frame using an `ifelse` statement. You will also update the format of the timetamps column so that the timestamps include the time only (not the month and day). The code to convert the timestamps to a different format is nested and repetitive, but the timestamp conversion will be performed correctly. + +When you 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). TKTK this change needs to come much earlier... +```{r} + +# Create a new column in the raw data for the date of data collection +rfid_raw2 <- rfid_raw %>% + dplyr::mutate( + day_label = ifelse(day == 1, "Day 1", "Day 2") + ) %>% + dplyr::mutate( + timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%H:%M:%S"), format = "%H:%M:%S")) + ) + +# You should see that the new column "day_label" was created +# You'll see that a new year, month, and day were appended to the updated timestamps, but this is expected (see above) +glimpse(rfid_raw2) + +# Repeat this process for the beam breaker data +irbb_raw2 <- irbb_raw %>% + dplyr::mutate( + day_label = ifelse(day == 1, "Day 1", "Day 2") + ) %>% + dplyr::mutate( + timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%H:%M:%S"), format = "%H:%M:%S")) + ) + +glimpse(irbb_raw2) + +``` + +Now you can update the code to include the new date labels, and you can also change the aesthetics of the x-axis using the function `scale_x_datetime()`: +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = rfid_raw2, + aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = irbb_raw2, + aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 2, strip.position = "left") + + + # Change the x and y axis labels + xlab("Time") + + + # Now you can add a y-axis label + ylab("Day of data collection") + + + # Change the aesthetics of the x-axis labels + scale_x_datetime( + # date_breaks = "1 hour", + # breaks = seq(x_lims[1], x_lims[2], "1 hour"), + date_labels = "%H:%M", + expand = c(0, 0) + # limits = x_lims + ) + + + # 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 + theme( + axis.text.y = element_blank(), + axis.ticks.y = element_blank() + ) + +``` + + + + +Then...TKTK add a legend, write out a plot as an image file. More modification to get font size right + ```{r} + ggplot() + + + # Add a vertical line for each RFID timestamp + geom_segment( + data = raw_rfid, + aes(x = timestamp, y = 0, xend = timestamp, yend = y_max/4), + color = cols[4], + linewidth = 0.3 + ) + + + geom_segment( + data = raw_all2 %>% + dplyr::filter(sensor_id == "Inner Beam Breaker"), + aes(x = timestamp, xend = timestamp, y = y_max/4, yend = (y_max/4) * 2), + color = cols[3], + linewidth = 0.3 + ) + + + geom_segment( + data = raw_all2 %>% + dplyr::filter(sensor_id == "RFID"), + aes(x = timestamp, xend = timestamp, y = (y_max/4) * 2, yend = (y_max/4) * 3), + color = cols[2], + linewidth = 0.3 + ) + + + geom_segment( + data = raw_all2 %>% + dplyr::filter(sensor_id == "Outer Beam Breaker"), + aes(x = timestamp, xend = timestamp, y = (y_max/4) * 3, yend = y_max), + color = cols[1], + linewidth = 0.3 + ) + + + # I removed switch = "y" because it's possible to rotate strip lables horizontally when they're on the right + facet_grid(rows = vars(new_date)) + + + # Add shaded rectangles for the nocturnal periods + geom_rect(data = rect_df2, aes(xmin = time_xmin, xmax = time_xmax, ymin = -Inf, ymax = Inf), color = alpha("black", 0), fill = alpha("black", 0.08), inherit.aes = FALSE) + + + scale_x_datetime( + breaks = seq(x_lims[1], x_lims[2], "1 hour"), + date_labels = "%H:%M", + expand = c(0, 0), + limits = x_lims + ) + + + scale_y_continuous(limits = c(0, y_max)) + + guides(color = "none") + + theme_bw() + + ylab("") + + xlab("") + + theme( + axis.ticks.y = element_blank(), + axis.text.y = element_blank(), + panel.grid.major = element_blank(), + panel.grid.minor = element_blank(), + axis.text.x = element_text(size = 7, angle = 90, vjust = 0.5, hjust = 0.5, color = "black"), + strip.text.y = element_text(size = 7, face = "bold", angle = 0), + strip.background = element_rect(fill = "white"), + legend.position = "top", + axis.ticks = element_line(linewidth = 0.25) + ) ``` From 45defaa105de0840ae78a7216e98862f4868bc42 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Wed, 3 Jan 2024 19:53:45 -0500 Subject: [PATCH 17/69] [PCT-472]: Updated plotting code, see notes for how to continue in fifth vignette --- R/vignettes/Vignette_05_ProcessData.Rmd | 226 ++++++++++++++++++------ 1 file changed, 168 insertions(+), 58 deletions(-) diff --git a/R/vignettes/Vignette_05_ProcessData.Rmd b/R/vignettes/Vignette_05_ProcessData.Rmd index 9c0f412..7116bb6 100644 --- a/R/vignettes/Vignette_05_ProcessData.Rmd +++ b/R/vignettes/Vignette_05_ProcessData.Rmd @@ -313,7 +313,7 @@ ggplot() + This change to the start and end coordinates of how segments are drawn on the y-axis offset the two sets of line segments per sensor dataset. The plot is now less crowded and it's easier to visualize patterns over time and between datasets now. -You can make some minor adjustments to the plot to make it easier to interpret, including changing the axis titles to be more informative, changing the background to be white, and removing the y-axis text and axis ticks (since the height of each segment does not reflect data that you want to interpret). +The plot is looking a lot better, but there's a problem with the x-axis. The x-axis currently holds two types of information: 1) the day of data collection, and 2) the time of day. It's difficult to see when detections were recorded on each day, and whether detections were recorded over similar or different time periods between days. To make the x-axis more interpretable, you can change the plot to be "faceted", so that you have 1 panel per day of data collection. ```{r} ggplot() + @@ -334,45 +334,164 @@ ggplot() + linewidth = 0.3 ) + - # Change the x and y axis labels - xlab("Date and time") + - - ylab("") + - - # Use this function to convert the plot background to black and white - theme_bw() + + # Facet the plot by day (e.g. create a panel per day) + facet_wrap(~ day, nrow = 2, strip.position = "left") + +``` + +The overall plot structure has improved a lot, but are still some modifications that would help interpretability. 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. label each hour). + +You can start by modifying the data frame of raw data for each senor 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 2 days of data collection that need recoding. + +First you can practice using `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 day is the first day of the month), the value you want to add to the vector if the condition is true ("Day 1" of data collection), and then the value that you want to add if the condition is false ("Day 2" of data collection, since there are only 2 dates in each dataset). +```{r} + +ifelse(test = rfid_raw$day == 1, yes = "Day 1", no = "Day 2") + +``` + +Now you can create a new column per data frame using an `ifelse` statement. +```{r} + +# Create a new column in the raw data for the date of data collection +rfid_raw2 <- rfid_raw %>% + dplyr::mutate( + day_label = ifelse(day == 1, "Day 1", "Day 2") + ) - # Use aesthetics functions to remove the y-axis labels and ticks - theme( - axis.text.y = element_blank(), - axis.ticks.y = element_blank() +# You should see that the new column "day_label" was created +# You'll see that a new year, month, and day were appended to the updated timestamps, but this is expected (see above) +glimpse(rfid_raw2) + +# Repeat this process for the beam breaker data +irbb_raw2 <- irbb_raw %>% + dplyr::mutate( + day_label = ifelse(day == 1, "Day 1", "Day 2") ) +glimpse(irbb_raw2) + ``` -The plot is looking a lot better, but there is at least one more aesthetic that you can change to make easier to interpret. The x-axis currently holds two types of information: 1) the day of data collection, and 2) the time of day. It's difficult to see when detections were recorded on each day, and whether detections were recorded over similar or different time periods between days. To make the x-axis more interpretable, you can change the plot to be "faceted", so that you have 1 panel per day of data collection. +Now you can update the code to include the new date labels: ```{r} ggplot() + # Add a vertical line for each RFID timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column geom_segment( - data = rfid_raw, + data = rfid_raw2, aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), color = "blue", linewidth = 0.3 ) + # Add a vertical line for each beam breaker timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column geom_segment( - data = irbb_raw, + data = irbb_raw2, aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), color = "orange", linewidth = 0.3 ) + # Facet the plot by day (e.g. create a panel per day) - facet_wrap(~ day, nrow = 2, strip.position = "left") + + # Use the new day labels here + facet_wrap(~ day_label, nrow = 2, 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 timestamps column. The code to convert the timestamps to a different format is nested and repetitive, but the timestamp conversion will be performed correctly. When you prompt the R to convert the timestamps to hours, minutes, and seconds only, R adds a default year, month, and day beforehand (likely the current date that you rant the code). This is expected, and it is not an error, but rather makes it possible for the timestamps to align correctly over days in the plot (since R sees all timestamps occurring on a single day). +```{r} + +rfid_raw3 <- rfid_raw2 %>% + dplyr::mutate( + timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%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(rfid_raw3) + +# Repeat this process for the beam breaker data +irbb_raw3 <- irbb_raw2 %>% + dplyr::mutate( + timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%H:%M:%S"), format = "%H:%M:%S")) + ) + +glimpse(irbb_raw3) + +``` + +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: +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = rfid_raw3, + aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = irbb_raw3, + aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 2, strip.position = "left") + + + # Change the aesthetics of the x-axis labels + scale_x_datetime( + date_breaks = "30 mins", + date_labels = "%H:%M" + ) + +``` + +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 (since the height of each segment does not reflect data that you want to interpret). +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = rfid_raw3, + aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = irbb_raw3, + aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 2, strip.position = "left") + # Change the x and y axis labels xlab("Time") + @@ -380,6 +499,12 @@ ggplot() + # Now you can add a y-axis label ylab("Day of data collection") + + # Change the aesthetics of the x-axis labels + scale_x_datetime( + date_breaks = "30 mins", + date_labels = "%H:%M" + ) + + # Use this function to convert the plot background to black and white theme_bw() + @@ -391,49 +516,24 @@ ggplot() + ``` -The overall plot structure has improved a lot, but there are still some modifications that would help interpretability. 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. label each hour). - -You can start by modifying the data frame of raw data for each senor 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 2 days of data collection that need recoding. +Finally, you need to add a legend to this plot so you can communicate what the two different colors mean. Adding a legend to this plot is tricky because you manually coded the colors of the line segments in two separate uses in `geom_segment()`. `ggplot2` functions have specific requirements for making a legend, and they can require automated coding of aesthetic values like color. -First you can practice using `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 day is the first day of the month), the value you want to add to the vector if the condition is true ("Day 1" of data collection), and then the value that you want to add if the condition is false ("Day 2" of data collection, since there are only 2 dates in each dataset). +In order to automatically create a legend in a plot, it helps to have a layer of the plot in which color is automatically encoded. The way that you would do this is to move the `color` argument inside of the function `aes()` for one layer of the plot, and then supply `color` with a column of data type `factor`...TKTK continue ```{r} -ifelse(test = rfid_raw$day == 1, yes = "Day 1", no = "Day 2") - -``` - -Now you can create a new column per data frame using an `ifelse` statement. You will also update the format of the timetamps column so that the timestamps include the time only (not the month and day). The code to convert the timestamps to a different format is nested and repetitive, but the timestamp conversion will be performed correctly. - -When you 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). TKTK this change needs to come much earlier... -```{r} - -# Create a new column in the raw data for the date of data collection -rfid_raw2 <- rfid_raw %>% - dplyr::mutate( - day_label = ifelse(day == 1, "Day 1", "Day 2") - ) %>% - dplyr::mutate( - timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%H:%M:%S"), format = "%H:%M:%S")) - ) - -# You should see that the new column "day_label" was created -# You'll see that a new year, month, and day were appended to the updated timestamps, but this is expected (see above) -glimpse(rfid_raw2) - -# Repeat this process for the beam breaker data -irbb_raw2 <- irbb_raw %>% - dplyr::mutate( - day_label = ifelse(day == 1, "Day 1", "Day 2") +all_sensors <- rfid_raw3 %>% + bind_rows( + irbb_raw3 ) %>% dplyr::mutate( - timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%H:%M:%S"), format = "%H:%M:%S")) + sensor_id = factor(sensor_id) ) -glimpse(irbb_raw2) +glimpse(all_sensors) ``` -Now you can update the code to include the new date labels, and you can also change the aesthetics of the x-axis using the function `scale_x_datetime()`: +Then make the plot...TKTK split up creating the legend a bit more (e.g. default colors first) ```{r} ggplot() + @@ -442,7 +542,7 @@ ggplot() + # Use the new version of this data frame with the new day_label column # Make sure to use the new timestamp column geom_segment( - data = rfid_raw2, + data = rfid_raw3, aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), color = "blue", linewidth = 0.3 @@ -452,12 +552,24 @@ ggplot() + # Use the new version of this data frame with the new day_label column # Make sure to use the new timestamp column geom_segment( - data = irbb_raw2, + data = irbb_raw3, aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), color = "orange", linewidth = 0.3 ) + + # Add the fake layer of lines so that you can create a legend + # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below + geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + + + # Set the colors that will be used to color the lines in the legend itself + # The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column + # If you do not supply your own color values, then R will assign default colors to each factor level + scale_color_manual(values = c("blue", "orange")) + + + # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding + guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + + # Facet the plot by day (e.g. create a panel per day) # Use the new day labels here facet_wrap(~ day_label, nrow = 2, strip.position = "left") + @@ -470,27 +582,25 @@ ggplot() + # Change the aesthetics of the x-axis labels scale_x_datetime( - # date_breaks = "1 hour", - # breaks = seq(x_lims[1], x_lims[2], "1 hour"), - date_labels = "%H:%M", - expand = c(0, 0) - # limits = x_lims + date_breaks = "30 mins", + date_labels = "%H:%M" ) + # 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() + axis.ticks.y = element_blank(), + legend.position = "top" ) ``` - Then...TKTK add a legend, write out a plot as an image file. More modification to get font size right ```{r} From 916056a607ba6b576330bd41f09dfff484d8f996 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Fri, 19 Jan 2024 15:42:25 -0500 Subject: [PATCH 18/69] Updated fifth vignette; continue with adding colors to plot --- R/vignettes/.Rhistory | 1000 +++++++++++------------ R/vignettes/Vignette_04_SaveData.Rmd | 2 +- R/vignettes/Vignette_05_ProcessData.Rmd | 157 ++-- 3 files changed, 608 insertions(+), 551 deletions(-) diff --git a/R/vignettes/.Rhistory b/R/vignettes/.Rhistory index 6825e7b..9458043 100644 --- a/R/vignettes/.Rhistory +++ b/R/vignettes/.Rhistory @@ -1,512 +1,512 @@ -# Use double and single bracket filtering to pull out the right file name -write.csv(file = files[[x]][y], row.names = FALSE) -dats[[x]] %>% -# Filter the data frame by one day at a time -dplyr::filter(day == days_tmp[y]) -# 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 <- sensors[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]]] -# 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 and single bracket filtering to pull out the right file name -write.csv(file = files[[x]][y], row.names = FALSE) -dats[[x]] %>% -# Filter the data frame by one day at a time -dplyr::filter(day == days_tmp[y]) -files[[x]][y] -file.path(path, "IRBB") -dir.create(file.path(path, "IRBB")) -# Testing -x <- 1 -y <- 1 -# 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 each sensor, iterate over days -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 and single bracket filtering to pull out the right file name -write.csv(file = files[[x]][y], row.names = FALSE) -}) -})) -# Make a vector of sensor labels -sensors <- c("RFID", "IRBB") -sensors -# Make a named list of the custom file names to write out -files <- list( -`RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv"), -`IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") +# Make sure to use the new timestamp column +geom_segment( +data = irbb_raw3, +aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), +color = "orange", +linewidth = 0.3 +) + +# Add the fake layer of points so that you can create a legend +geom_point(data = all_sensors, aes(timestamp, y = 1, color = sensor_id), size = 0) + +scale_color_manual(values = c("blue", "orange")) + +guides(color = guide_legend()) + +# Facet the plot by day (e.g. create a panel per day) +# Use the new day labels here +facet_wrap(~ day_label, nrow = 2, strip.position = "left") + +# Change the x and y axis labels +xlab("Time") + +# Now you can add a y-axis label +ylab("Day of data collection") + +# Change the aesthetics of the x-axis labels +scale_x_datetime( +date_breaks = "30 mins", +date_labels = "%H:%M", +limits = c() +) + +# 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 +theme( +axis.text.y = element_blank(), +axis.ticks.y = element_blank() ) -files -# Make a list of file paths per sensor that will be used inside of the loop -file_dirs <- list( -`RFID` = file.path(path, "RFID"), -`IRBB` = file.path(path, "IRBB") +ggplot() + +# Add a vertical line for each RFID timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = rfid_raw3, +aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), +color = "blue", +linewidth = 0.3 +) + +# Add a vertical line for each beam breaker timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = irbb_raw3, +aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), +color = "orange", +linewidth = 0.3 +) + +# Add the fake layer of points so that you can create a legend +geom_point(data = all_sensors, aes(timestamp, y = 1, color = sensor_id), size = -Inf) + +scale_color_manual(values = c("blue", "orange")) + +guides(color = guide_legend()) + +# Facet the plot by day (e.g. create a panel per day) +# Use the new day labels here +facet_wrap(~ day_label, nrow = 2, strip.position = "left") + +# Change the x and y axis labels +xlab("Time") + +# Now you can add a y-axis label +ylab("Day of data collection") + +# Change the aesthetics of the x-axis labels +scale_x_datetime( +date_breaks = "30 mins", +date_labels = "%H:%M", +limits = c() +) + +# 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 +theme( +axis.text.y = element_blank(), +axis.ticks.y = element_blank() ) -file_dirs -# Make a list of the days to write out for each sensor -# This could be a single vector rather than a list since you want to write out the same days per sensor, but a list is useful in case you wanted to change the days (and number of days) to write out per sensor -days <- list( -`RFID` = c(1, 2), -`IRBB` = c(1, 2) +ggplot() + +# Add a vertical line for each RFID timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = rfid_raw3, +aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), +color = "blue", +linewidth = 0.3 +) + +# Add a vertical line for each beam breaker timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = irbb_raw3, +aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), +color = "orange", +linewidth = 0.3 +) + +# Add the fake layer of points so that you can create a legend +geom_point(data = all_sensors, aes(timestamp, y = 1, color = sensor_id), size = -Inf) + +scale_color_manual(values = c("blue", "orange")) + +guides(color = guide_legend()) + +# Facet the plot by day (e.g. create a panel per day) +# Use the new day labels here +facet_wrap(~ day_label, nrow = 2, strip.position = "left") + +# Change the x and y axis labels +xlab("Time") + +# Now you can add a y-axis label +ylab("Day of data collection") + +# Change the aesthetics of the x-axis labels +scale_x_datetime( +date_breaks = "30 mins", +date_labels = "%H:%M" +) + +# 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 +theme( +axis.text.y = element_blank(), +axis.ticks.y = element_blank() ) -days -# Finally, you need to make a list of the data frames that you want to write out. Since you already know how to filter data frames by day, that code can go inside of the loop, and you only need to specify a data frame per sensor type here -dats <- list( -`RFID` = sim_dats_rfid, -`IRBB` = sim_dats_irbb +ggplot() + +# Add a vertical line for each RFID timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = rfid_raw3, +aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), +color = "blue", +linewidth = 0.3 +) + +# Add a vertical line for each beam breaker timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = irbb_raw3, +aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), +color = "orange", +linewidth = 0.3 +) + +# Add the fake layer of points so that you can create a legend +geom_point(data = all_sensors, aes(timestamp, y = 1, color = sensor_id), size = -Inf) + +scale_color_manual(values = c("blue", "orange")) + +guides(color = guide_legend()) + +# Facet the plot by day (e.g. create a panel per day) +# Use the new day labels here +facet_wrap(~ day_label, nrow = 2, strip.position = "left") + +# Change the x and y axis labels +xlab("Time") + +# Now you can add a y-axis label +ylab("Day of data collection") + +# Change the aesthetics of the x-axis labels +scale_x_datetime( +date_breaks = "30 mins", +date_labels = "%H:%M", +expand = c(0, 0) +) + +# 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 +theme( +axis.text.y = element_blank(), +axis.ticks.y = element_blank() ) -glimpse(dats) -x <- 2 -y <- 1 -# 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]]] -days_tmp -# 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]]] -# 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 and single bracket filtering to pull out the right file name -write.csv(file = files[[x]][y], row.names = FALSE) -dats[[x]] -dats[[x]] %>% -# Filter the data frame by one day at a time -dplyr::filter(day == days_tmp[y]) %>% -dats[[x]] %>% -# Filter the data frame by one day at a time -dplyr::filter(day == days_tmp[y]) -files[[x]][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 and single bracket filtering to pull out the right file name -write.csv(file = file.path(file_dirs, files[[x]][y]), row.names = FALSE) -# 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 and single bracket filtering to pull out the right file name -write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) -# Testing -x <- 1 -y <- 1 -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 -# 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]) -files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv") -# Add the file path for the correct directory -files <- file.path(path, "RFID", files) -files -file.remove(files) -# Make a vector of sensor labels -sensors <- c("RFID", "IRBB") -sensors -# Make a named list of the custom file names to write out -files <- list( -`RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv"), -`IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") +ggplot() + +# Add a vertical line for each RFID timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = rfid_raw3, +aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), +color = "blue", +linewidth = 0.3 +) + +# Add a vertical line for each beam breaker timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = irbb_raw3, +aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), +color = "orange", +linewidth = 0.3 +) + +# Add the fake layer of points so that you can create a legend +geom_point(data = all_sensors, aes(timestamp, y = 1, color = sensor_id), size = -Inf) + +scale_color_manual(values = c("blue", "orange")) + +guides(color = guide_legend(override.aes = list(size = 2))) + +# Facet the plot by day (e.g. create a panel per day) +# Use the new day labels here +facet_wrap(~ day_label, nrow = 2, strip.position = "left") + +# Change the x and y axis labels +xlab("Time") + +# Now you can add a y-axis label +ylab("Day of data collection") + +# Change the aesthetics of the x-axis labels +scale_x_datetime( +date_breaks = "30 mins", +date_labels = "%H:%M" +) + +# 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 +theme( +axis.text.y = element_blank(), +axis.ticks.y = element_blank() ) -files -# Make a list of file paths per sensor that will be used inside of the loop -file_dirs <- list( -`RFID` = file.path(path, "RFID"), -`IRBB` = file.path(path, "IRBB") +ggplot() + +# Add a vertical line for each RFID timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = rfid_raw3, +aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), +color = "blue", +linewidth = 0.3 +) + +# Add a vertical line for each beam breaker timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = irbb_raw3, +aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), +color = "orange", +linewidth = 0.3 +) + +# Add the fake layer of lines so that you can create a legend +geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), size = -Inf) + +# Set the colors that will be used to color the lines +scale_color_manual(values = c("blue", "orange")) + +guides(color = guide_legend(override.aes = list(linewidth = 2))) + +# Facet the plot by day (e.g. create a panel per day) +# Use the new day labels here +facet_wrap(~ day_label, nrow = 2, strip.position = "left") + +# Change the x and y axis labels +xlab("Time") + +# Now you can add a y-axis label +ylab("Day of data collection") + +# Change the aesthetics of the x-axis labels +scale_x_datetime( +date_breaks = "30 mins", +date_labels = "%H:%M" +) + +# 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 +theme( +axis.text.y = element_blank(), +axis.ticks.y = element_blank() ) -file_dirs -# Make a list of the days to write out for each sensor -# This could be a single vector rather than a list since you want to write out the same days per sensor, but a list is useful in case you wanted to change the days (and number of days) to write out per sensor -days <- list( -`RFID` = c(1, 2), -`IRBB` = c(1, 2) +ggplot() + +# Add a vertical line for each RFID timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = rfid_raw3, +aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), +color = "blue", +linewidth = 0.3 +) + +# Add a vertical line for each beam breaker timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = irbb_raw3, +aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), +color = "orange", +linewidth = 0.3 +) + +# Add the fake layer of lines so that you can create a legend +geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = -Inf) + +# Set the colors that will be used to color the lines +scale_color_manual(values = c("blue", "orange")) + +guides(color = guide_legend(override.aes = list(linewidth = 2))) + +# Facet the plot by day (e.g. create a panel per day) +# Use the new day labels here +facet_wrap(~ day_label, nrow = 2, strip.position = "left") + +# Change the x and y axis labels +xlab("Time") + +# Now you can add a y-axis label +ylab("Day of data collection") + +# Change the aesthetics of the x-axis labels +scale_x_datetime( +date_breaks = "30 mins", +date_labels = "%H:%M" +) + +# 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 +theme( +axis.text.y = element_blank(), +axis.ticks.y = element_blank() ) -days -# Finally, you need to make a list of the data frames that you want to write out. Since you already know how to filter data frames by day, that code can go inside of the loop, and you only need to specify a data frame per sensor type here -dats <- list( -`RFID` = sim_dats_rfid, -`IRBB` = sim_dats_irbb +ggplot() + +# Add a vertical line for each RFID timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = rfid_raw3, +aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), +color = "blue", +linewidth = 0.3 +) + +# Add a vertical line for each beam breaker timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = irbb_raw3, +aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), +color = "orange", +linewidth = 0.3 +) + +# Add the fake layer of lines so that you can create a legend +geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + +# Set the colors that will be used to color the lines +scale_color_manual(values = c("blue", "orange")) + +guides(color = guide_legend(override.aes = list(linewidth = 2))) + +# Facet the plot by day (e.g. create a panel per day) +# Use the new day labels here +facet_wrap(~ day_label, nrow = 2, strip.position = "left") + +# Change the x and y axis labels +xlab("Time") + +# Now you can add a y-axis label +ylab("Day of data collection") + +# Change the aesthetics of the x-axis labels +scale_x_datetime( +date_breaks = "30 mins", +date_labels = "%H:%M" +) + +# 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 +theme( +axis.text.y = element_blank(), +axis.ticks.y = element_blank() ) -glimpse(dats) -# 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]]] -# 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 each sensor, iterate over days -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 and single bracket filtering to pull out the right file name -write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) -}) -})) -list.files(file.path(path, "RFID")) -list.files(file.path(path, "IRBB")) -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 -# 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") -path -View(combine_raw_data) -# Make a new directory inside of your working directory for saving data -file.path(path, "Data") -# 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 -file.path(path, "Data", "IRBB") -dir.create(file.path(path, "Data", "IRBB")) -rm(list = ls()) # Clean global environment -library(tidyverse) # Load the set of tidyverse packages -path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Initialize an object with the path that is your working directory -# Create 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) -# For the beam breakers, simulate timestamps for an entrance, an exit, and then another entrance and exit -irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") -# Simulate some RFID detection failures for the beam breaker data -irbb_ts <- c(irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") -# Simulate some stray beam breaker detections -irbb_ts <- c(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(irbb_ts) -# Make a vector for the experimental replicate -exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) -# Make the data frame with the experimental replicate metadata and the timestamps -sim_dats_rfid <- data.frame(replicate = 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 -) %>% -# Allocate the first half of the detections to the first individual using nrow(.)/2, and the second half of the detctions to the second individual using nrow(.)/2 again. Both of the rep() expressions are combined into a single vector using the c() function -dplyr::mutate( -PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2)) -) %>% -dplyr::mutate( -sensor_id = "RFID" +ggplot() + +# Add a vertical line for each RFID timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = rfid_raw3, +aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), +color = "blue", +linewidth = 0.3 +) + +# Add a vertical line for each beam breaker timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = irbb_raw3, +aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), +color = "orange", +linewidth = 0.3 +) + +# # Add the fake layer of lines so that you can create a legend +# geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + +# +# # Set the colors that will be used to color the lines +# scale_color_manual(values = c("blue", "orange")) + +# +# guides(color = guide_legend(override.aes = list(linewidth = 2))) + +# Facet the plot by day (e.g. create a panel per day) +# Use the new day labels here +facet_wrap(~ day_label, nrow = 2, strip.position = "left") + +# Change the x and y axis labels +xlab("Time") + +# Now you can add a y-axis label +ylab("Day of data collection") + +# Change the aesthetics of the x-axis labels +scale_x_datetime( +date_breaks = "30 mins", +date_labels = "%H:%M" +) + +# 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 +theme( +axis.text.y = element_blank(), +axis.ticks.y = element_blank() ) -glimpse(sim_dats_rfid) -sim_dats_rfid <- sim_dats_rfid %>% -bind_rows( -sim_dats_rfid %>% -dplyr::mutate( -day = 02 +ggplot() + +# Add a vertical line for each RFID timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = rfid_raw3, +aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), +color = "blue", +linewidth = 0.3 +) + +# Add a vertical line for each beam breaker timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = irbb_raw3, +aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), +color = "orange", +linewidth = 0.3 +) + +# Add the fake layer of lines so that you can create a legend +geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + +# Set the colors that will be used to color the lines in the legend itself +scale_color_manual(values = c("blue", "orange")) + +# Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend +guides(color = guide_legend(override.aes = list(linewidth = 2))) + +# Facet the plot by day (e.g. create a panel per day) +# Use the new day labels here +facet_wrap(~ day_label, nrow = 2, strip.position = "left") + +# Change the x and y axis labels +xlab("Time") + +# Now you can add a y-axis label +ylab("Day of data collection") + +# Change the aesthetics of the x-axis labels +scale_x_datetime( +date_breaks = "30 mins", +date_labels = "%H:%M" +) + +# 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 +theme( +axis.text.y = element_blank(), +axis.ticks.y = element_blank(), +legend.position = "top" ) +ggplot() + +# Add a vertical line for each RFID timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = rfid_raw3, +aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), +color = "blue", +linewidth = 0.3 +) + +# Add a vertical line for each beam breaker timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = irbb_raw3, +aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), +color = "orange", +linewidth = 0.3 +) + +# Add the fake layer of lines so that you can create a legend +geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + +# Set the colors that will be used to color the lines in the legend itself +scale_color_manual(values = c("blue", "orange")) + +# Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding +guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + +# Facet the plot by day (e.g. create a panel per day) +# Use the new day labels here +facet_wrap(~ day_label, nrow = 2, strip.position = "left") + +# Change the x and y axis labels +xlab("Time") + +# Now you can add a y-axis label +ylab("Day of data collection") + +# Change the aesthetics of the x-axis labels +scale_x_datetime( +date_breaks = "30 mins", +date_labels = "%H:%M" +) + +# 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 +theme( +axis.text.y = element_blank(), +axis.ticks.y = element_blank(), +legend.position = "top" ) -glimpse(sim_dats_rfid) # Double the number of rows, looks good -# Overwrite the vector exp_rep with a new vector the same length as irbb_ts -exp_rep <- rep(x = "Pair_01", times = length(irbb_ts)) -sim_dats_irbb <- data.frame(replicate = exp_rep, timestamps = irbb_ts) -sim_dats_irbb <- sim_dats_irbb %>% -dplyr::mutate( -year = 2023, -month = 08, -day = 01, -sensor_id = "Beam breakers" +ggplot() + +# Add a vertical line for each RFID timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = rfid_raw3, +aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), +color = "blue", +linewidth = 0.3 +) + +# Add a vertical line for each beam breaker timestamp +# Use the new version of this data frame with the new day_label column +# Make sure to use the new timestamp column +geom_segment( +data = irbb_raw3, +aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), +color = "orange", +linewidth = 0.3 +) + +# Add the fake layer of lines so that you can create a legend +# The argument color here must be supplied with the name of a column that is the data type "factor" in R +geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + +# Set the colors that will be used to color the lines in the legend itself +# The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column +scale_color_manual(values = c("blue", "orange")) + +# Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding +guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + +# Facet the plot by day (e.g. create a panel per day) +# Use the new day labels here +facet_wrap(~ day_label, nrow = 2, strip.position = "left") + +# Change the x and y axis labels +xlab("Time") + +# Now you can add a y-axis label +ylab("Day of data collection") + +# Change the aesthetics of the x-axis labels +scale_x_datetime( +date_breaks = "30 mins", +date_labels = "%H:%M" +) + +# 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 +theme( +axis.text.y = element_blank(), +axis.ticks.y = element_blank(), +legend.position = "top" ) -glimpse(sim_dats_irbb) -sim_dats_irbb <- sim_dats_irbb %>% -bind_rows( -sim_dats_irbb %>% -dplyr::mutate( -day = 02 -) -) -glimpse(sim_dats_irbb) # Double the number of rows, looks good -# Two days, looks good -sim_dats_irbb %>% -pull(day) %>% -unique() -# 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") -rfid_file -# Make a vector of sensor labels -sensors <- c("RFID", "IRBB") -sensors -# Make a named list of the custom file names to write out -files <- list( -`RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv"), -`IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") -) -files -# Make a list of file paths per sensor that will be used inside of the loop -file_dirs <- list( -`RFID` = file.path(path, "RFID"), -`IRBB` = file.path(path, "IRBB") -) -file_dirs -# Make a list of the days to write out for each sensor -# This could be a single vector rather than a list since you want to write out the same days per sensor, but a list is useful in case you wanted to change the days (and number of days) to write out per sensor -days <- list( -`RFID` = c(1, 2), -`IRBB` = c(1, 2) -) -days -# Finally, you need to make a list of the data frames that you want to write out. Since you already know how to filter data frames by day, that code can go inside of the loop, and you only need to specify a data frame per sensor type here -dats <- list( -`RFID` = sim_dats_rfid, -`IRBB` = sim_dats_irbb -) -glimpse(dats) -# 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 each sensor, iterate over days -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 and single bracket filtering to pull out the right file name -write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) -}) -})) -# 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 -# 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 each sensor, iterate over days -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 and single bracket filtering to pull out the right file name -write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) -}) -})) -?POSIXct -? -DateTimeClasses -?DateTimeClasses -list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") -combine_raw_data(sensors = "RFID", path = path, data_dir = "Data", out_dir = "raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") -# 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") -combine_raw_data(sensors = "RFID", path = path, data_dir = "Data", out_dir = "raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") -library(data.table) # Load other packages that the ABISSMAL functions require -combine_raw_data(sensors = "RFID", path = path, data_dir = "Data", out_dir = "raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") -rm(list = ls()) # Clean global environment -library(tidyverse) # Load the set of tidyverse packages -path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Initialize an object with the path that is your working directory -# Create 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) -# For the beam breakers, simulate timestamps for an entrance, an exit, and then another entrance and exit -irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") -# Simulate some RFID detection failures for the beam breaker data -irbb_ts <- c(irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") -# Simulate some stray beam breaker detections -irbb_ts <- c(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(irbb_ts) -# Make a vector for the experimental replicate -exp_rep <- rep(x = "Nest_01", times = length(rfid_ts)) -# 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 -) %>% -# Allocate the first half of the detections to the first individual using nrow(.)/2, and the second half of the detctions to the second individual using nrow(.)/2 again. Both of the rep() expressions are combined into a single vector using the c() function -dplyr::mutate( -PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2)) -) %>% -dplyr::mutate( -sensor_id = "RFID" -) -glimpse(sim_dats_rfid) -sim_dats_rfid <- sim_dats_rfid %>% -bind_rows( -sim_dats_rfid %>% -dplyr::mutate( -day = 02 -) -) -glimpse(sim_dats_rfid) # Double the number of rows, looks good -# Overwrite the vector exp_rep with a new vector the same length as irbb_ts -exp_rep <- rep(x = "Nest_01", times = length(irbb_ts)) -sim_dats_irbb <- data.frame(chamber_id = exp_rep, timestamps = irbb_ts) -sim_dats_irbb <- sim_dats_irbb %>% -dplyr::mutate( -year = 2023, -month = 08, -day = 01, -sensor_id = "Beam breakers" -) -glimpse(sim_dats_irbb) -sim_dats_irbb <- sim_dats_irbb %>% -bind_rows( -sim_dats_irbb %>% -dplyr::mutate( -day = 02 -) -) -glimpse(sim_dats_irbb) # Double the number of rows, looks good -# Two days, looks good -sim_dats_irbb %>% -pull(day) %>% -unique() -# 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 -dir.create(file.path(path, "Data", "IRBB")) -# Make a vector of sensor labels -sensors <- c("RFID", "IRBB") -sensors -# Make a named list of the custom file names to write out -files <- list( -`RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv"), -`IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") -) -files -# Make a list of file paths per sensor that will be used inside of the loop -file_dirs <- list( -`RFID` = file.path(path, "Data/RFID"), -`IRBB` = file.path(path, "Data/IRBB") -) -file_dirs -# Make a list of the days to write out for each sensor -# This could be a single vector rather than a list since you want to write out the same days per sensor, but a list is useful in case you wanted to change the days (and number of days) to write out per sensor -days <- list( -`RFID` = c(1, 2), -`IRBB` = c(1, 2) -) -days -# Finally, you need to make a list of the data frames that you want to write out. Since you already know how to filter data frames by day, that code can go inside of the loop, and you only need to specify a data frame per sensor type here -dats <- list( -`RFID` = sim_dats_rfid, -`IRBB` = sim_dats_irbb -) -glimpse(dats) -# 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 each sensor, iterate over days -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 and single bracket filtering to pull out the right file name -write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) -}) -})) -list.files(file.path(path, "Data/RFID")) -list.files(file.path(path, "Data/IRBB")) -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 -path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Initialize an object with the path that is your working directory -# 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") -combine_raw_data(sensors = "RFID", path = path, data_dir = "Data", out_dir = "raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") -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") -list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") -file.path(path, "Data/raw_combined") -list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") -rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) -glimpse(rfid_data) -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") -list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") -detect_perching_events(file_nm = "combined_raw_data_RFID.csv", threshold = 2, run_length = 2, PIT_tag_col_nm = "PIT_tag", rfid_label = "RFID", path = data_path, 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(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", path = data_path, 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(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 = data_path, 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(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 = path, 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(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") -perching <- read.csv(file.path(path, "Data", "processed", "perching_events_RFID.csv")) -glimpse(perching) -perching$perching_start -perching$start -# The timestamps when each perching event started -perching$perching_start -perching$PIT_tag -View(preprocess_detections) -preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = path, data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") -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") -list.files(file.path(path, "Data/processed")) -rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) -glimpse(rfid_pp) -rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) -glimpse(rfid_data) -preprocess_detections(sensor = "IRBB", timestamps_col_nm = "timestamp_ms", group_col_nm = NULL, 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") -preprocess_detections(sensor = "IRBB", timestamps_col_nm = "timestamp_ms", group_col_nm = "sensor_id", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") -list.files(file.path(path, "Data/processed")) -irbb_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_IRBB.csv")) -glimpse(rfid_pp) -library(ggplot2) # Load a package for making plots diff --git a/R/vignettes/Vignette_04_SaveData.Rmd b/R/vignettes/Vignette_04_SaveData.Rmd index fd497f3..800fc98 100644 --- a/R/vignettes/Vignette_04_SaveData.Rmd +++ b/R/vignettes/Vignette_04_SaveData.Rmd @@ -13,7 +13,7 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE)

Vignette Overview and Learning Objectives

-In this fourth vignette, you will write out spreadsheets of simulated detections of animal movements to your computer. You'll begin using this data in the ABISSMAL data processing and analysis workflow, including combining raw data across days and pre-processing the raw data. You will continue to use coding skills that you learned in the previous vignettes, and you will learn additional skills that include: +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 will learn additional skills that include: 1. Indexing and filtering data frames 2. Checking data frame structure with base R and the tidyverse diff --git a/R/vignettes/Vignette_05_ProcessData.Rmd b/R/vignettes/Vignette_05_ProcessData.Rmd index 7116bb6..bd7bcec 100644 --- a/R/vignettes/Vignette_05_ProcessData.Rmd +++ b/R/vignettes/Vignette_05_ProcessData.Rmd @@ -13,7 +13,7 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE)

Vignette Overview and Learning Objectives

-In this fourth vignette, you will write out spreadsheets of simulated detections of animal movements to your computer. You'll begin using this data in the ABISSMAL data processing and analysis workflow, including combining raw data across days and pre-processing the raw data. You will continue to use coding skills that you learned in the previous vignettes, and you will learn additional skills that include: +In this fifth vignette, you will begin using the simulated detections of animal movements in the ABISSMAL data processing and analysis workflow, including combining raw data across days and pre-processing the raw data. You will also make visualizations of the processed data. You will continue to use coding skills that you learned in the previous vignettes, and you will learn additional skills that include: 1. Accessing custom functions 2. Using custom functions @@ -287,7 +287,7 @@ ggplot() + ``` -This plot has more information, but it's difficult to interpret. Some of the detections for each sensor type occurred so close in time that they're layered nearly directly over one another. You can change where the line segements per sensor start and end on the y-axis in order to have better temporal resolution for visualizng the datasets from both sensors. +This plot has more information, but it's difficult to interpret. Some of the detections for each sensor type occurred so close in time that they're layered nearly directly over one another. You can change the position of the line segements on the y-axis in order to have better resolution for visualizng the datasets from both sensors. ```{r} @@ -533,7 +533,7 @@ glimpse(all_sensors) ``` -Then make the plot...TKTK split up creating the legend a bit more (e.g. default colors first) +Then make the plot. ```{r} ggplot() + @@ -562,11 +562,6 @@ ggplot() + # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + - # Set the colors that will be used to color the lines in the legend itself - # The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column - # If you do not supply your own color values, then R will assign default colors to each factor level - scale_color_manual(values = c("blue", "orange")) + - # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + @@ -599,77 +594,139 @@ ggplot() + ``` - - -Then...TKTK add a legend, write out a plot as an image file. More modification to get font size right - +You added a legend to the plot, but the colors in the legend don't line up with the colors of the line segments per sensor. You can use the function `scale_color_manual()` to match colors between the plot and legend. ```{r} - ggplot() + +ggplot() + # Add a vertical line for each RFID timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column geom_segment( - data = raw_rfid, - aes(x = timestamp, y = 0, xend = timestamp, yend = y_max/4), - color = cols[4], + data = rfid_raw3, + aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), + color = "blue", linewidth = 0.3 ) + + # Add a vertical line for each beam breaker timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column geom_segment( - data = raw_all2 %>% - dplyr::filter(sensor_id == "Inner Beam Breaker"), - aes(x = timestamp, xend = timestamp, y = y_max/4, yend = (y_max/4) * 2), - color = cols[3], + data = irbb_raw3, + aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), + color = "orange", linewidth = 0.3 ) + + # Add the fake layer of lines so that you can create a legend + # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below + geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + + + # Set the colors that will be used to color the lines in the legend itself + # The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column + # If you do not supply your own color values, then R will assign default colors to each factor level + scale_color_manual(values = c("blue", "orange")) + + + # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding + guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 2, strip.position = "left") + + + # Change the x and y axis labels + xlab("Time") + + + # Now you can add a y-axis label + ylab("Day of data collection") + + + # Change the aesthetics of the x-axis labels + scale_x_datetime( + date_breaks = "30 mins", + date_labels = "%H:%M" + ) + + + # 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" + ) + +``` + +Now that you've made the plot in R, you can save the plot as an image file on your computer using the function `ggsave()`. +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column geom_segment( - data = raw_all2 %>% - dplyr::filter(sensor_id == "RFID"), - aes(x = timestamp, xend = timestamp, y = (y_max/4) * 2, yend = (y_max/4) * 3), - color = cols[2], + data = rfid_raw3, + aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), + color = "blue", linewidth = 0.3 ) + + # Add a vertical line for each beam breaker timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column geom_segment( - data = raw_all2 %>% - dplyr::filter(sensor_id == "Outer Beam Breaker"), - aes(x = timestamp, xend = timestamp, y = (y_max/4) * 3, yend = y_max), - color = cols[1], + data = irbb_raw3, + aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), + color = "orange", linewidth = 0.3 ) + - # I removed switch = "y" because it's possible to rotate strip lables horizontally when they're on the right - facet_grid(rows = vars(new_date)) + + # Add the fake layer of lines so that you can create a legend + # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below + geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + + + # Set the colors that will be used to color the lines in the legend itself + # The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column + # If you do not supply your own color values, then R will assign default colors to each factor level + scale_color_manual(values = c("blue", "orange")) + + + # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding + guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 2, strip.position = "left") + - # Add shaded rectangles for the nocturnal periods - geom_rect(data = rect_df2, aes(xmin = time_xmin, xmax = time_xmax, ymin = -Inf, ymax = Inf), color = alpha("black", 0), fill = alpha("black", 0.08), inherit.aes = FALSE) + + # Change the x and y axis labels + xlab("Time") + + + # Now you can add a y-axis label + ylab("Day of data collection") + + # Change the aesthetics of the x-axis labels scale_x_datetime( - breaks = seq(x_lims[1], x_lims[2], "1 hour"), - date_labels = "%H:%M", - expand = c(0, 0), - limits = x_lims + date_breaks = "30 mins", + date_labels = "%H:%M" ) + - - scale_y_continuous(limits = c(0, y_max)) + - guides(color = "none") + + + # Use this function to convert the plot background to black and white theme_bw() + - ylab("") + - xlab("") + + + # 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.ticks.y = element_blank(), axis.text.y = element_blank(), - panel.grid.major = element_blank(), - panel.grid.minor = element_blank(), - axis.text.x = element_text(size = 7, angle = 90, vjust = 0.5, hjust = 0.5, color = "black"), - strip.text.y = element_text(size = 7, face = "bold", angle = 0), - strip.background = element_rect(fill = "white"), - legend.position = "top", - axis.ticks = element_line(linewidth = 0.25) + axis.ticks.y = element_blank(), + legend.position = "top" ) +# Save the image file to your computer +ggsave(file.path(path, "raw_detections.tiff"), width = 8, height = 6, units = "in", dpi = 300) ``` - +You just learned how to make a plot in R, and also how to save it to your computer. 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. \ No newline at end of file From 5a39fbd0a79095ec1de44a2bd848c95d461481b0 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Wed, 24 Jan 2024 18:34:35 -0500 Subject: [PATCH 19/69] Simplifying plotting code in fifth vignette; started a sixth vignette --- R/vignettes/.Rhistory | 372 +++++------ R/vignettes/Vignette_05_ProcessData.Rmd | 5 + R/vignettes/Vignette_06_VisualizeData.Rmd | 737 ++++++++++++++++++++++ 3 files changed, 928 insertions(+), 186 deletions(-) create mode 100644 R/vignettes/Vignette_06_VisualizeData.Rmd diff --git a/R/vignettes/.Rhistory b/R/vignettes/.Rhistory index 9458043..f8286e9 100644 --- a/R/vignettes/.Rhistory +++ b/R/vignettes/.Rhistory @@ -1,33 +1,4 @@ -# Make sure to use the new timestamp column -geom_segment( -data = irbb_raw3, -aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), -color = "orange", -linewidth = 0.3 -) + -# Add the fake layer of points so that you can create a legend -geom_point(data = all_sensors, aes(timestamp, y = 1, color = sensor_id), size = 0) + -scale_color_manual(values = c("blue", "orange")) + -guides(color = guide_legend()) + -# Facet the plot by day (e.g. create a panel per day) -# Use the new day labels here -facet_wrap(~ day_label, nrow = 2, strip.position = "left") + -# Change the x and y axis labels -xlab("Time") + -# Now you can add a y-axis label -ylab("Day of data collection") + -# Change the aesthetics of the x-axis labels -scale_x_datetime( -date_breaks = "30 mins", -date_labels = "%H:%M", -limits = c() -) + -# 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 -theme( -axis.text.y = element_blank(), -axis.ticks.y = element_blank() +legend.position = "top" ) ggplot() + # Add a vertical line for each RFID timestamp @@ -48,10 +19,12 @@ aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), color = "orange", linewidth = 0.3 ) + -# Add the fake layer of points so that you can create a legend -geom_point(data = all_sensors, aes(timestamp, y = 1, color = sensor_id), size = -Inf) + +# Add the fake layer of lines so that you can create a legend +geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + +# Set the colors that will be used to color the lines in the legend itself scale_color_manual(values = c("blue", "orange")) + -guides(color = guide_legend()) + +# Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding +guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + # Facet the plot by day (e.g. create a panel per day) # Use the new day labels here facet_wrap(~ day_label, nrow = 2, strip.position = "left") + @@ -62,15 +35,15 @@ ylab("Day of data collection") + # Change the aesthetics of the x-axis labels scale_x_datetime( date_breaks = "30 mins", -date_labels = "%H:%M", -limits = c() +date_labels = "%H:%M" ) + # 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 theme( axis.text.y = element_blank(), -axis.ticks.y = element_blank() +axis.ticks.y = element_blank(), +legend.position = "top" ) ggplot() + # Add a vertical line for each RFID timestamp @@ -91,10 +64,14 @@ aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), color = "orange", linewidth = 0.3 ) + -# Add the fake layer of points so that you can create a legend -geom_point(data = all_sensors, aes(timestamp, y = 1, color = sensor_id), size = -Inf) + +# Add the fake layer of lines so that you can create a legend +# The argument color here must be supplied with the name of a column that is the data type "factor" in R +geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + +# Set the colors that will be used to color the lines in the legend itself +# The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column scale_color_manual(values = c("blue", "orange")) + -guides(color = guide_legend()) + +# Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding +guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + # Facet the plot by day (e.g. create a panel per day) # Use the new day labels here facet_wrap(~ day_label, nrow = 2, strip.position = "left") + @@ -112,143 +89,119 @@ theme_bw() + # Use aesthetics functions to remove the y-axis labels and ticks theme( axis.text.y = element_blank(), -axis.ticks.y = element_blank() +axis.ticks.y = element_blank(), +legend.position = "top" +) +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 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") +list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") +rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) +glimpse(rfid_data) +rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) +glimpse(rfid_pp) +irbb_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_IRBB.csv")) +glimpse(irbb_pp) +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")) +) +# You should see that the timestamp_ms is in "dttm" format, which means that the POSIX conversion was done correctly +glimpse(rfid_raw) +irbb_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_IRBB.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")) ) +glimpse(irbb_raw) ggplot() + # Add a vertical line for each RFID timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column geom_segment( -data = rfid_raw3, -aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), +data = rfid_raw, +aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1), +color = "blue", +linewidth = 0.3 +) +ggplot() + +# Add a vertical line for each RFID timestamp +geom_segment( +data = rfid_raw, +aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 1), color = "blue", linewidth = 0.3 ) + # Add a vertical line for each beam breaker timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column geom_segment( -data = irbb_raw3, -aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), +data = irbb_raw, +aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 1), color = "orange", linewidth = 0.3 -) + -# Add the fake layer of points so that you can create a legend -geom_point(data = all_sensors, aes(timestamp, y = 1, color = sensor_id), size = -Inf) + -scale_color_manual(values = c("blue", "orange")) + -guides(color = guide_legend()) + -# Facet the plot by day (e.g. create a panel per day) -# Use the new day labels here -facet_wrap(~ day_label, nrow = 2, strip.position = "left") + -# Change the x and y axis labels -xlab("Time") + -# Now you can add a y-axis label -ylab("Day of data collection") + -# Change the aesthetics of the x-axis labels -scale_x_datetime( -date_breaks = "30 mins", -date_labels = "%H:%M", -expand = c(0, 0) -) + -# 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 -theme( -axis.text.y = element_blank(), -axis.ticks.y = element_blank() ) ggplot() + # Add a vertical line for each RFID timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column geom_segment( -data = rfid_raw3, -aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), +data = rfid_raw, +aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), color = "blue", linewidth = 0.3 ) + # Add a vertical line for each beam breaker timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column geom_segment( -data = irbb_raw3, -aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), +data = irbb_raw, +aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), color = "orange", linewidth = 0.3 -) + -# Add the fake layer of points so that you can create a legend -geom_point(data = all_sensors, aes(timestamp, y = 1, color = sensor_id), size = -Inf) + -scale_color_manual(values = c("blue", "orange")) + -guides(color = guide_legend(override.aes = list(size = 2))) + -# Facet the plot by day (e.g. create a panel per day) -# Use the new day labels here -facet_wrap(~ day_label, nrow = 2, strip.position = "left") + -# Change the x and y axis labels -xlab("Time") + -# Now you can add a y-axis label -ylab("Day of data collection") + -# Change the aesthetics of the x-axis labels -scale_x_datetime( -date_breaks = "30 mins", -date_labels = "%H:%M" -) + -# 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 -theme( -axis.text.y = element_blank(), -axis.ticks.y = element_blank() ) ggplot() + # Add a vertical line for each RFID timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column geom_segment( -data = rfid_raw3, -aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), +data = rfid_raw, +aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), color = "blue", linewidth = 0.3 ) + # Add a vertical line for each beam breaker timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column geom_segment( -data = irbb_raw3, -aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), +data = irbb_raw, +aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), color = "orange", linewidth = 0.3 ) + -# Add the fake layer of lines so that you can create a legend -geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), size = -Inf) + -# Set the colors that will be used to color the lines -scale_color_manual(values = c("blue", "orange")) + -guides(color = guide_legend(override.aes = list(linewidth = 2))) + # Facet the plot by day (e.g. create a panel per day) -# Use the new day labels here -facet_wrap(~ day_label, nrow = 2, strip.position = "left") + -# Change the x and y axis labels -xlab("Time") + -# Now you can add a y-axis label -ylab("Day of data collection") + -# Change the aesthetics of the x-axis labels -scale_x_datetime( -date_breaks = "30 mins", -date_labels = "%H:%M" -) + -# 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 -theme( -axis.text.y = element_blank(), -axis.ticks.y = element_blank() +facet_wrap(~ day, nrow = 2, strip.position = "left") +ifelse(test = rfid_raw$day == 1, yes = "Day 1", no = "Day 2") +# Create a new column in the raw data for the date of data collection +rfid_raw2 <- rfid_raw %>% +dplyr::mutate( +day_label = ifelse(day == 1, "Day 1", "Day 2") +) +# You should see that the new column "day_label" was created +# You'll see that a new year, month, and day were appended to the updated timestamps, but this is expected (see above) +glimpse(rfid_raw2) +# Repeat this process for the beam breaker data +irbb_raw2 <- irbb_raw %>% +dplyr::mutate( +day_label = ifelse(day == 1, "Day 1", "Day 2") ) +glimpse(irbb_raw2) ggplot() + # Add a vertical line for each RFID timestamp # Use the new version of this data frame with the new day_label column # Make sure to use the new timestamp column geom_segment( -data = rfid_raw3, -aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), +data = rfid_raw2, +aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), color = "blue", linewidth = 0.3 ) + @@ -256,35 +209,26 @@ linewidth = 0.3 # Use the new version of this data frame with the new day_label column # Make sure to use the new timestamp column geom_segment( -data = irbb_raw3, -aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), +data = irbb_raw2, +aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), color = "orange", linewidth = 0.3 ) + -# Add the fake layer of lines so that you can create a legend -geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = -Inf) + -# Set the colors that will be used to color the lines -scale_color_manual(values = c("blue", "orange")) + -guides(color = guide_legend(override.aes = list(linewidth = 2))) + # Facet the plot by day (e.g. create a panel per day) # Use the new day labels here -facet_wrap(~ day_label, nrow = 2, strip.position = "left") + -# Change the x and y axis labels -xlab("Time") + -# Now you can add a y-axis label -ylab("Day of data collection") + -# Change the aesthetics of the x-axis labels -scale_x_datetime( -date_breaks = "30 mins", -date_labels = "%H:%M" -) + -# 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 -theme( -axis.text.y = element_blank(), -axis.ticks.y = element_blank() +facet_wrap(~ day_label, nrow = 2, strip.position = "left") +rfid_raw3 <- rfid_raw2 %>% +dplyr::mutate( +timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%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(rfid_raw3) +# Repeat this process for the beam breaker data +irbb_raw3 <- irbb_raw2 %>% +dplyr::mutate( +timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%H:%M:%S"), format = "%H:%M:%S")) +) +glimpse(irbb_raw3) ggplot() + # Add a vertical line for each RFID timestamp # Use the new version of this data frame with the new day_label column @@ -304,29 +248,13 @@ aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), color = "orange", linewidth = 0.3 ) + -# Add the fake layer of lines so that you can create a legend -geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + -# Set the colors that will be used to color the lines -scale_color_manual(values = c("blue", "orange")) + -guides(color = guide_legend(override.aes = list(linewidth = 2))) + # Facet the plot by day (e.g. create a panel per day) # Use the new day labels here facet_wrap(~ day_label, nrow = 2, strip.position = "left") + -# Change the x and y axis labels -xlab("Time") + -# Now you can add a y-axis label -ylab("Day of data collection") + # Change the aesthetics of the x-axis labels scale_x_datetime( date_breaks = "30 mins", date_labels = "%H:%M" -) + -# 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 -theme( -axis.text.y = element_blank(), -axis.ticks.y = element_blank() ) ggplot() + # Add a vertical line for each RFID timestamp @@ -347,13 +275,6 @@ aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), color = "orange", linewidth = 0.3 ) + -# # Add the fake layer of lines so that you can create a legend -# geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + -# -# # Set the colors that will be used to color the lines -# scale_color_manual(values = c("blue", "orange")) + -# -# guides(color = guide_legend(override.aes = list(linewidth = 2))) + # Facet the plot by day (e.g. create a panel per day) # Use the new day labels here facet_wrap(~ day_label, nrow = 2, strip.position = "left") + @@ -373,6 +294,14 @@ theme( axis.text.y = element_blank(), axis.ticks.y = element_blank() ) +all_sensors <- rfid_raw3 %>% +bind_rows( +irbb_raw3 +) %>% +dplyr::mutate( +sensor_id = factor(sensor_id) +) +glimpse(all_sensors) ggplot() + # Add a vertical line for each RFID timestamp # Use the new version of this data frame with the new day_label column @@ -393,11 +322,10 @@ color = "orange", linewidth = 0.3 ) + # Add the fake layer of lines so that you can create a legend +# The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + -# Set the colors that will be used to color the lines in the legend itself -scale_color_manual(values = c("blue", "orange")) + -# Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend -guides(color = guide_legend(override.aes = list(linewidth = 2))) + +# Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding +guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + # Facet the plot by day (e.g. create a panel per day) # Use the new day labels here facet_wrap(~ day_label, nrow = 2, strip.position = "left") + @@ -413,6 +341,7 @@ date_labels = "%H:%M" # 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(), @@ -438,8 +367,11 @@ color = "orange", linewidth = 0.3 ) + # Add the fake layer of lines so that you can create a legend +# The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + # Set the colors that will be used to color the lines in the legend itself +# The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column +# If you do not supply your own color values, then R will assign default colors to each factor level scale_color_manual(values = c("blue", "orange")) + # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + @@ -458,6 +390,7 @@ date_labels = "%H:%M" # 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(), @@ -465,6 +398,67 @@ legend.position = "top" ) ggplot() + # Add a vertical line for each RFID timestamp +geom_segment( +data = raw_rfid, +aes(x = timestamp, y = 0, xend = timestamp, yend = y_max/4), +color = cols[4], +linewidth = 0.3 +) + +geom_segment( +data = raw_all2 %>% +dplyr::filter(sensor_id == "Inner Beam Breaker"), +aes(x = timestamp, xend = timestamp, y = y_max/4, yend = (y_max/4) * 2), +color = cols[3], +linewidth = 0.3 +) + +geom_segment( +data = raw_all2 %>% +dplyr::filter(sensor_id == "RFID"), +aes(x = timestamp, xend = timestamp, y = (y_max/4) * 2, yend = (y_max/4) * 3), +color = cols[2], +linewidth = 0.3 +) + +geom_segment( +data = raw_all2 %>% +dplyr::filter(sensor_id == "Outer Beam Breaker"), +aes(x = timestamp, xend = timestamp, y = (y_max/4) * 3, yend = y_max), +color = cols[1], +linewidth = 0.3 +) + +# I removed switch = "y" because it's possible to rotate strip lables horizontally when they're on the right +facet_grid(rows = vars(new_date)) + +# Add shaded rectangles for the nocturnal periods +geom_rect(data = rect_df2, aes(xmin = time_xmin, xmax = time_xmax, ymin = -Inf, ymax = Inf), color = alpha("black", 0), fill = alpha("black", 0.08), inherit.aes = FALSE) + +scale_x_datetime( +breaks = seq(x_lims[1], x_lims[2], "1 hour"), +date_labels = "%H:%M", +expand = c(0, 0), +limits = x_lims +) + +scale_y_continuous(limits = c(0, y_max)) + +guides(color = "none") + +theme_bw() + +ylab("") + +xlab("") + +theme( +axis.ticks.y = element_blank(), +axis.text.y = element_blank(), +panel.grid.major = element_blank(), +panel.grid.minor = element_blank(), +axis.text.x = element_text(size = 7, angle = 90, vjust = 0.5, hjust = 0.5, color = "black"), +strip.text.y = element_text(size = 7, face = "bold", angle = 0), +strip.background = element_rect(fill = "white"), +legend.position = "top", +axis.ticks = element_line(linewidth = 0.25) +) +# Save the image file to your computer +ggsave(file.path(path, "raw_detections.tiff"), width = 4, height = 3, units = "inches", res = 300) +# Save the image file to your computer +ggsave(file.path(path, "raw_detections.tiff"), width = 4, height = 3, units = "in", res = 300) +# Save the image file to your computer +ggsave(file.path(path, "raw_detections.tiff"), width = 4, height = 3, units = "in", dpi = 300) +ggplot() + +# Add a vertical line for each RFID timestamp # Use the new version of this data frame with the new day_label column # Make sure to use the new timestamp column geom_segment( @@ -483,10 +477,11 @@ color = "orange", linewidth = 0.3 ) + # Add the fake layer of lines so that you can create a legend -# The argument color here must be supplied with the name of a column that is the data type "factor" in R +# The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + # Set the colors that will be used to color the lines in the legend itself # The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column +# If you do not supply your own color values, then R will assign default colors to each factor level scale_color_manual(values = c("blue", "orange")) + # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + @@ -505,8 +500,13 @@ date_labels = "%H:%M" # 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" ) +# Save the image file to your computer +ggsave(file.path(path, "raw_detections.tiff"), width = 4, height = 3, units = "in", dpi = 300) +# Save the image file to your computer +ggsave(file.path(path, "raw_detections.tiff"), width = 8, height = 6, units = "in", dpi = 300) diff --git a/R/vignettes/Vignette_05_ProcessData.Rmd b/R/vignettes/Vignette_05_ProcessData.Rmd index bd7bcec..45a3431 100644 --- a/R/vignettes/Vignette_05_ProcessData.Rmd +++ b/R/vignettes/Vignette_05_ProcessData.Rmd @@ -241,6 +241,11 @@ ggplot(data = rfid_raw) 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 (see a plot in the next vignette). + + + +TKTK the code below complicates adding a legend. Consider this code to be a more complex version of the figure...maybe save for another script? And here add just a single sensor + 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 x-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, information on the x-axis only). ```{r} diff --git a/R/vignettes/Vignette_06_VisualizeData.Rmd b/R/vignettes/Vignette_06_VisualizeData.Rmd new file mode 100644 index 0000000..45a3431 --- /dev/null +++ b/R/vignettes/Vignette_06_VisualizeData.Rmd @@ -0,0 +1,737 @@ +--- +title: "Vignette 05: Process Data" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: html_document +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = TRUE) + +``` + +

Vignette Overview and Learning Objectives

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

Load packages and your working directory path

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

Load ABISSMAL functions

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

Access ABISSMAL function information

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

Combine raw data

+ +Once you've loaded the ABISSMAL functions, you can start using the first function, `combine_raw_data()`, to combine data collected across days per sensor into a single spreadsheet per sensor. You'll start by combining raw data for the RFID sensor collected over different days into a single spreadsheet. + +Here goes more information about the arguments you are supplying to the function below: + +* `sensors` is a vector containing the labels of the sensors for which you want to combine raw data. Below you're specifying RFID as a single sensor + +* `path` is your general working directory + +* `data_dir` is the folder that holds data inside of your working directory + +* `out_dir` is the folder where you want to save the combined raw data spreadsheet. The function will create this folder if it does not already exist + +* `tz` is the timezone for converting timestamps to POSIXct format. The default is "America/New York", and you can check out the "Time zones" section in the documentation for DateTimeClasses in R for more information (`?DateTimeClasses`) + +* `POSIXct_format` is a string containing the POSIX formatting information for how dates and timestamps should be combined into a single column. The default is to encode the year as a 4 digit number and the month and day as 2 digit numbers, separated by dashes. The date is followed by a space, then the 2-digit hour, minute, and decimal second (separated by colons) +```{r} + +combine_raw_data(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 single spreadsheet of raw RFID data to the new directory `raw_combined`: +```{r} + +list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") + +``` + +You can read this combined .csv file (called "combined_raw_data_RFID.csv") back into R to check out the structure of this spreadsheet: +```{r} + +rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) + +glimpse(rfid_data) + +``` + +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 hace a unique identifier in `sensor_id`). + +The function created a new timestamps column in POSIXct format for downsteam processing and analysis, but it kept the original timestamps column. The function also 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 not removed 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`: +```{r} + +combine_raw_data(sensors = c("RFID", "IRBB"), path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +``` + +The RFID data will be overwritten, and you should see an additional spreadsheet with the raw IRBB data: +```{r} + +list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") + +``` + +

Detect perching events

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

Pre-process raw data

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

Plot raw and pre-processed data

+ +Now that you've combined the raw data per sensor, detected perching events in the raw data, and pre-processed the raw data, it's time to visualize these different datasets. Making visualizations in R is important for generating high-quality figures for publications and presentations, but it's also important for checking your work as you process and analyze data. + +In the code below, you'll learn how to use functions from the `ggplot2` package to make barcode style visualizations of the raw and processed detection datasets. + +Start by reading in the raw data for each sensor, and convert the timestamps to POSIX format. +```{r} + +rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) %>% + # The timestamps must be converted to POSIX format every time that the data is read back into R for plotting + dplyr::mutate( + timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) + ) + +# You should see that the timestamp_ms is in "dttm" format, which means that the POSIX conversion was done correctly +glimpse(rfid_raw) + +irbb_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_IRBB.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")) + ) + +glimpse(irbb_raw) + +``` + +Next, start plotting this raw data using functions from `ggplot2`. This package is part of the `tidyverse`, but can also be installed and used separately. You can check out this [link](https://ggplot2.tidyverse.org/) for more resources to learn how to use ggplot 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. + +Thde `ggplot2` package has a unique syntax for building plots, in which you start making a plot by calling the function `ggplot()`, and then add features by layering on other `ggplot2` functions with the `+` symbol. + +If you call `ggplot()`, you'll see that the function immediately draws a blank plot in your Plots pane in RStudio. +```{r} + +ggplot() + +``` + +The plot will still remain blank even if you supply information about your data in order to set up plot aesthetics. +```{r} + +ggplot(data = rfid_raw) + +``` + +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 (see a plot in the next vignette). + + + + +TKTK the code below complicates adding a legend. Consider this code to be a more complex version of the figure...maybe save for another script? And here add just a single sensor + +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 x-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, information on the x-axis only). +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + geom_segment( + data = rfid_raw, + aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1), + color = "blue", + linewidth = 0.3 + ) + +``` + +In the code above, you used the argument `data` inside of `geom_segment()` to tell the function where to get data for drawing the line segments. + +In the argument `aes()`, which is a standard `ggplot2` function for encoding plot aesthetics, you specified that the data to plot on the x-axis are the timestamps for each detection, by providing the column name `timestamp_ms` to the inner argument `x`. You also specified what data should be plotted on the y-axis using the argument `y`. In this case, you can set `y` to any value that you want, since you aren't plotting data along this axis. + +The arguments `x` and `y` determine where the beginning of each line 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 `timestamp_ms` to the argument `xend`, you're indicating that you want the line segment to start and end on the same timestamp. On the y-axis, the number that you supply to `yend` will determine the height of each line. Since you're adding data from a single sensor here, the height of the line does not matter much. If you changed the code to read `yend = 0.1`, the plot would look very similar, but the y-axis would be scaled to have 0.1 as the maximum value instead of 1. + +The last two arguments inside of `geom_segment()` control the color of the line segments (`color`), as well as the width of ech line (`linewidth`). Specifying the width of each line is different than specifying where each line segment starts and ends. + +You can continue adding information to this plot by layering on the raw beam breaker detections: +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + geom_segment( + data = rfid_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 1), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + geom_segment( + data = irbb_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 1), + color = "orange", + linewidth = 0.3 + ) + +``` + +This plot has more information, but it's difficult to interpret. Some of the detections for each sensor type occurred so close in time that they're layered nearly directly over one another. You can change the position of the line segements on the y-axis in order to have better resolution for visualizng the datasets from both sensors. + +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + geom_segment( + data = rfid_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + geom_segment( + data = irbb_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + +``` + +This change to the start and end coordinates of how segments are drawn on the y-axis offset the two sets of line segments per sensor dataset. The plot is now less crowded and it's easier to visualize patterns over time and between datasets now. + +The plot is looking a lot better, but there's a problem with the x-axis. The x-axis currently holds two types of information: 1) the day of data collection, and 2) the time of day. It's difficult to see when detections were recorded on each day, and whether detections were recorded over similar or different time periods between days. To make the x-axis more interpretable, you can change the plot to be "faceted", so that you have 1 panel per day of data collection. +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + geom_segment( + data = rfid_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + geom_segment( + data = irbb_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Facet the plot by day (e.g. create a panel per day) + facet_wrap(~ day, nrow = 2, strip.position = "left") + +``` + +The overall plot structure has improved a lot, but are still some modifications that would help interpretability. 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. label each hour). + +You can start by modifying the data frame of raw data for each senor 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 2 days of data collection that need recoding. + +First you can practice using `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 day is the first day of the month), the value you want to add to the vector if the condition is true ("Day 1" of data collection), and then the value that you want to add if the condition is false ("Day 2" of data collection, since there are only 2 dates in each dataset). +```{r} + +ifelse(test = rfid_raw$day == 1, yes = "Day 1", no = "Day 2") + +``` + +Now you can create a new column per data frame using an `ifelse` statement. +```{r} + +# Create a new column in the raw data for the date of data collection +rfid_raw2 <- rfid_raw %>% + dplyr::mutate( + day_label = ifelse(day == 1, "Day 1", "Day 2") + ) + +# You should see that the new column "day_label" was created +# You'll see that a new year, month, and day were appended to the updated timestamps, but this is expected (see above) +glimpse(rfid_raw2) + +# Repeat this process for the beam breaker data +irbb_raw2 <- irbb_raw %>% + dplyr::mutate( + day_label = ifelse(day == 1, "Day 1", "Day 2") + ) + +glimpse(irbb_raw2) + +``` + +Now you can update the code to include the new date labels: +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = rfid_raw2, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = irbb_raw2, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 2, 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 timestamps column. The code to convert the timestamps to a different format is nested and repetitive, but the timestamp conversion will be performed correctly. When you prompt the R to convert the timestamps to hours, minutes, and seconds only, R adds a default year, month, and day beforehand (likely the current date that you rant the code). This is expected, and it is not an error, but rather makes it possible for the timestamps to align correctly over days in the plot (since R sees all timestamps occurring on a single day). +```{r} + +rfid_raw3 <- rfid_raw2 %>% + dplyr::mutate( + timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%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(rfid_raw3) + +# Repeat this process for the beam breaker data +irbb_raw3 <- irbb_raw2 %>% + dplyr::mutate( + timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%H:%M:%S"), format = "%H:%M:%S")) + ) + +glimpse(irbb_raw3) + +``` + +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: +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = rfid_raw3, + aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = irbb_raw3, + aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 2, strip.position = "left") + + + # Change the aesthetics of the x-axis labels + scale_x_datetime( + date_breaks = "30 mins", + date_labels = "%H:%M" + ) + +``` + +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 (since the height of each segment does not reflect data that you want to interpret). +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = rfid_raw3, + aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = irbb_raw3, + aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 2, strip.position = "left") + + + # Change the x and y axis labels + xlab("Time") + + + # Now you can add a y-axis label + ylab("Day of data collection") + + + # Change the aesthetics of the x-axis labels + scale_x_datetime( + date_breaks = "30 mins", + date_labels = "%H:%M" + ) + + + # 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 + theme( + axis.text.y = element_blank(), + axis.ticks.y = element_blank() + ) + +``` + +Finally, you need to add a legend to this plot so you can communicate what the two different colors mean. Adding a legend to this plot is tricky because you manually coded the colors of the line segments in two separate uses in `geom_segment()`. `ggplot2` functions have specific requirements for making a legend, and they can require automated coding of aesthetic values like color. + +In order to automatically create a legend in a plot, it helps to have a layer of the plot in which color is automatically encoded. The way that you would do this is to move the `color` argument inside of the function `aes()` for one layer of the plot, and then supply `color` with a column of data type `factor`...TKTK continue +```{r} + +all_sensors <- rfid_raw3 %>% + bind_rows( + irbb_raw3 + ) %>% + dplyr::mutate( + sensor_id = factor(sensor_id) + ) + +glimpse(all_sensors) + +``` + +Then make the plot. +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = rfid_raw3, + aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = irbb_raw3, + aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Add the fake layer of lines so that you can create a legend + # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below + geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + + + # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding + guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 2, strip.position = "left") + + + # Change the x and y axis labels + xlab("Time") + + + # Now you can add a y-axis label + ylab("Day of data collection") + + + # Change the aesthetics of the x-axis labels + scale_x_datetime( + date_breaks = "30 mins", + date_labels = "%H:%M" + ) + + + # 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" + ) + +``` + +You added a legend to the plot, but the colors in the legend don't line up with the colors of the line segments per sensor. You can use the function `scale_color_manual()` to match colors between the plot and legend. +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = rfid_raw3, + aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = irbb_raw3, + aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Add the fake layer of lines so that you can create a legend + # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below + geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + + + # Set the colors that will be used to color the lines in the legend itself + # The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column + # If you do not supply your own color values, then R will assign default colors to each factor level + scale_color_manual(values = c("blue", "orange")) + + + # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding + guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 2, strip.position = "left") + + + # Change the x and y axis labels + xlab("Time") + + + # Now you can add a y-axis label + ylab("Day of data collection") + + + # Change the aesthetics of the x-axis labels + scale_x_datetime( + date_breaks = "30 mins", + date_labels = "%H:%M" + ) + + + # 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" + ) + +``` + +Now that you've made the plot in R, you can save the plot as an image file on your computer using the function `ggsave()`. +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = rfid_raw3, + aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = irbb_raw3, + aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Add the fake layer of lines so that you can create a legend + # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below + geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + + + # Set the colors that will be used to color the lines in the legend itself + # The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column + # If you do not supply your own color values, then R will assign default colors to each factor level + scale_color_manual(values = c("blue", "orange")) + + + # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding + guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 2, strip.position = "left") + + + # Change the x and y axis labels + xlab("Time") + + + # Now you can add a y-axis label + ylab("Day of data collection") + + + # Change the aesthetics of the x-axis labels + scale_x_datetime( + date_breaks = "30 mins", + date_labels = "%H:%M" + ) + + + # 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" + ) + +# Save the image file to your computer +ggsave(file.path(path, "raw_detections.tiff"), width = 8, height = 6, units = "in", dpi = 300) + +``` + +You just learned how to make a plot in R, and also how to save it to your computer. 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. \ No newline at end of file From 61bc124d2cdb482f84b394c0fdc6d4c0802a0647 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Thu, 25 Jan 2024 13:14:26 -0500 Subject: [PATCH 20/69] Working on fifth vignette; added notes for next two vignettes --- R/vignettes/README.md | 6 +- ...tte_05_ProcessData_SimpleBarcodePlots.Rmd} | 74 ++- .../Vignette_06_ComplexBarcodePlots.Rmd | 577 ++++++++++++++++++ ...Rmd => Vignette_07_FinalAnalysesPlots.Rmd} | 10 +- 4 files changed, 639 insertions(+), 28 deletions(-) rename R/vignettes/{Vignette_06_VisualizeData.Rmd => Vignette_05_ProcessData_SimpleBarcodePlots.Rmd} (90%) create mode 100644 R/vignettes/Vignette_06_ComplexBarcodePlots.Rmd rename R/vignettes/{Vignette_05_ProcessData.Rmd => Vignette_07_FinalAnalysesPlots.Rmd} (98%) diff --git a/R/vignettes/README.md b/R/vignettes/README.md index 92bbe26..7027115 100644 --- a/R/vignettes/README.md +++ b/R/vignettes/README.md @@ -22,11 +22,11 @@ The goal of these tutorials is to disseminate basic R coding skills in a biologi 4. Save data and practice writing loops -5. Combine the raw data, detect perching events and pre-process the raw data, make plots (barcode style visualizations of the raw and per-processed data) +5. Combine the raw data, detect perching events and pre-process the raw data, make simple barcode plots of the raw and per-processed data, and highlight how to encode color in these plots -- For vignette 05, I want to make 3 barcode style visuals: 1) the raw combined RFID data, perching events and RFID pre-processed,2) IRBB raw combined and pre-processed, then 3) pre-processed RFID and IRBB together. All of these will be faceted plots with ggplot +6. For vignette 06, I want to make complex barcode style visuals: 1) the raw combined RFID data, perching events and RFID pre-processed,2) IRBB raw combined and pre-processed, then 3) pre-processed RFID and IRBB together. All of these will be faceted plots with ggplot, and with a complicated legend -6. Detect clusters, score clusters, generate summary statistics +7. Detect clusters, score clusters, generate summary statistics, make final barcode ot line plots Things that would be important to add: - Errors and troubleshooting online diff --git a/R/vignettes/Vignette_06_VisualizeData.Rmd b/R/vignettes/Vignette_05_ProcessData_SimpleBarcodePlots.Rmd similarity index 90% rename from R/vignettes/Vignette_06_VisualizeData.Rmd rename to R/vignettes/Vignette_05_ProcessData_SimpleBarcodePlots.Rmd index 45a3431..9663f32 100644 --- a/R/vignettes/Vignette_06_VisualizeData.Rmd +++ b/R/vignettes/Vignette_05_ProcessData_SimpleBarcodePlots.Rmd @@ -17,7 +17,7 @@ In this fifth vignette, you will begin using the simulated detections of animal 1. Accessing custom functions 2. Using custom functions -3. Plotting data with ggplot +3. Making line graphs with ggplot

Load packages and your working directory path

@@ -193,13 +193,13 @@ glimpse(irbb_pp) ``` -

Plot raw and pre-processed data

+

Plot RFID data in a barcode plot

-Now that you've combined the raw data per sensor, detected perching events in the raw data, and pre-processed the raw data, it's time to visualize these different datasets. Making visualizations in R is important for generating high-quality figures for publications and presentations, but it's also important for checking your work as you process and analyze data. +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 barcode style visualizations of the raw and processed detection datasets. +In the code below, you'll learn how to use functions from the `ggplot2` package to make barcode style visualizations of the raw and pre-processed RFID detection datasets. -Start by reading in the raw data for each sensor, and convert the timestamps to POSIX format. +Start by reading in the raw and pre-processed RFID data, and convert the timestamps to POSIX format. ```{r} rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) %>% @@ -211,17 +211,37 @@ rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFI # You should see that the timestamp_ms is in "dttm" format, which means that the POSIX conversion was done correctly glimpse(rfid_raw) -irbb_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_IRBB.csv")) %>% +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")) ) -glimpse(irbb_raw) +glimpse(rfid_pp) ``` -Next, start plotting this raw data using functions from `ggplot2`. This package is part of the `tidyverse`, but can also be installed and used separately. You can check out this [link](https://ggplot2.tidyverse.org/) for more resources to learn how to use ggplot 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. +Next, you can combine these datasets into a single data frame to facilitate combining them in the same plot. You will add a new column in order to identify the two different datasets. +```{r} + +rfid_combined <- rfid_raw %>% + dplyr::select(sensor_id, timestamp_ms) %>% + dplyr::mutate( + dataset = "raw" + ) %>% + bind_rows( + rfid_pp %>% + dplyr::select(sensor_id, timestamp_ms) %>% + dplyr::mutate( + dataset = "pre-processed" + ) + ) + +glimpse(rfid_combined) + +``` + +You will start plotting the data using functions from `ggplot2`. This package is part of the `tidyverse`, but can also be installed and used separately. You can check out this [link](https://ggplot2.tidyverse.org/) for more resources to learn how to use ggplot 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. Thde `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. @@ -232,36 +252,48 @@ ggplot() ``` -The plot will still remain blank even if you supply information about your data in order to set up plot aesthetics. +The plot will still remain blank even when you supply information about your data in order to set up plot aesthetics. ```{r} -ggplot(data = rfid_raw) +ggplot(data = rfid_combined) ``` -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 (see a plot in the next vignette). - +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 (see a plot in the next vignette). +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 x-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, information on the x-axis only). +```{r} +ggplot(data = rfid_combined) + + + # Add a vertical line for each timestamp + geom_segment( + aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), + linewidth = 0.3 + ) + +``` -TKTK the code below complicates adding a legend. Consider this code to be a more complex version of the figure...maybe save for another script? And here add just a single sensor +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 holding the dataset labels. The `color` argument must be inside of the `aes()` function in order for this color assignment by dataset to work correctly. -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 x-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, information on the x-axis only). +The line segment colors are automatically assigned by ggplot using default colors, but you can change these colors using the function `scale_color_manual()`: ```{r} -ggplot() + +ggplot(data = rfid_combined) + - # Add a vertical line for each RFID timestamp + # Add a vertical line for each timestamp geom_segment( - data = rfid_raw, - aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1), - color = "blue", + 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 code above, you used the argument `data` inside of `geom_segment()` to tell the function where to get data for drawing the line segments. +The TKTK + +TKTK if you want to switch the order of the colors and the legend keys...change the dataset column to a factor and reorder the levels... In the argument `aes()`, which is a standard `ggplot2` function for encoding plot aesthetics, you specified that the data to plot on the x-axis are the timestamps for each detection, by providing the column name `timestamp_ms` to the inner argument `x`. You also specified what data should be plotted on the y-axis using the argument `y`. In this case, you can set `y` to any value that you want, since you aren't plotting data along this axis. diff --git a/R/vignettes/Vignette_06_ComplexBarcodePlots.Rmd b/R/vignettes/Vignette_06_ComplexBarcodePlots.Rmd new file mode 100644 index 0000000..85e6d99 --- /dev/null +++ b/R/vignettes/Vignette_06_ComplexBarcodePlots.Rmd @@ -0,0 +1,577 @@ +--- +title: "Vignette 05: Process Data" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: html_document +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = TRUE) + +``` + +

Vignette Overview and Learning Objectives

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

Load packages and your working directory path

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

Plot raw and pre-processed data

+ +Now that you've combined the raw data per sensor, detected perching events in the raw data, and pre-processed the raw data, it's time to visualize these different datasets. Making visualizations in R is important for generating high-quality figures for publications and presentations, but it's also important for checking your work as you process and analyze data. + +In the code below, you'll learn how to use functions from the `ggplot2` package to make barcode style visualizations of the raw and processed detection datasets. + +Start by reading in the raw data for each sensor, and convert the timestamps to POSIX format. +```{r} + +rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) %>% + # The timestamps must be converted to POSIX format every time that the data is read back into R for plotting + dplyr::mutate( + timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) + ) + +# You should see that the timestamp_ms is in "dttm" format, which means that the POSIX conversion was done correctly +glimpse(rfid_raw) + +irbb_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_IRBB.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")) + ) + +glimpse(irbb_raw) + +``` + +Next, start plotting this raw data using functions from `ggplot2`. This package is part of the `tidyverse`, but can also be installed and used separately. You can check out this [link](https://ggplot2.tidyverse.org/) for more resources to learn how to use ggplot 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. + +Thde `ggplot2` package has a unique syntax for building plots, in which you start making a plot by calling the function `ggplot()`, and then add features by layering on other `ggplot2` functions with the `+` symbol. + +If you call `ggplot()`, you'll see that the function immediately draws a blank plot in your Plots pane in RStudio. +```{r} + +ggplot() + +``` + +The plot will still remain blank even if you supply information about your data in order to set up plot aesthetics. +```{r} + +ggplot(data = rfid_raw) + +``` + +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 (see a plot in the next vignette). + + + + +TKTK the code below complicates adding a legend. Consider this code to be a more complex version of the figure...maybe save for another script? And here add just a single sensor + +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 x-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, information on the x-axis only). +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + geom_segment( + data = rfid_raw, + aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1), + color = "blue", + linewidth = 0.3 + ) + +``` + +In the code above, you used the argument `data` inside of `geom_segment()` to tell the function where to get data for drawing the line segments. + +In the argument `aes()`, which is a standard `ggplot2` function for encoding plot aesthetics, you specified that the data to plot on the x-axis are the timestamps for each detection, by providing the column name `timestamp_ms` to the inner argument `x`. You also specified what data should be plotted on the y-axis using the argument `y`. In this case, you can set `y` to any value that you want, since you aren't plotting data along this axis. + +The arguments `x` and `y` determine where the beginning of each line 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 `timestamp_ms` to the argument `xend`, you're indicating that you want the line segment to start and end on the same timestamp. On the y-axis, the number that you supply to `yend` will determine the height of each line. Since you're adding data from a single sensor here, the height of the line does not matter much. If you changed the code to read `yend = 0.1`, the plot would look very similar, but the y-axis would be scaled to have 0.1 as the maximum value instead of 1. + +The last two arguments inside of `geom_segment()` control the color of the line segments (`color`), as well as the width of ech line (`linewidth`). Specifying the width of each line is different than specifying where each line segment starts and ends. + +You can continue adding information to this plot by layering on the raw beam breaker detections: +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + geom_segment( + data = rfid_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 1), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + geom_segment( + data = irbb_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 1), + color = "orange", + linewidth = 0.3 + ) + +``` + +This plot has more information, but it's difficult to interpret. Some of the detections for each sensor type occurred so close in time that they're layered nearly directly over one another. You can change the position of the line segements on the y-axis in order to have better resolution for visualizng the datasets from both sensors. + +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + geom_segment( + data = rfid_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + geom_segment( + data = irbb_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + +``` + +This change to the start and end coordinates of how segments are drawn on the y-axis offset the two sets of line segments per sensor dataset. The plot is now less crowded and it's easier to visualize patterns over time and between datasets now. + +The plot is looking a lot better, but there's a problem with the x-axis. The x-axis currently holds two types of information: 1) the day of data collection, and 2) the time of day. It's difficult to see when detections were recorded on each day, and whether detections were recorded over similar or different time periods between days. To make the x-axis more interpretable, you can change the plot to be "faceted", so that you have 1 panel per day of data collection. +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + geom_segment( + data = rfid_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + geom_segment( + data = irbb_raw, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Facet the plot by day (e.g. create a panel per day) + facet_wrap(~ day, nrow = 2, strip.position = "left") + +``` + +The overall plot structure has improved a lot, but are still some modifications that would help interpretability. 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. label each hour). + +You can start by modifying the data frame of raw data for each senor 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 2 days of data collection that need recoding. + +First you can practice using `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 day is the first day of the month), the value you want to add to the vector if the condition is true ("Day 1" of data collection), and then the value that you want to add if the condition is false ("Day 2" of data collection, since there are only 2 dates in each dataset). +```{r} + +ifelse(test = rfid_raw$day == 1, yes = "Day 1", no = "Day 2") + +``` + +Now you can create a new column per data frame using an `ifelse` statement. +```{r} + +# Create a new column in the raw data for the date of data collection +rfid_raw2 <- rfid_raw %>% + dplyr::mutate( + day_label = ifelse(day == 1, "Day 1", "Day 2") + ) + +# You should see that the new column "day_label" was created +# You'll see that a new year, month, and day were appended to the updated timestamps, but this is expected (see above) +glimpse(rfid_raw2) + +# Repeat this process for the beam breaker data +irbb_raw2 <- irbb_raw %>% + dplyr::mutate( + day_label = ifelse(day == 1, "Day 1", "Day 2") + ) + +glimpse(irbb_raw2) + +``` + +Now you can update the code to include the new date labels: +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = rfid_raw2, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = irbb_raw2, + aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 2, 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 timestamps column. The code to convert the timestamps to a different format is nested and repetitive, but the timestamp conversion will be performed correctly. When you prompt the R to convert the timestamps to hours, minutes, and seconds only, R adds a default year, month, and day beforehand (likely the current date that you rant the code). This is expected, and it is not an error, but rather makes it possible for the timestamps to align correctly over days in the plot (since R sees all timestamps occurring on a single day). +```{r} + +rfid_raw3 <- rfid_raw2 %>% + dplyr::mutate( + timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%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(rfid_raw3) + +# Repeat this process for the beam breaker data +irbb_raw3 <- irbb_raw2 %>% + dplyr::mutate( + timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%H:%M:%S"), format = "%H:%M:%S")) + ) + +glimpse(irbb_raw3) + +``` + +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: +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = rfid_raw3, + aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = irbb_raw3, + aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 2, strip.position = "left") + + + # Change the aesthetics of the x-axis labels + scale_x_datetime( + date_breaks = "30 mins", + date_labels = "%H:%M" + ) + +``` + +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 (since the height of each segment does not reflect data that you want to interpret). +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = rfid_raw3, + aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = irbb_raw3, + aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 2, strip.position = "left") + + + # Change the x and y axis labels + xlab("Time") + + + # Now you can add a y-axis label + ylab("Day of data collection") + + + # Change the aesthetics of the x-axis labels + scale_x_datetime( + date_breaks = "30 mins", + date_labels = "%H:%M" + ) + + + # 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 + theme( + axis.text.y = element_blank(), + axis.ticks.y = element_blank() + ) + +``` + +Finally, you need to add a legend to this plot so you can communicate what the two different colors mean. Adding a legend to this plot is tricky because you manually coded the colors of the line segments in two separate uses in `geom_segment()`. `ggplot2` functions have specific requirements for making a legend, and they can require automated coding of aesthetic values like color. + +In order to automatically create a legend in a plot, it helps to have a layer of the plot in which color is automatically encoded. The way that you would do this is to move the `color` argument inside of the function `aes()` for one layer of the plot, and then supply `color` with a column of data type `factor`...TKTK continue +```{r} + +all_sensors <- rfid_raw3 %>% + bind_rows( + irbb_raw3 + ) %>% + dplyr::mutate( + sensor_id = factor(sensor_id) + ) + +glimpse(all_sensors) + +``` + +Then make the plot. +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = rfid_raw3, + aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = irbb_raw3, + aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Add the fake layer of lines so that you can create a legend + # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below + geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + + + # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding + guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 2, strip.position = "left") + + + # Change the x and y axis labels + xlab("Time") + + + # Now you can add a y-axis label + ylab("Day of data collection") + + + # Change the aesthetics of the x-axis labels + scale_x_datetime( + date_breaks = "30 mins", + date_labels = "%H:%M" + ) + + + # 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" + ) + +``` + +You added a legend to the plot, but the colors in the legend don't line up with the colors of the line segments per sensor. You can use the function `scale_color_manual()` to match colors between the plot and legend. +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = rfid_raw3, + aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = irbb_raw3, + aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Add the fake layer of lines so that you can create a legend + # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below + geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + + + # Set the colors that will be used to color the lines in the legend itself + # The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column + # If you do not supply your own color values, then R will assign default colors to each factor level + scale_color_manual(values = c("blue", "orange")) + + + # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding + guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 2, strip.position = "left") + + + # Change the x and y axis labels + xlab("Time") + + + # Now you can add a y-axis label + ylab("Day of data collection") + + + # Change the aesthetics of the x-axis labels + scale_x_datetime( + date_breaks = "30 mins", + date_labels = "%H:%M" + ) + + + # 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" + ) + +``` + +Now that you've made the plot in R, you can save the plot as an image file on your computer using the function `ggsave()`. +```{r} + +ggplot() + + + # Add a vertical line for each RFID timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = rfid_raw3, + aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), + color = "blue", + linewidth = 0.3 + ) + + + # Add a vertical line for each beam breaker timestamp + # Use the new version of this data frame with the new day_label column + # Make sure to use the new timestamp column + geom_segment( + data = irbb_raw3, + aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), + color = "orange", + linewidth = 0.3 + ) + + + # Add the fake layer of lines so that you can create a legend + # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below + geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + + + # Set the colors that will be used to color the lines in the legend itself + # The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column + # If you do not supply your own color values, then R will assign default colors to each factor level + scale_color_manual(values = c("blue", "orange")) + + + # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding + guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 2, strip.position = "left") + + + # Change the x and y axis labels + xlab("Time") + + + # Now you can add a y-axis label + ylab("Day of data collection") + + + # Change the aesthetics of the x-axis labels + scale_x_datetime( + date_breaks = "30 mins", + date_labels = "%H:%M" + ) + + + # 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" + ) + +# Save the image file to your computer +ggsave(file.path(path, "raw_detections.tiff"), width = 8, height = 6, units = "in", dpi = 300) + +``` + +You just learned how to make a plot in R, and also how to save it to your computer. 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. \ No newline at end of file diff --git a/R/vignettes/Vignette_05_ProcessData.Rmd b/R/vignettes/Vignette_07_FinalAnalysesPlots.Rmd similarity index 98% rename from R/vignettes/Vignette_05_ProcessData.Rmd rename to R/vignettes/Vignette_07_FinalAnalysesPlots.Rmd index 45a3431..1dcc0e1 100644 --- a/R/vignettes/Vignette_05_ProcessData.Rmd +++ b/R/vignettes/Vignette_07_FinalAnalysesPlots.Rmd @@ -13,11 +13,13 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE)

Vignette Overview and Learning Objectives

-In this fifth vignette, you will begin using the simulated detections of animal movements in the ABISSMAL data processing and analysis workflow, including combining raw data across days and pre-processing the raw data. You will also make visualizations of the processed data. You will continue to use coding skills that you learned in the previous vignettes, and you will learn additional skills that include: +In this sixth vignette... You will also make visualizations of the processed data. You will continue to use coding skills that you learned in the previous vignettes, and you will learn additional skills that include: -1. Accessing custom functions -2. Using custom functions -3. Plotting data with ggplot +TKTK change code before plots to be about detecting and scoring clusters + +1. TKTK +2. TKTK +3. TKTK

Load packages and your working directory path

From 083b6b048d16b397f62dcffb5fc79fe9e9dc695a Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Mon, 29 Jan 2024 09:07:28 -0500 Subject: [PATCH 21/69] Finished draft of fifth vignette --- R/vignettes/.Rhistory | 854 +++++++++--------- R/vignettes/README.md | 4 +- .../Vignette_05_ProcessData_BuildPlots.Rmd | 480 ++++++++++ ...ette_05_ProcessData_SimpleBarcodePlots.Rmd | 769 ---------------- 4 files changed, 908 insertions(+), 1199 deletions(-) create mode 100644 R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd delete mode 100644 R/vignettes/Vignette_05_ProcessData_SimpleBarcodePlots.Rmd diff --git a/R/vignettes/.Rhistory b/R/vignettes/.Rhistory index f8286e9..f381551 100644 --- a/R/vignettes/.Rhistory +++ b/R/vignettes/.Rhistory @@ -1,97 +1,280 @@ -legend.position = "top" +"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){ +return(seq(starts[x], ends[x], 0.5)) +}, simplify = FALSE) +event_ts +test_that("The correct number and timing of discrete movement events are retained per pre-processing mode for RFID data and multiple temporal thresholds", { +# 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")) +} +# Create 4 clusters of detections: each cluster consists of a different number of detections spaced different numbers of seconds apart (testing `threshold`) +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 +ths <- seq(0.5, 5, by = 0.5) +invisible(lapply(1:length(ths), function(x){ +# Create the timestamps +tmp_tstmps <- data.table::rbindlist(lapply(1:length(starts), function(i){ +sim_ts <- seq(starts[i], ends[i], by = ths[x]) +return(data.frame(tstmps = sim_ts, cluster = i)) +})) +# Write out a spreadsheet with these timestamps that will be used as input data for the function +sim_ts <- data.frame(timestamp_ms = tmp_tstmps$tstmps) %>% +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" ) -ggplot() + -# Add a vertical line for each RFID timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column -geom_segment( -data = rfid_raw3, -aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), -color = "blue", -linewidth = 0.3 -) + -# Add a vertical line for each beam breaker timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column -geom_segment( -data = irbb_raw3, -aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), -color = "orange", -linewidth = 0.3 -) + -# Add the fake layer of lines so that you can create a legend -geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + -# Set the colors that will be used to color the lines in the legend itself -scale_color_manual(values = c("blue", "orange")) + -# Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding -guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + -# Facet the plot by day (e.g. create a panel per day) -# Use the new day labels here -facet_wrap(~ day_label, nrow = 2, strip.position = "left") + -# Change the x and y axis labels -xlab("Time") + -# Now you can add a y-axis label -ylab("Day of data collection") + -# Change the aesthetics of the x-axis labels -scale_x_datetime( -date_breaks = "30 mins", -date_labels = "%H:%M" -) + -# 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 -theme( -axis.text.y = element_blank(), -axis.ticks.y = element_blank(), -legend.position = "top" +write.csv(sim_ts, file.path(tmp_path, "raw_combined", "combined_raw_data_RFID.csv"), row.names = FALSE) +####### `retain_first` mode ####### +preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag_ID", pixel_col_nm = NULL, thin_threshold = ths[x], 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")) ) -ggplot() + -# Add a vertical line for each RFID timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column -geom_segment( -data = rfid_raw3, -aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), -color = "blue", -linewidth = 0.3 -) + -# Add a vertical line for each beam breaker timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column -geom_segment( -data = irbb_raw3, -aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), -color = "orange", -linewidth = 0.3 -) + -# Add the fake layer of lines so that you can create a legend -# The argument color here must be supplied with the name of a column that is the data type "factor" in R -geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + -# Set the colors that will be used to color the lines in the legend itself -# The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column -scale_color_manual(values = c("blue", "orange")) + -# Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding -guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + -# Facet the plot by day (e.g. create a panel per day) -# Use the new day labels here -facet_wrap(~ day_label, nrow = 2, strip.position = "left") + -# Change the x and y axis labels -xlab("Time") + -# Now you can add a y-axis label -ylab("Day of data collection") + -# Change the aesthetics of the x-axis labels -scale_x_datetime( -date_breaks = "30 mins", -date_labels = "%H:%M" -) + -# 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 -theme( -axis.text.y = element_blank(), -axis.ticks.y = element_blank(), -legend.position = "top" +# 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 >= ths[x])) +# 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(z){ +expect_equal(test_res$timestamp_ms[z], tmp_starts[z]) +})) +####### `thin` mode ####### +preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag_ID", pixel_col_nm = NULL, thin_threshold = ths[x], 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' +# cat("thin mode: th = ", ths[x], "\n") +num_seq <- seq(starts[1], ends[1], by = ths[x]) +expect_equal(nrow(test_res), length(starts) * length(seq(1, length(num_seq), 2))) +# Check that 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 >= ths[x])) +# Check that the every other timestamp from the raw data is returned as the timestamp per detection cluster +tmp_tstmps <- data.table::rbindlist(lapply(1:length(starts), function(i){ +sim_ts <- seq(starts[i], ends[i], by = ths[x]) +if(length(sim_ts) %% 2 != 0){ +sim_ts <- sim_ts[-seq(2, length(sim_ts) - 1, 2)] +} else if(length(sim_ts) %% 2 == 0){ +sim_ts <- sim_ts[-seq(2, length(sim_ts), 2)] +} +return(data.frame(tstmps = sim_ts, cluster = i)) +})) +tmp_tstmps$tstmps <- tmp_tstmps$tstmps[order(tmp_tstmps$tstmps)] +expect_equal(test_res$timestamp_ms, tmp_tstmps$tstmps) +})) +# Remove the temporary directory and all files within it +if(tmp_path == file.path(path, data_dir)){ +unlink(tmp_path, recursive = TRUE) +} +}) +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")) +} +# Create 4 clusters of detections: each cluster consists of a different number of detections spaced different numbers of seconds apart (testing `threshold`) +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 +ths <- seq(0.5, 5, by = 0.5) +ths +invisible(lapply(1:length(ths), function(x){ +# Create the timestamps +tmp_tstmps <- data.table::rbindlist(lapply(1:length(starts), function(i){ +sim_ts <- seq(starts[i], ends[i], by = ths[x]) +return(data.frame(tstmps = sim_ts, cluster = i)) +})) +# Write out a spreadsheet with these timestamps that will be used as input data for the function +sim_ts <- data.frame(timestamp_ms = tmp_tstmps$tstmps) %>% +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) +####### `retain_first` mode ####### +preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag_ID", pixel_col_nm = NULL, thin_threshold = ths[x], 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 >= ths[x])) +# 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(z){ +expect_equal(test_res$timestamp_ms[z], tmp_starts[z]) +})) +####### `thin` mode ####### +preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag_ID", pixel_col_nm = NULL, thin_threshold = ths[x], 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' +# cat("thin mode: th = ", ths[x], "\n") +num_seq <- seq(starts[1], ends[1], by = ths[x]) +expect_equal(nrow(test_res), length(starts) * length(seq(1, length(num_seq), 2))) +# Check that 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 >= ths[x])) +# Check that the every other timestamp from the raw data is returned as the timestamp per detection cluster +tmp_tstmps <- data.table::rbindlist(lapply(1:length(starts), function(i){ +sim_ts <- seq(starts[i], ends[i], by = ths[x]) +if(length(sim_ts) %% 2 != 0){ +sim_ts <- sim_ts[-seq(2, length(sim_ts) - 1, 2)] +} else if(length(sim_ts) %% 2 == 0){ +sim_ts <- sim_ts[-seq(2, length(sim_ts), 2)] +} +return(data.frame(tstmps = sim_ts, cluster = i)) +})) +tmp_tstmps$tstmps <- tmp_tstmps$tstmps[order(tmp_tstmps$tstmps)] +expect_equal(test_res$timestamp_ms, tmp_tstmps$tstmps) +})) +x <- 1 +# Create the timestamps +tmp_tstmps <- data.table::rbindlist(lapply(1:length(starts), function(i){ +sim_ts <- seq(starts[i], ends[i], by = ths[x]) +return(data.frame(tstmps = sim_ts, cluster = i)) +})) +tmp_tstmps +ths +x <- 4 +# Create the timestamps +tmp_tstmps <- data.table::rbindlist(lapply(1:length(starts), function(i){ +sim_ts <- seq(starts[i], ends[i], by = ths[x]) +return(data.frame(tstmps = sim_ts, cluster = i)) +})) +tmp_tstmps +# Write out a spreadsheet with these timestamps that will be used as input data for the function +sim_ts <- data.frame(timestamp_ms = tmp_tstmps$tstmps) %>% +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) +preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag_ID", pixel_col_nm = NULL, thin_threshold = ths[x], 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)) +nrow(test_res) +length(starts) +View(test_res) +# 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)] +diffs +expect_true(all(diffs >= ths[x])) +# Check that the first timestamp from the raw data is returned as the timestamp per detection cluster +tmp_starts <- starts[order(starts)] +tmp_starts +invisible(lapply(1:nrow(test_res), function(z){ +expect_equal(test_res$timestamp_ms[z], tmp_starts[z]) +})) +####### `thin` mode ####### +preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag_ID", pixel_col_nm = NULL, thin_threshold = ths[x], 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")) +) +View(test_res) +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")) +) %>% +dplyr::arrange(-desc(timestamp_ms)) 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 @@ -105,408 +288,225 @@ source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_perching_events. 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") -list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") -rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) -glimpse(rfid_data) -rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) -glimpse(rfid_pp) -irbb_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_IRBB.csv")) -glimpse(irbb_pp) 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")) -) +) %>% +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) -irbb_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_IRBB.csv")) %>% +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")) -) -glimpse(irbb_raw) -ggplot() + -# Add a vertical line for each RFID timestamp -geom_segment( -data = rfid_raw, -aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1), -color = "blue", -linewidth = 0.3 -) -ggplot() + -# Add a vertical line for each RFID timestamp -geom_segment( -data = rfid_raw, -aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 1), -color = "blue", -linewidth = 0.3 -) + -# Add a vertical line for each beam breaker timestamp -geom_segment( -data = irbb_raw, -aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 1), -color = "orange", -linewidth = 0.3 -) -ggplot() + -# Add a vertical line for each RFID timestamp -geom_segment( -data = rfid_raw, -aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), -color = "blue", -linewidth = 0.3 -) + -# Add a vertical line for each beam breaker timestamp -geom_segment( -data = irbb_raw, -aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), -color = "orange", -linewidth = 0.3 -) -ggplot() + -# Add a vertical line for each RFID timestamp -geom_segment( -data = rfid_raw, -aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), -color = "blue", -linewidth = 0.3 +) %>% +dplyr::arrange(-desc(timestamp_ms)) +glimpse(rfid_pp) +View(cbind(rfid_raw$timestamp_ms, rfid_pp$timestamp_ms)) +View(data.frame(rfid_raw$timestamp_ms, rfid_pp$timestamp_ms)) +rfid_raw$timestamp_ms +View(data.frame(rfid_raw$timestamp_ms[1:20], rfid_pp$timestamp_ms[1;20])) +View(data.frame(rfid_raw$timestamp_ms[1:20], rfid_pp$timestamp_ms[1:20])) +View(data.frame(rfid_raw$timestamp_ms[1:25], rfid_pp$timestamp_ms[1:25])) +ggplot(data = rfid_combined %>% +group_by(dataset) %>% +slice(1:5) %>% +ungroup() ) + -# Add a vertical line for each beam breaker timestamp +# Add a vertical line for each timestamp geom_segment( -data = irbb_raw, -aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), -color = "orange", +aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 ) + -# Facet the plot by day (e.g. create a panel per day) -facet_wrap(~ day, nrow = 2, strip.position = "left") -ifelse(test = rfid_raw$day == 1, yes = "Day 1", no = "Day 2") -# Create a new column in the raw data for the date of data collection -rfid_raw2 <- rfid_raw %>% +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 = "right") +rfid_combined <- rfid_raw %>% +dplyr::select(sensor_id, day, timestamp_ms) %>% dplyr::mutate( -day_label = ifelse(day == 1, "Day 1", "Day 2") +dataset = "raw" +) %>% +bind_rows( +rfid_pp %>% +dplyr::select(sensor_id, day, timestamp_ms) %>% +dplyr::mutate( +dataset = "pre-processed" ) -# You should see that the new column "day_label" was created -# You'll see that a new year, month, and day were appended to the updated timestamps, but this is expected (see above) -glimpse(rfid_raw2) -# Repeat this process for the beam breaker data -irbb_raw2 <- irbb_raw %>% +) +glimpse(rfid_combined) +# 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( -day_label = ifelse(day == 1, "Day 1", "Day 2") +dataset = factor(dataset, levels = c("raw", "pre-processed")) ) -glimpse(irbb_raw2) -ggplot() + -# Add a vertical line for each RFID timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column -geom_segment( -data = rfid_raw2, -aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), -color = "blue", -linewidth = 0.3 +# The dataset column is now type "fct", or "factor" +glimpse(rfid_combined) +# The levels of the factor column are ordered with "raw" first, rather than in alphabetical order +levels(rfid_combined$dataset) +ggplot(data = rfid_combined %>% +group_by(dataset) %>% +slice(1:5) %>% +ungroup() ) + -# Add a vertical line for each beam breaker timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column +# Add a vertical line for each timestamp geom_segment( -data = irbb_raw2, -aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), -color = "orange", +aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 ) + -# Facet the plot by day (e.g. create a panel per day) -# Use the new day labels here -facet_wrap(~ day_label, nrow = 2, strip.position = "left") -rfid_raw3 <- rfid_raw2 %>% -dplyr::mutate( -timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%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(rfid_raw3) -# Repeat this process for the beam breaker data -irbb_raw3 <- irbb_raw2 %>% -dplyr::mutate( -timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%H:%M:%S"), format = "%H:%M:%S")) -) -glimpse(irbb_raw3) -ggplot() + -# Add a vertical line for each RFID timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column +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 = "right") +ggplot(data = rfid_combined %>% +group_by(dataset) %>% +slice(1:3) %>% +ungroup() +) + +# Add a vertical line for each timestamp geom_segment( -data = rfid_raw3, -aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), -color = "blue", +aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 ) + -# Add a vertical line for each beam breaker timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column +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 = "right") +ggplot(data = rfid_combined %>% +group_by(dataset) %>% +slice(1:4) %>% +ungroup() +) + +# Add a vertical line for each timestamp geom_segment( -data = irbb_raw3, -aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), -color = "orange", +aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 ) + -# Facet the plot by day (e.g. create a panel per day) -# Use the new day labels here -facet_wrap(~ day_label, nrow = 2, strip.position = "left") + -# Change the aesthetics of the x-axis labels -scale_x_datetime( -date_breaks = "30 mins", -date_labels = "%H:%M" -) -ggplot() + -# Add a vertical line for each RFID timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column +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 = "right") +ggplot(data = rfid_combined %>% +group_by(dataset) %>% +slice(1:3) %>% +ungroup() +) + +# Add a vertical line for each timestamp geom_segment( -data = rfid_raw3, -aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), -color = "blue", +aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 ) + -# Add a vertical line for each beam breaker timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column +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 = "right") +ggplot(data = rfid_combined) + +# Add a vertical line for each timestamp geom_segment( -data = irbb_raw3, -aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), -color = "orange", +aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 ) + -# Facet the plot by day (e.g. create a panel per day) -# Use the new day labels here -facet_wrap(~ day_label, nrow = 2, strip.position = "left") + -# Change the x and y axis labels -xlab("Time") + -# Now you can add a y-axis label -ylab("Day of data collection") + -# Change the aesthetics of the x-axis labels -scale_x_datetime( -date_breaks = "30 mins", -date_labels = "%H:%M" -) + -# 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 -theme( -axis.text.y = element_blank(), -axis.ticks.y = element_blank() -) -all_sensors <- rfid_raw3 %>% -bind_rows( -irbb_raw3 +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 = "right") +rfid_perch <- read.csv(file.path(path, "Data/processed/perching_events_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)) +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( -sensor_id = factor(sensor_id) -) -glimpse(all_sensors) -ggplot() + -# Add a vertical line for each RFID timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column +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) +ggplot(data = rfid_combined) + +# Add a vertical line for each timestamp geom_segment( -data = rfid_raw3, -aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), -color = "blue", +aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 ) + -# Add a vertical line for each beam breaker timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column +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") +ggplot(data = rfid_combined) + +# Add a vertical line for each timestamp geom_segment( -data = irbb_raw3, -aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), -color = "orange", +aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 ) + -# Add the fake layer of lines so that you can create a legend -# The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below -geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + -# Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding -guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + -# Facet the plot by day (e.g. create a panel per day) -# Use the new day labels here -facet_wrap(~ day_label, nrow = 2, strip.position = "left") + -# Change the x and y axis labels -xlab("Time") + -# Now you can add a y-axis label -ylab("Day of data collection") + -# Change the aesthetics of the x-axis labels -scale_x_datetime( -date_breaks = "30 mins", -date_labels = "%H:%M" -) + -# 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" -) -ggplot() + -# Add a vertical line for each RFID timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column +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_raw3, -aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), +data = rfid_perch, +aes(x = perching_start, xend = perching_end, y = 0, yend = 1), color = "blue", linewidth = 0.3 -) + -# Add a vertical line for each beam breaker timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column -geom_segment( -data = irbb_raw3, -aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), -color = "orange", -linewidth = 0.3 -) + -# Add the fake layer of lines so that you can create a legend -# The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below -geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + -# Set the colors that will be used to color the lines in the legend itself -# The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column -# If you do not supply your own color values, then R will assign default colors to each factor level -scale_color_manual(values = c("blue", "orange")) + -# Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding -guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + -# Facet the plot by day (e.g. create a panel per day) -# Use the new day labels here -facet_wrap(~ day_label, nrow = 2, strip.position = "left") + -# Change the x and y axis labels -xlab("Time") + -# Now you can add a y-axis label -ylab("Day of data collection") + -# Change the aesthetics of the x-axis labels -scale_x_datetime( -date_breaks = "30 mins", -date_labels = "%H:%M" -) + -# 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" ) -ggplot() + -# Add a vertical line for each RFID timestamp +ggplot(data = rfid_combined) + +# Add a vertical line for each timestamp geom_segment( -data = raw_rfid, -aes(x = timestamp, y = 0, xend = timestamp, yend = y_max/4), -color = cols[4], +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 = raw_all2 %>% -dplyr::filter(sensor_id == "Inner Beam Breaker"), -aes(x = timestamp, xend = timestamp, y = y_max/4, yend = (y_max/4) * 2), -color = cols[3], +data = rfid_perch, +aes(x = perching_start, xend = perching_end, y = 1, yend = 1.5), +color = "blue", linewidth = 0.3 -) + +) +ggplot(data = rfid_combined) + +# Add a vertical line for each timestamp geom_segment( -data = raw_all2 %>% -dplyr::filter(sensor_id == "RFID"), -aes(x = timestamp, xend = timestamp, y = (y_max/4) * 2, yend = (y_max/4) * 3), -color = cols[2], +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 = raw_all2 %>% -dplyr::filter(sensor_id == "Outer Beam Breaker"), -aes(x = timestamp, xend = timestamp, y = (y_max/4) * 3, yend = y_max), -color = cols[1], +data = rfid_perch, +aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5), +color = "blue", linewidth = 0.3 -) + -# I removed switch = "y" because it's possible to rotate strip lables horizontally when they're on the right -facet_grid(rows = vars(new_date)) + -# Add shaded rectangles for the nocturnal periods -geom_rect(data = rect_df2, aes(xmin = time_xmin, xmax = time_xmax, ymin = -Inf, ymax = Inf), color = alpha("black", 0), fill = alpha("black", 0.08), inherit.aes = FALSE) + -scale_x_datetime( -breaks = seq(x_lims[1], x_lims[2], "1 hour"), -date_labels = "%H:%M", -expand = c(0, 0), -limits = x_lims -) + -scale_y_continuous(limits = c(0, y_max)) + -guides(color = "none") + -theme_bw() + -ylab("") + -xlab("") + -theme( -axis.ticks.y = element_blank(), -axis.text.y = element_blank(), -panel.grid.major = element_blank(), -panel.grid.minor = element_blank(), -axis.text.x = element_text(size = 7, angle = 90, vjust = 0.5, hjust = 0.5, color = "black"), -strip.text.y = element_text(size = 7, face = "bold", angle = 0), -strip.background = element_rect(fill = "white"), -legend.position = "top", -axis.ticks = element_line(linewidth = 0.25) ) -# Save the image file to your computer -ggsave(file.path(path, "raw_detections.tiff"), width = 4, height = 3, units = "inches", res = 300) -# Save the image file to your computer -ggsave(file.path(path, "raw_detections.tiff"), width = 4, height = 3, units = "in", res = 300) -# Save the image file to your computer -ggsave(file.path(path, "raw_detections.tiff"), width = 4, height = 3, units = "in", dpi = 300) -ggplot() + -# Add a vertical line for each RFID timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column +gg <- ggplot(data = rfid_combined) + +# Add a vertical line for each timestamp geom_segment( -data = rfid_raw3, -aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), -color = "blue", +aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 ) + -# Add a vertical line for each beam breaker timestamp -# Use the new version of this data frame with the new day_label column -# Make sure to use the new timestamp column +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 = irbb_raw3, -aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), -color = "orange", +data = rfid_perch, +aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5), +color = "blue", linewidth = 0.3 ) + -# Add the fake layer of lines so that you can create a legend -# The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below -geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + -# Set the colors that will be used to color the lines in the legend itself -# The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column -# If you do not supply your own color values, then R will assign default colors to each factor level -scale_color_manual(values = c("blue", "orange")) + -# Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding -guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + -# Facet the plot by day (e.g. create a panel per day) -# Use the new day labels here -facet_wrap(~ day_label, nrow = 2, strip.position = "left") + +theme( +legend.position = "top" +) +gg +gg <- gg + # Change the x and y axis labels -xlab("Time") + -# Now you can add a y-axis label -ylab("Day of data collection") + -# Change the aesthetics of the x-axis labels -scale_x_datetime( -date_breaks = "30 mins", -date_labels = "%H:%M" -) + +xlab("Date and time") + +# The y-axis does not contain information right now, so it 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 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" +axis.ticks.y = element_blank() ) -# Save the image file to your computer -ggsave(file.path(path, "raw_detections.tiff"), width = 4, height = 3, units = "in", dpi = 300) -# Save the image file to your computer -ggsave(file.path(path, "raw_detections.tiff"), width = 8, height = 6, units = "in", dpi = 300) +gg diff --git a/R/vignettes/README.md b/R/vignettes/README.md index 7027115..9b21729 100644 --- a/R/vignettes/README.md +++ b/R/vignettes/README.md @@ -24,9 +24,7 @@ The goal of these tutorials is to disseminate basic R coding skills in a biologi 5. Combine the raw data, detect perching events and pre-process the raw data, make simple barcode plots of the raw and per-processed data, and highlight how to encode color in these plots -6. For vignette 06, I want to make complex barcode style visuals: 1) the raw combined RFID data, perching events and RFID pre-processed,2) IRBB raw combined and pre-processed, then 3) pre-processed RFID and IRBB together. All of these will be faceted plots with ggplot, and with a complicated legend - -7. Detect clusters, score clusters, generate summary statistics, make final barcode ot line plots +6. For vignette 06, I want to detect clusters, score clusters, generate summary statistics, and make complex barcode style visuals: 1) the raw combined RFID data, perching events and RFID pre-processed,2) IRBB raw combined and pre-processed, then 3) pre-processed RFID and IRBB together. All of these will be faceted plots with ggplot, and with a complicated legend Things that would be important to add: - Errors and troubleshooting online diff --git a/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd b/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd new file mode 100644 index 0000000..03fb8f3 --- /dev/null +++ b/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd @@ -0,0 +1,480 @@ +--- +title: "Vignette 05: Process and Plot Data" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: html_document +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = TRUE) + +``` + +

Vignette Overview and Learning Objectives

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

Load packages and your working directory path

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

Load ABISSMAL functions

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

Access ABISSMAL function information

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

Combine raw data

+ +Once you've loaded the ABISSMAL functions, you can start using the first function, `combine_raw_data()`, to combine data collected across days per sensor into a single spreadsheet per sensor. You'll start by combining raw data for the RFID sensor collected over different days into a single spreadsheet. + +Here goes more information about the arguments you are supplying to the function below: + +* `sensors` is a vector containing the labels of the sensors for which you want to combine raw data. Below you're specifying RFID as a single sensor + +* `path` is your general working directory + +* `data_dir` is the folder that holds data inside of your working directory + +* `out_dir` is the folder where you want to save the combined raw data spreadsheet. The function will create this folder if it does not already exist + +* `tz` is the timezone for converting timestamps to POSIXct format. The default is "America/New York", and you can check out the "Time zones" section in the documentation for DateTimeClasses in R for more information (`?DateTimeClasses`) + +* `POSIXct_format` is a string containing the POSIX formatting information for how dates and timestamps should be combined into a single column. The default is to encode the year as a 4 digit number and the month and day as 2 digit numbers, separated by dashes. The date is followed by a space, then the 2-digit hour, minute, and decimal second (separated by colons) +```{r} + +combine_raw_data(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 single spreadsheet of raw RFID data to the new directory `raw_combined`: +```{r} + +list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") + +``` + +You can read this combined .csv file (called "combined_raw_data_RFID.csv") back into R to check out the structure of this spreadsheet: +```{r} + +rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) + +glimpse(rfid_data) + +``` + +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 hace a unique identifier in `sensor_id`). + +The function created a new timestamps column in POSIXct format for downsteam processing and analysis, but it kept the original timestamps column. The function also 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 not removed 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`: +```{r} + +combine_raw_data(sensors = c("RFID", "IRBB"), path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +``` + +The RFID data will be overwritten, and you should see an additional spreadsheet with the raw IRBB data: +```{r} + +list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") + +``` + +

Detect perching events

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

Pre-process raw data

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

Plot RFID data in a barcode plot

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

Vignette Overview and Learning Objectives

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

Load packages and your working directory path

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

Load ABISSMAL functions

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

Access ABISSMAL function information

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

Combine raw data

- -Once you've loaded the ABISSMAL functions, you can start using the first function, `combine_raw_data()`, to combine data collected across days per sensor into a single spreadsheet per sensor. You'll start by combining raw data for the RFID sensor collected over different days into a single spreadsheet. - -Here goes more information about the arguments you are supplying to the function below: - -* `sensors` is a vector containing the labels of the sensors for which you want to combine raw data. Below you're specifying RFID as a single sensor - -* `path` is your general working directory - -* `data_dir` is the folder that holds data inside of your working directory - -* `out_dir` is the folder where you want to save the combined raw data spreadsheet. The function will create this folder if it does not already exist - -* `tz` is the timezone for converting timestamps to POSIXct format. The default is "America/New York", and you can check out the "Time zones" section in the documentation for DateTimeClasses in R for more information (`?DateTimeClasses`) - -* `POSIXct_format` is a string containing the POSIX formatting information for how dates and timestamps should be combined into a single column. The default is to encode the year as a 4 digit number and the month and day as 2 digit numbers, separated by dashes. The date is followed by a space, then the 2-digit hour, minute, and decimal second (separated by colons) -```{r} - -combine_raw_data(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 single spreadsheet of raw RFID data to the new directory `raw_combined`: -```{r} - -list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") - -``` - -You can read this combined .csv file (called "combined_raw_data_RFID.csv") back into R to check out the structure of this spreadsheet: -```{r} - -rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) - -glimpse(rfid_data) - -``` - -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 hace a unique identifier in `sensor_id`). - -The function created a new timestamps column in POSIXct format for downsteam processing and analysis, but it kept the original timestamps column. The function also 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 not removed 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`: -```{r} - -combine_raw_data(sensors = c("RFID", "IRBB"), path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") - -``` - -The RFID data will be overwritten, and you should see an additional spreadsheet with the raw IRBB data: -```{r} - -list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") - -``` - -

Detect perching events

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

Pre-process raw data

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

Plot RFID data in a barcode plot

- -Now that you've combined and processed the raw data per sensor, it's time to visualize these different datasets. Making visualizations as you write code is important for generating high-quality figures for publications and presentations, but also for checking your work. - -In the code below, you'll learn how to use functions from the `ggplot2` package to make barcode style visualizations of the raw and pre-processed RFID detection datasets. - -Start by reading in the raw and pre-processed RFID data, and convert the timestamps to POSIX format. -```{r} - -rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) %>% - # The timestamps must be converted to POSIX format every time that the data is read back into R for plotting - dplyr::mutate( - timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) - ) - -# You should see that the timestamp_ms is in "dttm" format, which means that the POSIX conversion was done correctly -glimpse(rfid_raw) - -rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) %>% - # The timestamps must be converted to POSIX format every time that the data is read back into R for plotting - dplyr::mutate( - timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) - ) - -glimpse(rfid_pp) - -``` - -Next, you can combine these datasets into a single data frame to facilitate combining them in the same plot. You will add a new column in order to identify the two different datasets. -```{r} - -rfid_combined <- rfid_raw %>% - dplyr::select(sensor_id, timestamp_ms) %>% - dplyr::mutate( - dataset = "raw" - ) %>% - bind_rows( - rfid_pp %>% - dplyr::select(sensor_id, timestamp_ms) %>% - dplyr::mutate( - dataset = "pre-processed" - ) - ) - -glimpse(rfid_combined) - -``` - -You will start plotting the data using functions from `ggplot2`. This package is part of the `tidyverse`, but can also be installed and used separately. You can check out this [link](https://ggplot2.tidyverse.org/) for more resources to learn how to use ggplot 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. - -Thde `ggplot2` package has a unique syntax for building plots, in which you start making a plot by calling the function `ggplot()`, and then add features by layering on other `ggplot2` functions with the `+` symbol. - -If you call `ggplot()`, you'll see that the function immediately draws a blank plot in your Plots pane in RStudio. -```{r} - -ggplot() - -``` - -The plot will still remain blank even when you supply information about your data in order to set up plot aesthetics. -```{r} - -ggplot(data = rfid_combined) - -``` - -You need to add other aesthetics functions to this base layer of the plot in order to see your data. The functions that you use to layer aesthetics over the empty plot will depend on the type of plot that you want to make. For this example, you will make a barcode style plot, in which each timestamp is shown as a thin vertical line. Barcode plots can be useful visualizations when you're working with timestamps, since the most important information is contained in one dimension (time on the x-axis). If you were to summarize the number of timestamps recorded on each day, then you could create a line plot instead (see a plot in the next vignette). - -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 x-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, information on the x-axis only). -```{r} - -ggplot(data = rfid_combined) + - - # Add a vertical line for each timestamp - geom_segment( - aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), - linewidth = 0.3 - ) - -``` - -In the code above, `geom_segment()` adds a vertical line segment to the plot for each detection in the full dataset. Using the argument `color` inside of `geom_segment()`, you told the function that these line segments should be colored by dataset by supplying the column name holding the dataset labels. The `color` argument must be inside of the `aes()` function in order for this color assignment by dataset to work correctly. - -The line segment colors are automatically assigned by ggplot using default colors, but you can change these colors using the function `scale_color_manual()`: -```{r} - -ggplot(data = rfid_combined) + - - # Add a vertical line for each timestamp - geom_segment( - aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), - linewidth = 0.3 - ) + - - scale_color_manual(values = c("orange", "darkgreen")) - -``` - -The TKTK - -TKTK if you want to switch the order of the colors and the legend keys...change the dataset column to a factor and reorder the levels... - -In the argument `aes()`, which is a standard `ggplot2` function for encoding plot aesthetics, you specified that the data to plot on the x-axis are the timestamps for each detection, by providing the column name `timestamp_ms` to the inner argument `x`. You also specified what data should be plotted on the y-axis using the argument `y`. In this case, you can set `y` to any value that you want, since you aren't plotting data along this axis. - -The arguments `x` and `y` determine where the beginning of each line 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 `timestamp_ms` to the argument `xend`, you're indicating that you want the line segment to start and end on the same timestamp. On the y-axis, the number that you supply to `yend` will determine the height of each line. Since you're adding data from a single sensor here, the height of the line does not matter much. If you changed the code to read `yend = 0.1`, the plot would look very similar, but the y-axis would be scaled to have 0.1 as the maximum value instead of 1. - -The last two arguments inside of `geom_segment()` control the color of the line segments (`color`), as well as the width of ech line (`linewidth`). Specifying the width of each line is different than specifying where each line segment starts and ends. - -You can continue adding information to this plot by layering on the raw beam breaker detections: -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - geom_segment( - data = rfid_raw, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 1), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - geom_segment( - data = irbb_raw, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 1), - color = "orange", - linewidth = 0.3 - ) - -``` - -This plot has more information, but it's difficult to interpret. Some of the detections for each sensor type occurred so close in time that they're layered nearly directly over one another. You can change the position of the line segements on the y-axis in order to have better resolution for visualizng the datasets from both sensors. - -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - geom_segment( - data = rfid_raw, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - geom_segment( - data = irbb_raw, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) - -``` - -This change to the start and end coordinates of how segments are drawn on the y-axis offset the two sets of line segments per sensor dataset. The plot is now less crowded and it's easier to visualize patterns over time and between datasets now. - -The plot is looking a lot better, but there's a problem with the x-axis. The x-axis currently holds two types of information: 1) the day of data collection, and 2) the time of day. It's difficult to see when detections were recorded on each day, and whether detections were recorded over similar or different time periods between days. To make the x-axis more interpretable, you can change the plot to be "faceted", so that you have 1 panel per day of data collection. -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - geom_segment( - data = rfid_raw, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - geom_segment( - data = irbb_raw, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) + - - # Facet the plot by day (e.g. create a panel per day) - facet_wrap(~ day, nrow = 2, strip.position = "left") - -``` - -The overall plot structure has improved a lot, but are still some modifications that would help interpretability. 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. label each hour). - -You can start by modifying the data frame of raw data for each senor 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 2 days of data collection that need recoding. - -First you can practice using `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 day is the first day of the month), the value you want to add to the vector if the condition is true ("Day 1" of data collection), and then the value that you want to add if the condition is false ("Day 2" of data collection, since there are only 2 dates in each dataset). -```{r} - -ifelse(test = rfid_raw$day == 1, yes = "Day 1", no = "Day 2") - -``` - -Now you can create a new column per data frame using an `ifelse` statement. -```{r} - -# Create a new column in the raw data for the date of data collection -rfid_raw2 <- rfid_raw %>% - dplyr::mutate( - day_label = ifelse(day == 1, "Day 1", "Day 2") - ) - -# You should see that the new column "day_label" was created -# You'll see that a new year, month, and day were appended to the updated timestamps, but this is expected (see above) -glimpse(rfid_raw2) - -# Repeat this process for the beam breaker data -irbb_raw2 <- irbb_raw %>% - dplyr::mutate( - day_label = ifelse(day == 1, "Day 1", "Day 2") - ) - -glimpse(irbb_raw2) - -``` - -Now you can update the code to include the new date labels: -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = rfid_raw2, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = irbb_raw2, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) + - - # Facet the plot by day (e.g. create a panel per day) - # Use the new day labels here - facet_wrap(~ day_label, nrow = 2, 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 timestamps column. The code to convert the timestamps to a different format is nested and repetitive, but the timestamp conversion will be performed correctly. When you prompt the R to convert the timestamps to hours, minutes, and seconds only, R adds a default year, month, and day beforehand (likely the current date that you rant the code). This is expected, and it is not an error, but rather makes it possible for the timestamps to align correctly over days in the plot (since R sees all timestamps occurring on a single day). -```{r} - -rfid_raw3 <- rfid_raw2 %>% - dplyr::mutate( - timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%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(rfid_raw3) - -# Repeat this process for the beam breaker data -irbb_raw3 <- irbb_raw2 %>% - dplyr::mutate( - timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%H:%M:%S"), format = "%H:%M:%S")) - ) - -glimpse(irbb_raw3) - -``` - -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: -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = rfid_raw3, - aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = irbb_raw3, - aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) + - - # Facet the plot by day (e.g. create a panel per day) - # Use the new day labels here - facet_wrap(~ day_label, nrow = 2, strip.position = "left") + - - # Change the aesthetics of the x-axis labels - scale_x_datetime( - date_breaks = "30 mins", - date_labels = "%H:%M" - ) - -``` - -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 (since the height of each segment does not reflect data that you want to interpret). -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = rfid_raw3, - aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = irbb_raw3, - aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) + - - # Facet the plot by day (e.g. create a panel per day) - # Use the new day labels here - facet_wrap(~ day_label, nrow = 2, strip.position = "left") + - - # Change the x and y axis labels - xlab("Time") + - - # Now you can add a y-axis label - ylab("Day of data collection") + - - # Change the aesthetics of the x-axis labels - scale_x_datetime( - date_breaks = "30 mins", - date_labels = "%H:%M" - ) + - - # 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 - theme( - axis.text.y = element_blank(), - axis.ticks.y = element_blank() - ) - -``` - -Finally, you need to add a legend to this plot so you can communicate what the two different colors mean. Adding a legend to this plot is tricky because you manually coded the colors of the line segments in two separate uses in `geom_segment()`. `ggplot2` functions have specific requirements for making a legend, and they can require automated coding of aesthetic values like color. - -In order to automatically create a legend in a plot, it helps to have a layer of the plot in which color is automatically encoded. The way that you would do this is to move the `color` argument inside of the function `aes()` for one layer of the plot, and then supply `color` with a column of data type `factor`...TKTK continue -```{r} - -all_sensors <- rfid_raw3 %>% - bind_rows( - irbb_raw3 - ) %>% - dplyr::mutate( - sensor_id = factor(sensor_id) - ) - -glimpse(all_sensors) - -``` - -Then make the plot. -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = rfid_raw3, - aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = irbb_raw3, - aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) + - - # Add the fake layer of lines so that you can create a legend - # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below - geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + - - # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding - guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + - - # Facet the plot by day (e.g. create a panel per day) - # Use the new day labels here - facet_wrap(~ day_label, nrow = 2, strip.position = "left") + - - # Change the x and y axis labels - xlab("Time") + - - # Now you can add a y-axis label - ylab("Day of data collection") + - - # Change the aesthetics of the x-axis labels - scale_x_datetime( - date_breaks = "30 mins", - date_labels = "%H:%M" - ) + - - # 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" - ) - -``` - -You added a legend to the plot, but the colors in the legend don't line up with the colors of the line segments per sensor. You can use the function `scale_color_manual()` to match colors between the plot and legend. -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = rfid_raw3, - aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = irbb_raw3, - aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) + - - # Add the fake layer of lines so that you can create a legend - # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below - geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + - - # Set the colors that will be used to color the lines in the legend itself - # The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column - # If you do not supply your own color values, then R will assign default colors to each factor level - scale_color_manual(values = c("blue", "orange")) + - - # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding - guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + - - # Facet the plot by day (e.g. create a panel per day) - # Use the new day labels here - facet_wrap(~ day_label, nrow = 2, strip.position = "left") + - - # Change the x and y axis labels - xlab("Time") + - - # Now you can add a y-axis label - ylab("Day of data collection") + - - # Change the aesthetics of the x-axis labels - scale_x_datetime( - date_breaks = "30 mins", - date_labels = "%H:%M" - ) + - - # 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" - ) - -``` - -Now that you've made the plot in R, you can save the plot as an image file on your computer using the function `ggsave()`. -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = rfid_raw3, - aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = irbb_raw3, - aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) + - - # Add the fake layer of lines so that you can create a legend - # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below - geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + - - # Set the colors that will be used to color the lines in the legend itself - # The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column - # If you do not supply your own color values, then R will assign default colors to each factor level - scale_color_manual(values = c("blue", "orange")) + - - # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding - guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + - - # Facet the plot by day (e.g. create a panel per day) - # Use the new day labels here - facet_wrap(~ day_label, nrow = 2, strip.position = "left") + - - # Change the x and y axis labels - xlab("Time") + - - # Now you can add a y-axis label - ylab("Day of data collection") + - - # Change the aesthetics of the x-axis labels - scale_x_datetime( - date_breaks = "30 mins", - date_labels = "%H:%M" - ) + - - # 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" - ) - -# Save the image file to your computer -ggsave(file.path(path, "raw_detections.tiff"), width = 8, height = 6, units = "in", dpi = 300) - -``` - -You just learned how to make a plot in R, and also how to save it to your computer. 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. \ No newline at end of file From 9f81f9876d4aee1fc4ceba594ee5945e3502686f Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Mon, 29 Jan 2024 12:05:01 -0500 Subject: [PATCH 22/69] Fixed issues with creating simulated data; added code to finish ABISSMAL analysis pipeline and check final data in sixth vignette --- R/vignettes/Vignette_03_SimulateData.Rmd | 24 +++-- R/vignettes/Vignette_04_SaveData.Rmd | 92 ++++++++++++------- .../Vignette_05_ProcessData_BuildPlots.Rmd | 15 ++- ...=> Vignette_06_FinishAnalysisPipeline.Rmd} | 80 ++++++++++------ 4 files changed, 137 insertions(+), 74 deletions(-) rename R/vignettes/{Vignette_06_ComplexBarcodePlots.Rmd => Vignette_06_FinishAnalysisPipeline.Rmd} (81%) diff --git a/R/vignettes/Vignette_03_SimulateData.Rmd b/R/vignettes/Vignette_03_SimulateData.Rmd index dfc80ac..de834aa 100644 --- a/R/vignettes/Vignette_03_SimulateData.Rmd +++ b/R/vignettes/Vignette_03_SimulateData.Rmd @@ -65,7 +65,7 @@ You can practice removing the `path` object from your global environment by writ The primary data collected by the ABISSMAL tracking system are timestamps indicating the moment of time when a movement sensor triggered. 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 and 1 pair of infrared beam breakers is mounted in front of the RFID antenna. In this simulated setup, the infrared beam breakers will trigger first when a bird enters the nest container, and the RFID antenna should trigger first when a bird leaves the 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 the container entrance. In this simulated setup, the outer beam breakers will trigger first 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 trigger 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 is called `rfid_ts`, and it holds 4 timestamps in hours:minutes:seconds format. Each timestamp is surrounded by double quotes to denote that we are using character information here. @@ -93,8 +93,9 @@ length(rfid_ts) # This vector has 4 elements Let's continue by simulating 2 entrance and 2 exit events. We can choose timestamps for the infrared beam breakers that precede the RFID timestamps to simulate an entrance event, and infrared beam breaker timestamps that follow the RFID timestamps to simulate an exit event. We will offset detections from each sensor within the entrance and exit events by 1 second. ```{r class.source = "bg-info"} -# Simulate timestamps for an entrance, an exit, and then another entrance and exit -irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") +# 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") ``` @@ -116,19 +117,22 @@ glimpse(rfid_ts) Another important type of information to simulate is noise in the sensor detections. For instance, the RFID antenna can fail to detect an individual's passive integrated transponder (PIT) tag, and the infrared beam breakers can trigger when birds leave nesting material hanging in the entrance of the container. In both of these cases, the infrared beam breakers should trigger when the RFID antenna does not. ```{r class.source = "bg-info"} -# Simulate some RFID detection failures -irbb_ts <- c(irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") +# 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(irbb_ts) +glimpse(o_irbb_ts) +glimpse(i_irbb_ts) -# Simulate some stray beam breaker detections -irbb_ts <- c(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") +# 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(irbb_ts) +glimpse(o_irbb_ts) ``` -You've created simulated datasets of detections of animal movements, but currently, these datasets are in vector format only 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 identity of each PIT tag detected. +You've created simulated datasets of detections of animal movements, but currently, these datasets are in vector format only 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. Right now, the data for both sensors is in vector format. Vectors are useful data structures in R, but one limitation of vectors is that you cannot combine different data types into the same object. You can try this out with the code below: ```{r class.source = "bg-warning"} diff --git a/R/vignettes/Vignette_04_SaveData.Rmd b/R/vignettes/Vignette_04_SaveData.Rmd index 800fc98..2a80d2d 100644 --- a/R/vignettes/Vignette_04_SaveData.Rmd +++ b/R/vignettes/Vignette_04_SaveData.Rmd @@ -50,20 +50,24 @@ glimpse(rfid_ts) ```{r} -# For the beam breakers, simulate timestamps for an entrance, an exit, and then another entrance and exit -irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") +# 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 for the beam breaker data -irbb_ts <- c(irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") +# 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 beam breaker detections -irbb_ts <- c(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") +# 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(irbb_ts) +glimpse(o_irbb_ts) +glimpse(i_irbb_ts) ``` -

Simulate 2 days of RFID data collection

+

Simulate 3 days of RFID data collection

In the code below, you'll use combine the vector of RFID timestamps that you made above with metadata in a data frame. This metadata will include the year, month, and day, as well as a column with 2 unique PIT tag identifiers (1 for each simulated individual), and a column with the sensor type. You should recognize some of this code from the previous vignette: ```{r} @@ -71,6 +75,11 @@ In the code below, you'll use combine the vector of RFID timestamps that you mad # 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) @@ -83,9 +92,9 @@ sim_dats_rfid <- sim_dats_rfid %>% month = 08, day = 01 ) %>% - # Allocate the first half of the detections to the first individual using nrow(.)/2, and the second half of the detctions to the second individual using nrow(.)/2 again. Both of the rep() expressions are combined into a single vector using the c() function + # Add the PIT tag metadata as a new column dplyr::mutate( - PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2)) + PIT_tag = PIT_tag ) %>% dplyr::mutate( sensor_id = "RFID" @@ -106,9 +115,15 @@ sim_dats_rfid <- sim_dats_rfid %>% dplyr::mutate( day = 02 ) + ) %>% + bind_rows( + sim_dats_rfid %>% + dplyr::mutate( + day = 03 + ) ) -glimpse(sim_dats_rfid) # Double the number of rows, looks good +glimpse(sim_dats_rfid) # Triple the number of rows, looks good ``` @@ -124,9 +139,9 @@ sim_dats_rfid$day sim_dats_rfid[["day"]] # You can use the function unique() to see the unique values of any vector, including a column of a data frame -unique(sim_dats_rfid$day) # Two days, looks good +unique(sim_dats_rfid$day) # Three days, looks good -unique(sim_dats_rfid[["day"]]) # Two days, looks good +unique(sim_dats_rfid[["day"]]) # Three days, looks good ``` @@ -135,29 +150,32 @@ unique(sim_dats_rfid[["day"]]) # Two days, looks good 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, and then that vector is piped into the function `unique()` to check the unique values contained in the vector. The function `unique()` does not need an argument inside of the parentheses here because you're already piping the output that it needs directly into the function. ```{r} -# Two days, looks good +# Three days, looks good sim_dats_rfid %>% pull(day) %>% unique() ``` -

Simulate 2 days of beam breaker data collection

+

Simulate 3 days of beam breaker data collection

-Next, repeat this process of creating a data frame with metadata for the infrared beam breaker dataset. 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 two days as the RFID system. +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 two days as the RFID system. ```{r} -# Overwrite the vector exp_rep with a new vector the same length as irbb_ts -exp_rep <- rep(x = "Nest_01", times = length(irbb_ts)) +# 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)) -sim_dats_irbb <- data.frame(chamber_id = exp_rep, timestamps = 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, - sensor_id = "Beam breakers" + # 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) @@ -168,11 +186,17 @@ sim_dats_irbb <- sim_dats_irbb %>% dplyr::mutate( day = 02 ) + ) %>% + bind_rows( + sim_dats_irbb %>% + dplyr::mutate( + day = 03 + ) ) -glimpse(sim_dats_irbb) # Double the number of rows, looks good +glimpse(sim_dats_irbb) # Triple the number of rows, looks good -# Two days, looks good +# Three days, looks good sim_dats_irbb %>% pull(day) %>% unique() @@ -381,7 +405,7 @@ list.files(file.path(path, "Data/RFID")) You just wrote out 2 spreadsheets using 1 loop, but you wrote out the same data frame to each spreadsheet. In order to write out a different data frame to each spreadsheet, you add the data frame filtering that you learned above to the loop. In the code below, you'll create another vector object called `days`, and then use the iterating variable to filter `sim_dats_rfid` by each day in `days`. ```{r eval = FALSE} -days <- c(1, 2) +days <- c(1, 2, 3) lapply(X = 1:length(files), FUN = function(x){ @@ -410,14 +434,14 @@ Now you can put all of these pieces together and use the loop to write out a spr ```{r eval = FALSE} # Make a vector of the custom file names to write out -files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv") +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) +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 @@ -444,7 +468,7 @@ list.files(file.path(path, "Data/RFID")) You can remove these files. ```{r eval = FALSE} -files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv") +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) @@ -470,8 +494,8 @@ To carry out the file filtering and writing, you'll use a type of object called # Make a list of the custom file names to write out files <- list( - c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv"), - c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") + 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) @@ -498,8 +522,8 @@ Lists can also have named elements, which makes it possible to access elements b # 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"), - `IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") + `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) @@ -529,8 +553,8 @@ sensors # Make a named list of the custom file names to write out files <- list( - `RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv"), - `IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv") + `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 @@ -546,8 +570,8 @@ file_dirs # Make a list of the days to write out for each sensor # This could be a single vector rather than a list since you want to write out the same days per sensor, but a list is useful in case you wanted to change the days (and number of days) to write out per sensor days <- list( - `RFID` = c(1, 2), - `IRBB` = c(1, 2) + `RFID` = c(1, 2, 3), + `IRBB` = c(1, 2, 3) ) days diff --git a/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd b/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd index 03fb8f3..e90e9f0 100644 --- a/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd +++ b/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd @@ -135,7 +135,7 @@ glimpse(perching) ``` -`detect_perching_events()` identified 4 total perching events, which is the same number that you simulated in the previous vignette. You can look at the values inside of data frame for more information about these perching events: +`detect_perching_events()` identified 6 total perching events, which is the same number that you simulated in the previous vignette (two perching events per day). You can look at the values inside of data frame for more information about these perching events: ```{r} # The timestamps when each perching event started @@ -149,6 +149,13 @@ perching$PIT_tag ``` +You can also view the whole data frame in a separate pane: +```{r eval = FALSE} + +View(perching) + +``` + The information above tells you that there were 2 perching events detected at 8:00 each day, and 2 perching events detected at 11:30 each day (as expected). The PIT tag for each individual was detected once each day, so there was 1 perching event performed by each individual on each day. Detecting perching events is not a requirement in the ABISSMAL workflow, but it can be a useful step to obtain as much information from the raw data before some detections are dropped during pre-processing (see below). @@ -247,7 +254,9 @@ rfid_combined <- rfid_raw %>% dplyr::mutate( dataset = "pre-processed" ) - ) + ) %>% + # Order the data frame by timestamps. The -desc() inside of arrange() means that the timestamps will be arranged in increasing order + dplyr::arrange(-desc(timestamp_ms)) glimpse(rfid_combined) @@ -363,7 +372,7 @@ From this point of view though, it's hard to see how the raw and pre-processed d ggplot(data = rfid_combined %>% group_by(dataset) %>% - slice(1:3) %>% + slice(1:2) %>% ungroup() ) + diff --git a/R/vignettes/Vignette_06_ComplexBarcodePlots.Rmd b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd similarity index 81% rename from R/vignettes/Vignette_06_ComplexBarcodePlots.Rmd rename to R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd index 85e6d99..c81b0c7 100644 --- a/R/vignettes/Vignette_06_ComplexBarcodePlots.Rmd +++ b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd @@ -1,5 +1,5 @@ --- -title: "Vignette 05: Process Data" +title: "Vignette 06: Finish Analysis Pipeline" author: "Grace Smith-Vidaurre" date: "2023-12-27" output: html_document @@ -13,11 +13,7 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE)

Vignette Overview and Learning Objectives

-In this fifth vignette, you will begin using the simulated detections of animal movements in the ABISSMAL data processing and analysis workflow, including combining raw data across days and pre-processing the raw data. You will also make visualizations of the processed data. You will continue to use coding skills that you learned in the previous vignettes, and you will learn additional skills that include: - -1. Accessing custom functions -2. Using custom functions -3. Plotting data with ggplot +In this sixth vignette, you will finish using the simulated detections of animal movements in the ABISSMAL data processing and analysis workflow. You will detect clusters of detections that represent distinct movement events, and then score behavioral inferences about the movement events. You will also make plots to visualize these behavioral inferences. You will continue to use coding skills that you learned in the previous vignettes, and you will additionally learn how to build more complex visualizations with ggplot.

Load packages and your working directory path

@@ -33,45 +29,75 @@ path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" ``` -

Plot raw and pre-processed data

+

Load ABISSMAL functions

+ +```{r} + +# Load the function that detects clusters in the pre-processed data +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_clusters.R") -Now that you've combined the raw data per sensor, detected perching events in the raw data, and pre-processed the raw data, it's time to visualize these different datasets. Making visualizations in R is important for generating high-quality figures for publications and presentations, but it's also important for checking your work as you process and analyze data. +# 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") + +``` -In the code below, you'll learn how to use functions from the `ggplot2` package to make barcode style visualizations of the raw and processed detection datasets. +

Finish the ABISSMAL pipeline

-Start by reading in the raw data for each sensor, and convert the timestamps to POSIX format. +Here you'll use the ABISSMAL function `detect_clusters()` to identify clusters of detections across sensor types (e.g. detections from different sensors that were recorded close together in time). ```{r} -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")) - ) +# 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") -# You should see that the timestamp_ms is in "dttm" format, which means that the POSIX conversion was done correctly -glimpse(rfid_raw) +``` -irbb_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_IRBB.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")) - ) +Next, you'll score behavioral inferences about these detection clusters with the function `score_clusters()`. +```{r} -glimpse(irbb_raw) +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") ``` -Next, start plotting this raw data using functions from `ggplot2`. This package is part of the `tidyverse`, but can also be installed and used separately. You can check out this [link](https://ggplot2.tidyverse.org/) for more resources to learn how to use ggplot 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. +

Check final results

-Thde `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. +Now that you finished running the ABISSMAL pipeline to detect and make inferences about movement events, you can check the final results. +```{r} + +scored_clusters <- read.csv(file.path(path, "Data/processed", "scored_detectionClusters.csv")) + +glimpse(scored_clusters) -If you call `ggplot()`, you'll see that the function immediately draws a blank plot in your Plots pane in RStudio. +``` + +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 1) make a new column with information about the day per timestamp, 2) drop rows with missing data for direction scored, 3) group the data frame by day and direction scored, and 4) count the number of rows per group. ```{r} -ggplot() +scored_clusters %>% + # Extract the day from each timestamp and make a new column with this information + dplyr::mutate( + day = lubridate::day(start) + ) %>% + # Drop rows with missing values for direction_scored + # 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 this function, 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) %>% + # The number of rows here is the number of exits or entrances scored per day + dplyr::summarise( + n = n() + ) ``` +Four entrance and exit events were scored per day. Does this line up with the anticipated number of exits and entrances per day? If you go back to the code where you created the simulated raw datasets in vignettes 3 and 4, 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. + + +TKTK continue with summarizing perching data, individual identity information, then a complex barcode plot with color encoding individual, line type indicating the direction of movement, and perching events as dots above these lines. + + The plot will still remain blank even if you supply information about your data in order to set up plot aesthetics. ```{r} From ce9c79bc5a7b08b75af745e5aab4d65b9e1d0dd5 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Mon, 29 Jan 2024 17:55:02 -0500 Subject: [PATCH 23/69] Nearly done with draft of sixth vignette --- R/vignettes/.Rhistory | 632 +++++++++--------- .../Vignette_06_FinishAnalysisPipeline.Rmd | 396 ++++++----- 2 files changed, 546 insertions(+), 482 deletions(-) diff --git a/R/vignettes/.Rhistory b/R/vignettes/.Rhistory index f381551..9d4eb82 100644 --- a/R/vignettes/.Rhistory +++ b/R/vignettes/.Rhistory @@ -1,280 +1,144 @@ -"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){ -return(seq(starts[x], ends[x], 0.5)) -}, simplify = FALSE) -event_ts -test_that("The correct number and timing of discrete movement events are retained per pre-processing mode for RFID data and multiple temporal thresholds", { -# 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")) -} -# Create 4 clusters of detections: each cluster consists of a different number of detections spaced different numbers of seconds apart (testing `threshold`) -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 -ths <- seq(0.5, 5, by = 0.5) -invisible(lapply(1:length(ths), function(x){ -# Create the timestamps -tmp_tstmps <- data.table::rbindlist(lapply(1:length(starts), function(i){ -sim_ts <- seq(starts[i], ends[i], by = ths[x]) -return(data.frame(tstmps = sim_ts, cluster = i)) -})) -# Write out a spreadsheet with these timestamps that will be used as input data for the function -sim_ts <- data.frame(timestamp_ms = tmp_tstmps$tstmps) %>% +glimpse(rfid_ts) +# 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 +o_irbb_ts <- c(o_irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") +i_irbb_ts <- c(i_irbb_ts, "06:05:04", "06:35:09", "07:15:41", "11:10:24") +# Simulate some stray detections for the outer beam breaker +o_irbb_ts <- c(o_irbb_ts, "09:45:01", "09:45:02", "09:45:03", "09:45:04", "09:45:05", "09:45:06", "09:45:07", "09:45:08", "09:45:09", "09:45:10", "09:45:11") +glimpse(o_irbb_ts) +glimpse(i_irbb_ts) +# 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( -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) -####### `retain_first` mode ####### -preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag_ID", pixel_col_nm = NULL, thin_threshold = ths[x], 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 +year = 2023 +) %>% 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 >= ths[x])) -# 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(z){ -expect_equal(test_res$timestamp_ms[z], tmp_starts[z]) -})) -####### `thin` mode ####### -preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag_ID", pixel_col_nm = NULL, thin_threshold = ths[x], 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 +month = 08, +day = 01 +) %>% +# Add the PIT tag metadata as a new column dplyr::mutate( -timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) +PIT_tag = PIT_tag +) %>% +dplyr::mutate( +sensor_id = "RFID" ) -# 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' -# cat("thin mode: th = ", ths[x], "\n") -num_seq <- seq(starts[1], ends[1], by = ths[x]) -expect_equal(nrow(test_res), length(starts) * length(seq(1, length(num_seq), 2))) -# Check that 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 >= ths[x])) -# Check that the every other timestamp from the raw data is returned as the timestamp per detection cluster -tmp_tstmps <- data.table::rbindlist(lapply(1:length(starts), function(i){ -sim_ts <- seq(starts[i], ends[i], by = ths[x]) -if(length(sim_ts) %% 2 != 0){ -sim_ts <- sim_ts[-seq(2, length(sim_ts) - 1, 2)] -} else if(length(sim_ts) %% 2 == 0){ -sim_ts <- sim_ts[-seq(2, length(sim_ts), 2)] -} -return(data.frame(tstmps = sim_ts, cluster = i)) -})) -tmp_tstmps$tstmps <- tmp_tstmps$tstmps[order(tmp_tstmps$tstmps)] -expect_equal(test_res$timestamp_ms, tmp_tstmps$tstmps) -})) -# Remove the temporary directory and all files within it -if(tmp_path == file.path(path, data_dir)){ -unlink(tmp_path, recursive = TRUE) -} -}) -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")) -} -# Create 4 clusters of detections: each cluster consists of a different number of detections spaced different numbers of seconds apart (testing `threshold`) -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 -ths <- seq(0.5, 5, by = 0.5) -ths -invisible(lapply(1:length(ths), function(x){ -# Create the timestamps -tmp_tstmps <- data.table::rbindlist(lapply(1:length(starts), function(i){ -sim_ts <- seq(starts[i], ends[i], by = ths[x]) -return(data.frame(tstmps = sim_ts, cluster = i)) -})) -# Write out a spreadsheet with these timestamps that will be used as input data for the function -sim_ts <- data.frame(timestamp_ms = tmp_tstmps$tstmps) %>% +glimpse(sim_dats_rfid) +sim_dats_rfid <- sim_dats_rfid %>% +bind_rows( +sim_dats_rfid %>% 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" +day = 02 ) -write.csv(sim_ts, file.path(tmp_path, "raw_combined", "combined_raw_data_RFID.csv"), row.names = FALSE) -####### `retain_first` mode ####### -preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag_ID", pixel_col_nm = NULL, thin_threshold = ths[x], 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 +) %>% +bind_rows( +sim_dats_rfid %>% dplyr::mutate( -timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) +day = 03 ) -# 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 >= ths[x])) -# 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(z){ -expect_equal(test_res$timestamp_ms[z], tmp_starts[z]) -})) -####### `thin` mode ####### -preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag_ID", pixel_col_nm = NULL, thin_threshold = ths[x], 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 +) +glimpse(sim_dats_rfid) # Triple the number of rows, looks good +# 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( -timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) +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))) ) -# 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' -# cat("thin mode: th = ", ths[x], "\n") -num_seq <- seq(starts[1], ends[1], by = ths[x]) -expect_equal(nrow(test_res), length(starts) * length(seq(1, length(num_seq), 2))) -# Check that 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 >= ths[x])) -# Check that the every other timestamp from the raw data is returned as the timestamp per detection cluster -tmp_tstmps <- data.table::rbindlist(lapply(1:length(starts), function(i){ -sim_ts <- seq(starts[i], ends[i], by = ths[x]) -if(length(sim_ts) %% 2 != 0){ -sim_ts <- sim_ts[-seq(2, length(sim_ts) - 1, 2)] -} else if(length(sim_ts) %% 2 == 0){ -sim_ts <- sim_ts[-seq(2, length(sim_ts), 2)] -} -return(data.frame(tstmps = sim_ts, cluster = i)) -})) -tmp_tstmps$tstmps <- tmp_tstmps$tstmps[order(tmp_tstmps$tstmps)] -expect_equal(test_res$timestamp_ms, tmp_tstmps$tstmps) -})) -x <- 1 -# Create the timestamps -tmp_tstmps <- data.table::rbindlist(lapply(1:length(starts), function(i){ -sim_ts <- seq(starts[i], ends[i], by = ths[x]) -return(data.frame(tstmps = sim_ts, cluster = i)) -})) -tmp_tstmps -ths -x <- 4 -# Create the timestamps -tmp_tstmps <- data.table::rbindlist(lapply(1:length(starts), function(i){ -sim_ts <- seq(starts[i], ends[i], by = ths[x]) -return(data.frame(tstmps = sim_ts, cluster = i)) -})) -tmp_tstmps -# Write out a spreadsheet with these timestamps that will be used as input data for the function -sim_ts <- data.frame(timestamp_ms = tmp_tstmps$tstmps) %>% +glimpse(sim_dats_irbb) +sim_dats_irbb <- sim_dats_irbb %>% +bind_rows( +sim_dats_irbb %>% 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" +day = 02 ) -write.csv(sim_ts, file.path(tmp_path, "raw_combined", "combined_raw_data_RFID.csv"), row.names = FALSE) -preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag_ID", pixel_col_nm = NULL, thin_threshold = ths[x], 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 +) %>% +bind_rows( +sim_dats_irbb %>% dplyr::mutate( -timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) +day = 03 +) ) -# Check that the results contain the expected number of detection clusters -expect_equal(nrow(test_res), length(starts)) -nrow(test_res) -length(starts) -View(test_res) -# 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)] -diffs -expect_true(all(diffs >= ths[x])) -# Check that the first timestamp from the raw data is returned as the timestamp per detection cluster -tmp_starts <- starts[order(starts)] -tmp_starts -invisible(lapply(1:nrow(test_res), function(z){ -expect_equal(test_res$timestamp_ms[z], tmp_starts[z]) +glimpse(sim_dats_irbb) # Triple the number of rows, looks good +# Three days, looks good +sim_dats_irbb %>% +pull(day) %>% +unique() +View(sim_dats_irbb) +# 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 each sensor, iterate over days +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 and single bracket filtering to pull out the right file name +write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) +}) })) -####### `thin` mode ####### -preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag_ID", pixel_col_nm = NULL, thin_threshold = ths[x], 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")) +# Make a vector of sensor labels +sensors <- c("RFID", "IRBB") +sensors +# Make a named list of the custom file names to write out +files <- list( +`RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv"), +`IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv", "IRBB_simulated_Pair-01_2023_08_03.csv") ) -View(test_res) -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")) -) %>% -dplyr::arrange(-desc(timestamp_ms)) +files +# Make a list of file paths per sensor that will be used inside of the loop +file_dirs <- list( +`RFID` = file.path(path, "Data/RFID"), +`IRBB` = file.path(path, "Data/IRBB") +) +file_dirs +# Make a list of the days to write out for each sensor +# This could be a single vector rather than a list since you want to write out the same days per sensor, but a list is useful in case you wanted to change the days (and number of days) to write out per sensor +days <- list( +`RFID` = c(1, 2, 3), +`IRBB` = c(1, 2, 3) +) +days +# Finally, you need to make a list of the data frames that you want to write out. Since you already know how to filter data frames by day, that code can go inside of the loop, and you only need to specify a data frame per sensor type here +dats <- list( +`RFID` = sim_dats_rfid, +`IRBB` = sim_dats_irbb +) +glimpse(dats) +# 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 each sensor, iterate over days +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 and single bracket filtering to pull out the right file name +write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) +}) +})) rm(list = ls()) # Clean global environment library(tidyverse) # Load the set of tidyverse packages library(data.table) # Load other packages that the ABISSMAL functions require @@ -288,6 +152,31 @@ source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_perching_events. 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") +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") +list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") +rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) +glimpse(rfid_data) +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") +list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") +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") +perching <- read.csv(file.path(path, "Data", "processed", "perching_events_RFID.csv")) +glimpse(perching) +# The timestamps when each perching event started +perching$perching_start +# The timestamps when each perching event ended +perching$perching_end +# The timestamps when each perching event ended +perching$perching_end +# The unique PIT tag identifier that tells you which individual was perched on the RFID antenna +perching$PIT_tag +View(perching) +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") +rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) +glimpse(rfid_pp) +preprocess_detections(sensor = "IRBB", timestamps_col_nm = "timestamp_ms", group_col_nm = "sensor_id", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") +list.files(file.path(path, "Data/processed")) +irbb_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_IRBB.csv")) +glimpse(irbb_pp) 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( @@ -303,25 +192,14 @@ timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York ) %>% dplyr::arrange(-desc(timestamp_ms)) glimpse(rfid_pp) -View(cbind(rfid_raw$timestamp_ms, rfid_pp$timestamp_ms)) -View(data.frame(rfid_raw$timestamp_ms, rfid_pp$timestamp_ms)) -rfid_raw$timestamp_ms -View(data.frame(rfid_raw$timestamp_ms[1:20], rfid_pp$timestamp_ms[1;20])) -View(data.frame(rfid_raw$timestamp_ms[1:20], rfid_pp$timestamp_ms[1:20])) -View(data.frame(rfid_raw$timestamp_ms[1:25], rfid_pp$timestamp_ms[1:25])) -ggplot(data = rfid_combined %>% -group_by(dataset) %>% -slice(1:5) %>% -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 = "right") +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) rfid_combined <- rfid_raw %>% dplyr::select(sensor_id, day, timestamp_ms) %>% dplyr::mutate( @@ -335,6 +213,19 @@ dataset = "pre-processed" ) ) glimpse(rfid_combined) +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 +) +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")) # 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 %>% @@ -345,11 +236,14 @@ dataset = factor(dataset, levels = c("raw", "pre-processed")) glimpse(rfid_combined) # The levels of the factor column are ordered with "raw" first, rather than in alphabetical order levels(rfid_combined$dataset) -ggplot(data = rfid_combined %>% -group_by(dataset) %>% -slice(1:5) %>% -ungroup() +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")) +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), @@ -357,7 +251,7 @@ 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 = "right") +facet_wrap(~ dataset, nrow = 2, strip.position = "left") ggplot(data = rfid_combined %>% group_by(dataset) %>% slice(1:3) %>% @@ -370,7 +264,7 @@ 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 = "right") +facet_wrap(~ dataset, nrow = 2, strip.position = "left") ggplot(data = rfid_combined %>% group_by(dataset) %>% slice(1:4) %>% @@ -383,10 +277,10 @@ 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 = "right") +facet_wrap(~ dataset, nrow = 2, strip.position = "left") ggplot(data = rfid_combined %>% group_by(dataset) %>% -slice(1:3) %>% +slice(1:6) %>% ungroup() ) + # Add a vertical line for each timestamp @@ -396,8 +290,12 @@ 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 = "right") -ggplot(data = rfid_combined) + +facet_wrap(~ dataset, nrow = 2, strip.position = "left") +ggplot(data = rfid_combined %>% +group_by(dataset) %>% +slice(1:3) %>% +ungroup() +) + # Add a vertical line for each timestamp geom_segment( aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), @@ -405,21 +303,54 @@ 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 = "right") -rfid_perch <- read.csv(file.path(path, "Data/processed/perching_events_RFID.csv")) %>% -# The timestamps must be converted to POSIX format every time that the data is read back into R for plotting +facet_wrap(~ dataset, nrow = 2, strip.position = "left") +rfid_combined <- rfid_raw %>% +dplyr::select(sensor_id, day, timestamp_ms) %>% dplyr::mutate( -timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) +dataset = "raw" ) %>% -dplyr::arrange(-desc(timestamp_ms)) -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 +bind_rows( +rfid_pp %>% +dplyr::select(sensor_id, day, timestamp_ms) %>% 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")) +dataset = "pre-processed" +) ) %>% -dplyr::arrange(-desc(perching_start)) -glimpse(rfid_perch) +# Order the data frame by timestamps. The -desc() inside of arrange() means that the timestamps will be arranged in increasing order +dplyr::arrange(-desc(timestammp_ms)) +glimpse(rfid_combined) +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" +) +) %>% +# Order the data frame by timestamps. The -desc() inside of arrange() means that the timestamps will be arranged in increasing order +dplyr::arrange(-desc(timestamp_ms)) +glimpse(rfid_combined) +# Change the column dataset to data type "factor" +# By specifying "raw" first in the argument levels, you are reordering the factor levels so that "raw" comes first +rfid_combined <- rfid_combined %>% +dplyr::mutate( +dataset = factor(dataset, levels = c("raw", "pre-processed")) +) +# The dataset column is now type "fct", or "factor" +glimpse(rfid_combined) +# The levels of the factor column are ordered with "raw" first, rather than in alphabetical order +levels(rfid_combined$dataset) +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")) ggplot(data = rfid_combined) + # Add a vertical line for each timestamp geom_segment( @@ -429,7 +360,11 @@ 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") -ggplot(data = rfid_combined) + +ggplot(data = rfid_combined %>% +group_by(dataset) %>% +slice(1:3) %>% +ungroup() +) + # Add a vertical line for each timestamp geom_segment( aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), @@ -437,15 +372,28 @@ 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 +facet_wrap(~ dataset, nrow = 2, strip.position = "left") +rfid_combined %>% +group_by(dataset) %>% +slice(1:3) +ggplot(data = rfid_combined %>% +group_by(dataset) %>% +slice(1:2) %>% +ungroup() +) + +# Add a vertical line for each timestamp geom_segment( -data = rfid_perch, -aes(x = perching_start, xend = perching_end, y = 0, yend = 1), -color = "blue", +aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 -) -ggplot(data = rfid_combined) + +) + +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") +ggplot(data = rfid_combined %>% +group_by(dataset) %>% +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), @@ -453,14 +401,20 @@ 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 +facet_wrap(~ dataset, nrow = 2, strip.position = "left") +ggplot(data = rfid_combined %>% +group_by(dataset) %>% +slice(1:2) %>% +ungroup() +) + +# Add a vertical line for each timestamp geom_segment( -data = rfid_perch, -aes(x = perching_start, xend = perching_end, y = 1, yend = 1.5), -color = "blue", +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") ggplot(data = rfid_combined) + # Add a vertical line for each timestamp geom_segment( @@ -507,6 +461,52 @@ theme_bw() + # Use aesthetics functions to remove the y-axis labels and ticks theme( axis.text.y = element_blank(), -axis.ticks.y = element_blank() +axis.ticks.y = element_blank(), +legend.position = "top" ) gg +gg +# Save the image file to your computer +ggsave(file.path(path, "raw_processed_perching.tiff"), width = 8, height = 6, units = "in", dpi = 300) +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") +# 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") +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") +scored_clusters <- read.csv(file.path(path, "Data/processed", "scored_detectionClusters.csv")) +glimpse(scored_clusters) +scored_clusters %>% +group_by(day, direction_scored) %>% +dplyr::summarise( +n = n() +) +scored_clusters %>% +dplyr::mutate( +day = lubridate::day(start) +) %>% +group_by(day, direction_scored) %>% +dplyr::summarise( +n = n() +) +scored_clusters %>% +# Extract the day from each timestamp and make a new column with this information +dplyr::mutate( +day = lubridate::day(start) +) %>% +# Drop rows with missing values for 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) %>% +# The number of rows here is the number of exits or entrances scored per day +dplyr::summarise( +n = n() +) diff --git a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd index c81b0c7..f156f12 100644 --- a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd +++ b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd @@ -66,7 +66,13 @@ score_clusters(file_nm = "detection_clusters.csv", rfid_label = "RFID", camera_l Now that you finished running the ABISSMAL pipeline to detect and make inferences about movement events, you can check the final results. ```{r} -scored_clusters <- read.csv(file.path(path, "Data/processed", "scored_detectionClusters.csv")) +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")) + ) %>% + dplyr::arrange(-desc(start)) glimpse(scored_clusters) @@ -94,238 +100,274 @@ scored_clusters %>% 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 3 and 4, you should see that you started by simulating 2 entrances and exits per day across the RFID and beam breaker datasets. Then, you added 2 more entrance and 2 more exit events per day when you simulated RFID detection failures (e.g. movements captured by the beam breakers only). `score_clusters()` detected the correct number of entrances and exits per day. +Next, check out the perching events. Start by filtering out all rows that were not scored as perching events (rows that have NA values in the column "perching_PIT_tag"). Then, select only the columns that have information that is useful to check: the PIT tag codes, as well as the start and end of each perching event. +```{r} -TKTK continue with summarizing perching data, individual identity information, then a complex barcode plot with color encoding individual, line type indicating the direction of movement, and perching events as dots above these lines. +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) +``` -The plot will still remain blank even if you supply information about your data in order to set up plot aesthetics. +As specified in vignettes 3 and 4, the first perching event each day was performed by the first individual (PIT tag 1357aabbcc), and the second was attributed to the second individual (PIT tag 2468zzyyxx). + +How many movement events that were not perching events were attributed to each individual? ```{r} -ggplot(data = rfid_raw) +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() + ) ``` -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 (see a plot in the next vignette). +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 3 and 4). More non-perching movement events were detected across these days, but those events were not always captured by the RFID antenna (e.g. the simulated RFID detection failures that were captured by the beam breakers only). The beam breakers captured those movement events but cannot record individual identity. +

Build complex barcode plot

+Now that you checked out the final results, you can visualize them. In the code below, you'll learn how to build a barcode plot that is more complex than the plot you made in vignette 5. 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. -TKTK the code below complicates adding a legend. Consider this code to be a more complex version of the figure...maybe save for another script? And here add just a single sensor +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. -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 x-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, information on the x-axis only). +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 day is the first day of the month), the value you want to add to the vector if the condition is true ("Day 1" of data collection), and then the value that you want to add if the condition is false ("Day 2" of data collection, since there are only 2 dates in each dataset). ```{r} -ggplot() + - - # Add a vertical line for each RFID timestamp - geom_segment( - data = rfid_raw, - aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1), - color = "blue", - linewidth = 0.3 - ) - -``` - -In the code above, you used the argument `data` inside of `geom_segment()` to tell the function where to get data for drawing the line segments. +# 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) -In the argument `aes()`, which is a standard `ggplot2` function for encoding plot aesthetics, you specified that the data to plot on the x-axis are the timestamps for each detection, by providing the column name `timestamp_ms` to the inner argument `x`. You also specified what data should be plotted on the y-axis using the argument `y`. In this case, you can set `y` to any value that you want, since you aren't plotting data along this axis. - -The arguments `x` and `y` determine where the beginning of each line 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 `timestamp_ms` to the argument `xend`, you're indicating that you want the line segment to start and end on the same timestamp. On the y-axis, the number that you supply to `yend` will determine the height of each line. Since you're adding data from a single sensor here, the height of the line does not matter much. If you changed the code to read `yend = 0.1`, the plot would look very similar, but the y-axis would be scaled to have 0.1 as the maximum value instead of 1. - -The last two arguments inside of `geom_segment()` control the color of the line segments (`color`), as well as the width of ech line (`linewidth`). Specifying the width of each line is different than specifying where each line segment starts and ends. +``` -You can continue adding information to this plot by layering on the raw beam breaker detections: +Now you can use `is.na()` inside of `ifelse` statements to modify columns in the data frame. ```{r} -ggplot() + - - # Add a vertical line for each RFID timestamp - geom_segment( - data = rfid_raw, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 1), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - geom_segment( - data = irbb_raw, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 1), - color = "orange", - linewidth = 0.3 +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) -This plot has more information, but it's difficult to interpret. Some of the detections for each sensor type occurred so close in time that they're layered nearly directly over one another. You can change the position of the line segements on the y-axis in order to have better resolution for visualizng the datasets from both sensors. +``` +You can use this modified data frame to build the plot. In the code below, you'll add lines colored by individual identity and with line types encoding the direction of movement. First you'll specify the plot aesthetics: ```{r} -ggplot() + - - # Add a vertical line for each RFID timestamp - geom_segment( - data = rfid_raw, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - geom_segment( - data = irbb_raw, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) +# 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") ``` -This change to the start and end coordinates of how segments are drawn on the y-axis offset the two sets of line segments per sensor dataset. The plot is now less crowded and it's easier to visualize patterns over time and between datasets now. - -The plot is looking a lot better, but there's a problem with the x-axis. The x-axis currently holds two types of information: 1) the day of data collection, and 2) the time of day. It's difficult to see when detections were recorded on each day, and whether detections were recorded over similar or different time periods between days. To make the x-axis more interpretable, you can change the plot to be "faceted", so that you have 1 panel per day of data collection. +Then you can add lines to the plot by individual identity. Here, you're splitting up the `geom_segment()` calls by unique values in the individual_initiated column. You only added segments for the first individual and all non-perching events that were not assigned to an individual, because from checking the results above, you know that no non-perching events were assigned to the second individual. ```{r} ggplot() + - # Add a vertical line for each RFID timestamp + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( - data = rfid_raw, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), - color = "blue", + 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.3 ) + - # Add a vertical line for each beam breaker timestamp + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( - data = irbb_raw, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), - color = "orange", + 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.3 ) + - # Facet the plot by day (e.g. create a panel per day) - facet_wrap(~ day, nrow = 2, strip.position = "left") - + # 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" + ) + ``` -The overall plot structure has improved a lot, but are still some modifications that would help interpretability. 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. label each hour). - -You can start by modifying the data frame of raw data for each senor 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 2 days of data collection that need recoding. - -First you can practice using `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 day is the first day of the month), the value you want to add to the vector if the condition is true ("Day 1" of data collection), and then the value that you want to add if the condition is false ("Day 2" of data collection, since there are only 2 dates in each dataset). -```{r} - -ifelse(test = rfid_raw$day == 1, yes = "Day 1", no = "Day 2") +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 end 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). -Now you can create a new column per data frame using an `ifelse` statement. +You can start by modifying the data frame to add information about the day of data collection. You'll create this new column by relying on conditional statements with the `ifelse()` function, since there are only 3 days of data collection that need recoding. ```{r} # Create a new column in the raw data for the date of data collection -rfid_raw2 <- rfid_raw %>% +scored_clusters_gg2 <- scored_clusters_gg %>% + # First you need to create a column with information about the day dplyr::mutate( - day_label = ifelse(day == 1, "Day 1", "Day 2") - ) - -# You should see that the new column "day_label" was created -# You'll see that a new year, month, and day were appended to the updated timestamps, but this is expected (see above) -glimpse(rfid_raw2) - -# Repeat this process for the beam breaker data -irbb_raw2 <- irbb_raw %>% + day = lubridate::day(start) + ) %>% dplyr::mutate( - day_label = ifelse(day == 1, "Day 1", "Day 2") + # 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) ) -glimpse(irbb_raw2) +# Looks good +glimpse(scored_clusters_gg2) + +scored_clusters_gg2 %>% + distinct(day_label) ``` Now you can update the code to include the new date labels: ```{r} -ggplot() + +# 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_gg) + - # Add a vertical line for each RFID timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( - data = rfid_raw2, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), - color = "blue", + 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.3 ) + - # Add a vertical line for each beam breaker timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( - data = irbb_raw2, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), - color = "orange", + 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.3 ) + + # 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 = 2, strip.position = "left") + 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 timestamps column. 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). +To line up the timestamps across days, you will need to update the format of the columns that hold timestamps. The code to convert the timestamps to a different format is nested and repetitive, but the timestamp conversion will be performed correctly. When you prompt the R to convert the timestamps to hours, minutes, and seconds only, R adds a default year, month, and day beforehand (likely the current date that you rant the code). This is expected, and it is not an error, but rather makes it possible for the timestamps to align correctly over days in the plot (since R sees all timestamps occurring on a single day). ```{r} -rfid_raw3 <- rfid_raw2 %>% +scored_clusters_gg3 <- scored_clusters_gg2 %>% dplyr::mutate( - timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%H:%M:%S"), format = "%H:%M:%S")) + 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(rfid_raw3) - -# Repeat this process for the beam breaker data -irbb_raw3 <- irbb_raw2 %>% - dplyr::mutate( - timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%H:%M:%S"), format = "%H:%M:%S")) - ) - -glimpse(irbb_raw3) +glimpse(scored_clusters_gg3) ``` -Now you can update the plotting code to change the aesthetics of the x-axis using the function `scale_x_datetime()` to specify that you want x-axis labels every half hour: +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: ```{r} -ggplot() + +# Update the dataset in the base layer of the plot, which will be used by all layers that do not have a specific data argument themselves +ggplot(data = scored_clusters_gg3) + - # Add a vertical line for each RFID timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( - data = rfid_raw3, - aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), - color = "blue", + 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.3 ) + - # Add a vertical line for each beam breaker timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( - data = irbb_raw3, - aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), - color = "orange", + 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.3 ) + + # Add the custom linetype values to this plot + scale_linetype_manual(values = ltys) + + + # Remove the y-axis label + ylab("") + + + # Add a y-axis label + xlab("Time") + + + # 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 = 2, strip.position = "left") + + facet_wrap(~ day_label, nrow = 3, strip.position = "left") + # Change the aesthetics of the x-axis labels scale_x_datetime( @@ -335,59 +377,81 @@ ggplot() + ``` -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 (since the height of each segment does not reflect data that you want to interpret). +There's still one more important piece of information to add to the plot: the perching events. You'll add these using `geom_segment()`, but you'll make these line segments short and thick, so that they appear more like dots on the plot: ```{r} -ggplot() + +ggplot(data = scored_clusters_gg3) + - # Add a vertical line for each RFID timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( - data = rfid_raw3, - aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), - color = "blue", + 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.3 ) + - # Add a vertical line for each beam breaker timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( - data = irbb_raw3, - aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), - color = "orange", + 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.3 ) + - # Facet the plot by day (e.g. create a panel per day) - # Use the new day labels here - facet_wrap(~ day_label, nrow = 2, strip.position = "left") + + # Add the custom linetype values to this plot + scale_linetype_manual(values = ltys) + - # Change the x and y axis labels + # Remove the y-axis label + ylab("") + + + # Add a y-axis label xlab("Time") + - # Now you can add a y-axis label - ylab("Day of data collection") + + # 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" ) + - # Use this function to convert the plot background to black and white - theme_bw() + + # 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" + ) + - # Use aesthetics functions to remove the y-axis labels and ticks - theme( - axis.text.y = element_blank(), - axis.ticks.y = element_blank() - ) + # Add the custom colors + scale_color_manual(values = cols) + ``` -Finally, you need to add a legend to this plot so you can communicate what the two different colors mean. Adding a legend to this plot is tricky because you manually coded the colors of the line segments in two separate uses in `geom_segment()`. `ggplot2` functions have specific requirements for making a legend, and they can require automated coding of aesthetic values like color. +TKTK the color legend is added by default with the new geom_segment call, so make only minor changes from here on + + + +Then end with adding the legend for color. + + +Finally, you need to add a legend to this plot so you can communicate what the two different colors mean. Adding this second legend to this plot is tricky because you manually coded the colors of the line segments in two separate uses in `geom_segment()`. `ggplot2` functions have specific requirements for making a legend, and they can require automated coding of aesthetic values like color. In order to automatically create a legend in a plot, it helps to have a layer of the plot in which color is automatically encoded. The way that you would do this is to move the `color` argument inside of the function `aes()` for one layer of the plot, and then supply `color` with a column of data type `factor`...TKTK continue ```{r} From cdb2ce357e0b0053dd4cd74bb033163242da5d7b Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Tue, 30 Jan 2024 09:29:09 -0500 Subject: [PATCH 24/69] Finished complex plot in sixth vignette; continue with reducing amount of plotting code --- R/vignettes/.Rhistory | 884 +++++++++--------- R/vignettes/README.md | 1 + .../Vignette_06_FinishAnalysisPipeline.Rmd | 249 ++--- 3 files changed, 506 insertions(+), 628 deletions(-) diff --git a/R/vignettes/.Rhistory b/R/vignettes/.Rhistory index 9d4eb82..d0f6c23 100644 --- a/R/vignettes/.Rhistory +++ b/R/vignettes/.Rhistory @@ -1,512 +1,512 @@ -glimpse(rfid_ts) -# 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 -o_irbb_ts <- c(o_irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") -i_irbb_ts <- c(i_irbb_ts, "06:05:04", "06:35:09", "07:15:41", "11:10:24") -# Simulate some stray detections for the outer beam breaker -o_irbb_ts <- c(o_irbb_ts, "09:45:01", "09:45:02", "09:45:03", "09:45:04", "09:45:05", "09:45:06", "09:45:07", "09:45:08", "09:45:09", "09:45:10", "09:45:11") -glimpse(o_irbb_ts) -glimpse(i_irbb_ts) -# 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" +scale_x_datetime( +date_breaks = "1 hour", +date_labels = "%H:%M" ) -glimpse(sim_dats_rfid) -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 -# Overwrite the vector exp_rep with a new vector the same length as o_irbb_ts and i_irbb_ts together -exp_rep <- rep(x = "Nest_01", times = length(o_irbb_ts) + length(i_irbb_ts)) -# Here the timestamps of both beam breaker pairs have been added to the same column using c() -sim_dats_irbb <- data.frame(chamber_id = exp_rep, timestamps = c(o_irbb_ts, i_irbb_ts)) -sim_dats_irbb <- sim_dats_irbb %>% -dplyr::mutate( -year = 2023, -month = 08, -day = 01, -# Add a unique sensor identifier by beam breaker pair -# Each unique label is repeated for the length of the vector of timestamps of each beam breaker pair -sensor_id = c(rep("Outer Beam Breakers", length(o_irbb_ts)), rep("Inner Beam Breakers", length(i_irbb_ts))) -) -glimpse(sim_dats_irbb) -sim_dats_irbb <- sim_dats_irbb %>% -bind_rows( -sim_dats_irbb %>% -dplyr::mutate( -day = 02 -) -) %>% -bind_rows( -sim_dats_irbb %>% -dplyr::mutate( -day = 03 -) -) -glimpse(sim_dats_irbb) # Triple the number of rows, looks good -# Three days, looks good -sim_dats_irbb %>% -pull(day) %>% -unique() -View(sim_dats_irbb) -# 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 each sensor, iterate over days -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 and single bracket filtering to pull out the right file name -write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) -}) -})) -# Make a vector of sensor labels -sensors <- c("RFID", "IRBB") -sensors -# Make a named list of the custom file names to write out -files <- list( -`RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv"), -`IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv", "IRBB_simulated_Pair-01_2023_08_03.csv") -) -files -# Make a list of file paths per sensor that will be used inside of the loop -file_dirs <- list( -`RFID` = file.path(path, "Data/RFID"), -`IRBB` = file.path(path, "Data/IRBB") -) -file_dirs -# Make a list of the days to write out for each sensor -# This could be a single vector rather than a list since you want to write out the same days per sensor, but a list is useful in case you wanted to change the days (and number of days) to write out per sensor -days <- list( -`RFID` = c(1, 2, 3), -`IRBB` = c(1, 2, 3) -) -days -# Finally, you need to make a list of the data frames that you want to write out. Since you already know how to filter data frames by day, that code can go inside of the loop, and you only need to specify a data frame per sensor type here -dats <- list( -`RFID` = sim_dats_rfid, -`IRBB` = sim_dats_irbb -) -glimpse(dats) -# 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 each sensor, iterate over days -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 and single bracket filtering to pull out the right file name -write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) -}) -})) -rm(list = ls()) # Clean global environment -library(tidyverse) # Load the set of tidyverse packages -library(data.table) # Load other packages that the ABISSMAL functions require -# Initialize an object with the path that is your working directory -path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" -# Load 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") -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") -list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") -rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) -glimpse(rfid_data) -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") -list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") -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") -perching <- read.csv(file.path(path, "Data", "processed", "perching_events_RFID.csv")) -glimpse(perching) -# The timestamps when each perching event started -perching$perching_start -# The timestamps when each perching event ended -perching$perching_end -# The timestamps when each perching event ended -perching$perching_end -# The unique PIT tag identifier that tells you which individual was perched on the RFID antenna -perching$PIT_tag -View(perching) -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") -rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) -glimpse(rfid_pp) -preprocess_detections(sensor = "IRBB", timestamps_col_nm = "timestamp_ms", group_col_nm = "sensor_id", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") -list.files(file.path(path, "Data/processed")) -irbb_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_IRBB.csv")) -glimpse(irbb_pp) -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")) -) %>% -dplyr::arrange(-desc(timestamp_ms)) -# You should see that the timestamp_ms is in "dttm" format, which means that the POSIX conversion was done correctly -glimpse(rfid_raw) -rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) %>% -# The timestamps must be converted to POSIX format every time that the data is read back into R for plotting -dplyr::mutate( -timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) -) %>% -dplyr::arrange(-desc(timestamp_ms)) -glimpse(rfid_pp) -rfid_perch <- read.csv(file.path(path, "Data/processed/perching_events_RFID.csv")) %>% -# The start and end timestamps must be converted to POSIX format every time that the data is read back into R for plotting -dplyr::mutate( -perching_start = as.POSIXct(format(as.POSIXct(perching_start, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")), -perching_end = as.POSIXct(format(as.POSIXct(perching_end, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) -) %>% -dplyr::arrange(-desc(perching_start)) -glimpse(rfid_perch) -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" -) -) -glimpse(rfid_combined) -ggplot(data = rfid_combined) + -# Add a vertical line for each timestamp +# Update the dataset in the base layer of the plot, which will be used by all layers that do not have a specific data argument themselves +ggplot(data = scored_clusters_gg3) + +# Add a vertical line for each non-perching event assigned to the first individual geom_segment( -aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), +data = scored_clusters_gg3 %>% +dplyr::filter(individual_initiated == "1357aabbcc"), +aes(x = start, y = 0, xend = end, yend = 0.5, linetype = direction_scored), +color = "orange", linewidth = 0.3 -) -ggplot(data = rfid_combined) + -# Add a vertical line for each timestamp +) + +# Add a vertical line for each non-perching event assigned to the first individual geom_segment( -aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), +data = scored_clusters_gg3 %>% +dplyr::filter(individual_initiated == "unassigned"), +aes(x = start, y = 0.6, xend = end, yend = 1.1, linetype = direction_scored), +color = "black", linewidth = 0.3 ) + -scale_color_manual(values = c("orange", "darkgreen")) -# 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")) +# Add the custom linetype values to this plot +scale_linetype_manual(values = ltys) + +# Remove the y-axis legend +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" ) -# The dataset column is now type "fct", or "factor" -glimpse(rfid_combined) -# The levels of the factor column are ordered with "raw" first, rather than in alphabetical order -levels(rfid_combined$dataset) -ggplot(data = rfid_combined) + -# Add a vertical line for each timestamp +# Update the dataset in the base layer of the plot, which will be used by all layers that do not have a specific data argument themselves +ggplot(data = scored_clusters_gg3) + +# Add a vertical line for each non-perching event assigned to the first individual geom_segment( -aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), +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.3 ) + -scale_color_manual(values = c("orange", "darkgreen")) -ggplot(data = rfid_combined) + -# Add a vertical line for each timestamp +# Add a vertical line for each non-perching event assigned to the first individual geom_segment( -aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), +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.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") -ggplot(data = rfid_combined %>% -group_by(dataset) %>% -slice(1:3) %>% -ungroup() +# Add the custom linetype values to this plot +scale_linetype_manual(values = ltys) + +# Remove the y-axis legend +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" ) + -# Add a vertical line for each timestamp +# 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" +) +# Update the dataset in the base layer of the plot, which will be used by all layers that do not have a specific data argument themselves +ggplot(data = scored_clusters_gg3) + +# Add a vertical line for each non-perching event assigned to the first individual geom_segment( -aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), +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.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") -ggplot(data = rfid_combined %>% -group_by(dataset) %>% -slice(1:4) %>% -ungroup() -) + -# Add a vertical line for each timestamp +# Add a vertical line for each non-perching event assigned to the first individual geom_segment( -aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), +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.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") -ggplot(data = rfid_combined %>% -group_by(dataset) %>% -slice(1:6) %>% -ungroup() +# Add the custom linetype values to this plot +scale_linetype_manual(values = ltys) + +# Remove the y-axis label +ylab("") + +# Add a y-axis label +ylab("Time") + +# 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" ) + -# Add a vertical line for each timestamp +# 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" +) +levels(scored_clusters_gg$individual_initiated) +# 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") +ggplot(data = scored_clusters_gg3) + +# Add a vertical line for each non-perching event assigned to the first individual geom_segment( -aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), +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.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") -ggplot(data = rfid_combined %>% -group_by(dataset) %>% -slice(1:3) %>% -ungroup() -) + -# Add a vertical line for each timestamp +# Add a vertical line for each non-perching event assigned to the first individual geom_segment( -aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), +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.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") -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" -) -) %>% -# Order the data frame by timestamps. The -desc() inside of arrange() means that the timestamps will be arranged in increasing order -dplyr::arrange(-desc(timestammp_ms)) -glimpse(rfid_combined) -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" -) -) %>% -# Order the data frame by timestamps. The -desc() inside of arrange() means that the timestamps will be arranged in increasing order -dplyr::arrange(-desc(timestamp_ms)) -glimpse(rfid_combined) -# Change the column dataset to data type "factor" -# By specifying "raw" first in the argument levels, you are reordering the factor levels so that "raw" comes first -rfid_combined <- rfid_combined %>% -dplyr::mutate( -dataset = factor(dataset, levels = c("raw", "pre-processed")) -) -# The dataset column is now type "fct", or "factor" -glimpse(rfid_combined) -# The levels of the factor column are ordered with "raw" first, rather than in alphabetical order -levels(rfid_combined$dataset) -ggplot(data = rfid_combined) + -# Add a vertical line for each timestamp +# Add the custom linetype values to this plot +scale_linetype_manual(values = ltys) + +# Remove the y-axis label +ylab("") + +# Add a y-axis label +ylab("Time") + +# 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 the perching events, and now encode color through the column individual_initiated geom_segment( -aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), +data = scored_clusters_gg3 %>% +dplyr::filter(!is.na(perching_sensor)), +aes(x = start_gg, y = 1.2, xend = end_gg, yend = 1.3, color = individual_initiated), +linewidth = 0.8 +) + +# Add the custom colors +scale_color_manual(values = cols) +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.3 ) + -scale_color_manual(values = c("orange", "darkgreen")) -ggplot(data = rfid_combined) + -# Add a vertical line for each timestamp +# Add a vertical line for each non-perching event assigned to the first individual geom_segment( -aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), +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.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") -ggplot(data = rfid_combined %>% -group_by(dataset) %>% -slice(1:3) %>% -ungroup() +# Add the custom linetype values to this plot +scale_linetype_manual(values = ltys) + +# Remove the y-axis label +ylab("") + +# Add a y-axis label +ylab("Time") + +# 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 a vertical line for each timestamp +# Add the perching events, and now encode color through the column individual_initiated geom_segment( -aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), +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 = 0.8 +) + +# Add the custom colors +scale_color_manual(values = cols) +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.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") -rfid_combined %>% -group_by(dataset) %>% -slice(1:3) -ggplot(data = rfid_combined %>% -group_by(dataset) %>% -slice(1:2) %>% -ungroup() -) + -# Add a vertical line for each timestamp +# Add a vertical line for each non-perching event assigned to the first individual geom_segment( -aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), +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.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") -ggplot(data = rfid_combined %>% -group_by(dataset) %>% -slice(1:2) %>% -ungroup() +# Add the custom linetype values to this plot +scale_linetype_manual(values = ltys) + +# Remove the y-axis label +ylab("") + +# Add a y-axis label +ylab("Time") + +# 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 the perching events, 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.3, color = individual_initiated), +linewidth = 4 +) + +# Add the custom colors +scale_color_manual(values = cols) +?geom_segment +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.3 ) + -# Add a vertical line for each timestamp +# Add a vertical line for each non-perching event assigned to the first individual geom_segment( -aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), +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.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") -ggplot(data = rfid_combined %>% -group_by(dataset) %>% -slice(1:2) %>% -ungroup() +# Add the custom linetype values to this plot +scale_linetype_manual(values = ltys) + +# Remove the y-axis label +ylab("") + +# Add a y-axis label +ylab("Time") + +# 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 a vertical line for each timestamp +# Add the perching events, and now encode color through the column individual_initiated geom_segment( -aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), +data = scored_clusters_gg3 %>% +dplyr::filter(!is.na(perching_sensor)), +aes(x = start_gg, y = 1.2, xend = end_gg, yend = 1.3, color = individual_initiated), +linewidth = 4, lineend = "round" +) + +# Add the custom colors +scale_color_manual(values = cols) +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.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") -ggplot(data = rfid_combined) + -# Add a vertical line for each timestamp +# Add a vertical line for each non-perching event assigned to the first individual geom_segment( -aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), +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.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 +# Add the custom linetype values to this plot +scale_linetype_manual(values = ltys) + +# Remove the y-axis label +ylab("") + +# Add a y-axis label +ylab("Time") + +# 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 the perching events, and now encode color through the column individual_initiated geom_segment( -data = rfid_perch, -aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5), -color = "blue", +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 = 4, lineend = "round" +) + +# Add the custom colors +scale_color_manual(values = cols) +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.3 -) -gg <- ggplot(data = rfid_combined) + -# Add a vertical line for each timestamp +) + +# Add a vertical line for each non-perching event assigned to the first individual geom_segment( -aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), +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.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 +# Add the custom linetype values to this plot +scale_linetype_manual(values = ltys) + +# Remove the y-axis label +ylab("") + +# Add a y-axis label +ylab("Time") + +# 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 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) +ggplot(data = scored_clusters_gg3) + +# Add a vertical line for each non-perching event assigned to the first individual geom_segment( -data = rfid_perch, -aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5), -color = "blue", +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.3 ) + +# 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.3 +) + +# Add the custom linetype values to this plot +scale_linetype_manual(values = ltys) + +# Remove the y-axis label +ylab("") + +# Add a y-axis label +xlab("Time") + +# 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" -) -gg -gg <- gg + -# Change the x and y axis labels -xlab("Date and time") + -# The y-axis does not contain information right now, so it can be blank +) + +# 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 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) +# Update the dataset in the base layer of the plot, which will be used by all layers that do not have a specific data argument themselves +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.3 +) + +# 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.3 +) + +# Add the custom linetype values to this plot +scale_linetype_manual(values = ltys) + +# Remove the y-axis label ylab("") + +# Add a y-axis label +xlab("Time") + # 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" -) -gg -gg -# Save the image file to your computer -ggsave(file.path(path, "raw_processed_perching.tiff"), width = 8, height = 6, units = "in", dpi = 300) -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") -# 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") -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") -scored_clusters <- read.csv(file.path(path, "Data/processed", "scored_detectionClusters.csv")) -glimpse(scored_clusters) -scored_clusters %>% -group_by(day, direction_scored) %>% -dplyr::summarise( -n = n() -) -scored_clusters %>% -dplyr::mutate( -day = lubridate::day(start) -) %>% -group_by(day, direction_scored) %>% -dplyr::summarise( -n = n() -) -scored_clusters %>% -# Extract the day from each timestamp and make a new column with this information -dplyr::mutate( -day = lubridate::day(start) -) %>% -# Drop rows with missing values for 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) %>% -# The number of rows here is the number of exits or entrances scored per day -dplyr::summarise( -n = n() +) + +# 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" ) diff --git a/R/vignettes/README.md b/R/vignettes/README.md index 9b21729..5c0630e 100644 --- a/R/vignettes/README.md +++ b/R/vignettes/README.md @@ -30,6 +30,7 @@ Things that would be important to add: - Errors and troubleshooting online - Color code chunks that are for testing code versus chunks that are for creating necessary objects (https://bookdown.org/yihui/rmarkdown-cookbook/chunk-styling.html) - an explanation about how simulating the data replaces the process that ABISSMAL does to collect data from live animals +- NA missing values I want to make a pre- and post-vignette Google form to asses the content and style of the vignettes for disseminating basic R coding skills in a biological context diff --git a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd index f156f12..5ddf3b7 100644 --- a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd +++ b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd @@ -144,6 +144,9 @@ In order to encode colors and line types correctly in the plot, you need to modi 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 day is the first day of the month), the value you want to add to the vector if the condition is true ("Day 1" of data collection), and then the value that you want to add if the condition is false ("Day 2" of data collection, since there are only 2 dates in each dataset). ```{r} +# is.na() returns TRUE when it encounters NAs inside of a vector (or column), and FALSE when a value inside of the vector is not NA +is.na(scored_clusters$individual_initiated) + # All NAs in this column are converted to "unassigned", but all other values were not changed ifelse(test = is.na(scored_clusters$individual_initiated), yes = "unassigned", no = scored_clusters$individual_initiated) @@ -321,7 +324,7 @@ glimpse(scored_clusters_gg3) ``` -Now you can update the plotting code to change the aesthetics of the x-axis using the function `scale_x_datetime()` to specify that you want x-axis labels every half hour. Also add an x-axis title: +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, make the vertical line segments thicker, and remove the y-axis grid lines inside of each panel: ```{r} # Update the dataset in the base layer of the plot, which will be used by all layers that do not have a specific data argument themselves @@ -333,7 +336,7 @@ ggplot(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.3 + linewidth = 0.5 ) + # Add a vertical line for each non-perching event assigned to the first individual @@ -342,7 +345,7 @@ ggplot(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.3 + linewidth = 0.5 ) + # Add the custom linetype values to this plot @@ -359,10 +362,13 @@ ggplot(data = scored_clusters_gg3) + # Use aesthetics functions to remove the y-axis labels and ticks # Add an argument to change where the legend is located in the plot + # Then remove the y-axis grid lines (major and minor) inside of each panel theme( axis.text.y = element_blank(), axis.ticks.y = element_blank(), - legend.position = "top" + legend.position = "top", + panel.grid.major.y = element_blank(), + panel.grid.minor.y = element_blank() ) + # Facet the plot by day (e.g. create a panel per day) @@ -388,7 +394,7 @@ ggplot(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.3 + linewidth = 0.5 ) + # Add a vertical line for each non-perching event assigned to the first individual @@ -397,7 +403,7 @@ ggplot(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.3 + linewidth = 0.5 ) + # Add the custom linetype values to this plot @@ -414,10 +420,13 @@ ggplot(data = scored_clusters_gg3) + # 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) from each panel theme( axis.text.y = element_blank(), axis.ticks.y = element_blank(), - legend.position = "top" + legend.position = "top", + panel.grid.major.y = element_blank(), + panel.grid.minor.y = element_blank() ) + # Facet the plot by day (e.g. create a panel per day) @@ -441,227 +450,95 @@ ggplot(data = scored_clusters_gg3) + # Add the custom colors scale_color_manual(values = cols) - ``` -TKTK the color legend is added by default with the new geom_segment call, so make only minor changes from here on - - - -Then end with adding the legend for color. - - -Finally, you need to add a legend to this plot so you can communicate what the two different colors mean. Adding this second legend to this plot is tricky because you manually coded the colors of the line segments in two separate uses in `geom_segment()`. `ggplot2` functions have specific requirements for making a legend, and they can require automated coding of aesthetic values like color. - -In order to automatically create a legend in a plot, it helps to have a layer of the plot in which color is automatically encoded. The way that you would do this is to move the `color` argument inside of the function `aes()` for one layer of the plot, and then supply `color` with a column of data type `factor`...TKTK continue +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 legend. 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 legend, you'll use the argument `legend.margin` inside of the function `theme()`. ```{r} -all_sensors <- rfid_raw3 %>% - bind_rows( - irbb_raw3 - ) %>% - dplyr::mutate( - sensor_id = factor(sensor_id) - ) - -glimpse(all_sensors) - -``` - -Then make the plot. -```{r} - -ggplot() + +gg <- ggplot(data = scored_clusters_gg3) + - # Add a vertical line for each RFID timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( - data = rfid_raw3, - aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 + 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 beam breaker timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( - data = irbb_raw3, - aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 + 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 fake layer of lines so that you can create a legend - # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below - geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + - - # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding - guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + + # Add the custom linetype values to this plot + scale_linetype_manual(values = ltys) + - # Facet the plot by day (e.g. create a panel per day) - # Use the new day labels here - facet_wrap(~ day_label, nrow = 2, strip.position = "left") + + # Remove the y-axis label + ylab("") + - # Change the x and y axis labels + # Add a y-axis label xlab("Time") + - # Now you can add a y-axis label - ylab("Day of data collection") + - - # Change the aesthetics of the x-axis labels - scale_x_datetime( - date_breaks = "30 mins", - date_labels = "%H:%M" - ) + - # 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) from each panel + # Increase the legend text size theme( axis.text.y = element_blank(), axis.ticks.y = element_blank(), - legend.position = "top" - ) - -``` - -You added a legend to the plot, but the colors in the legend don't line up with the colors of the line segments per sensor. You can use the function `scale_color_manual()` to match colors between the plot and legend. -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = rfid_raw3, - aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 + 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") ) + - # Add a vertical line for each beam breaker timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = irbb_raw3, - aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) + - - # Add the fake layer of lines so that you can create a legend - # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below - geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + - - # Set the colors that will be used to color the lines in the legend itself - # The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column - # If you do not supply your own color values, then R will assign default colors to each factor level - scale_color_manual(values = c("blue", "orange")) + - - # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding - guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + - # Facet the plot by day (e.g. create a panel per day) # Use the new day labels here - facet_wrap(~ day_label, nrow = 2, strip.position = "left") + - - # Change the x and y axis labels - xlab("Time") + - - # Now you can add a y-axis label - ylab("Day of data collection") + - + 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" ) + - # Use this function to convert the plot background to black and white - theme_bw() + + # 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" + ) + - # 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" + # Add the custom colors + scale_color_manual(values = cols) + + + guides( + linetype = guide_legend(title = "Direction"), + color = guide_legend(title = "Individual") ) +gg + ``` -Now that you've made the plot in R, you can save the plot as an image file on your computer using the function `ggsave()`. +Finally, you can save this plot as an image file: ```{r} -ggplot() + - - # Add a vertical line for each RFID timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = rfid_raw3, - aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = irbb_raw3, - aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) + - - # Add the fake layer of lines so that you can create a legend - # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below - geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + - - # Set the colors that will be used to color the lines in the legend itself - # The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column - # If you do not supply your own color values, then R will assign default colors to each factor level - scale_color_manual(values = c("blue", "orange")) + - - # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding - guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + - - # Facet the plot by day (e.g. create a panel per day) - # Use the new day labels here - facet_wrap(~ day_label, nrow = 2, strip.position = "left") + - - # Change the x and y axis labels - xlab("Time") + - - # Now you can add a y-axis label - ylab("Day of data collection") + - - # Change the aesthetics of the x-axis labels - scale_x_datetime( - date_breaks = "30 mins", - date_labels = "%H:%M" - ) + - - # 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" - ) +gg # Save the image file to your computer -ggsave(file.path(path, "raw_detections.tiff"), width = 8, height = 6, units = "in", dpi = 300) +ggsave(file.path(path, "behavioral_inferences.tiff"), width = 8, height = 6, units = "in", dpi = 300) ``` -You just learned how to make a plot in R, and also how to save it to your computer. 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. \ No newline at end of file +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. \ No newline at end of file From d199dfce852729399d4b9b005cda80dee0e9f84f Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Tue, 30 Jan 2024 13:59:25 -0500 Subject: [PATCH 25/69] Finished draft of sixth vignette --- .../Vignette_06_FinishAnalysisPipeline.Rmd | 211 ++++++++---------- 1 file changed, 95 insertions(+), 116 deletions(-) diff --git a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd index 5ddf3b7..d863e6c 100644 --- a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd +++ b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd @@ -201,16 +201,16 @@ ggplot() + dplyr::filter(individual_initiated == "1357aabbcc"), aes(x = start, y = 0, xend = end, yend = 0.5, linetype = direction_scored), color = "orange", - linewidth = 0.3 + linewidth = 0.5 ) + - # Add a vertical line for each non-perching event assigned to the first individual + # 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.3 + linewidth = 0.5 ) + # Add the custom linetype values to this plot @@ -265,7 +265,7 @@ Now you can update the code to include the new date labels: ```{r} # Add the data frame as the default dataset for the base layer plot, so that the facet_wrap() layer below has data to plot -ggplot(data = scored_clusters_gg) + +ggplot(data = scored_clusters_gg2) + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( @@ -273,16 +273,16 @@ ggplot(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.3 + linewidth = 0.5 ) + - # Add a vertical line for each non-perching event assigned to the first individual + # 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.3 + linewidth = 0.5 ) + # Add the custom linetype values to this plot @@ -306,6 +306,7 @@ ggplot(data = scored_clusters_gg) + # 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. @@ -324,11 +325,10 @@ glimpse(scored_clusters_gg3) ``` -Now you can update the plotting code to change the aesthetics of the x-axis using the function `scale_x_datetime()` to specify that you want x-axis labels every half hour. Also add an x-axis title, make the vertical line segments thicker, and remove the y-axis grid lines inside of each panel: +Now you can update the plotting code to change the aesthetics of the x-axis using the function `scale_x_datetime()` to specify that you want x-axis labels every half hour. Also add an x-axis title, and remove the y-axis grid lines inside of each panel: ```{r} -# Update the dataset in the base layer of the plot, which will be used by all layers that do not have a specific data argument themselves -ggplot(data = 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( @@ -339,7 +339,7 @@ ggplot(data = scored_clusters_gg3) + linewidth = 0.5 ) + - # Add a vertical line for each non-perching event assigned to the first individual + # 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"), @@ -354,90 +354,44 @@ ggplot(data = scored_clusters_gg3) + # Remove the y-axis label ylab("") + - # Add a y-axis label - xlab("Time") + - # 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 - # Then remove the y-axis grid lines (major and minor) inside of each panel 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.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" - ) - -``` - -There's still one more important piece of information to add to the plot: the perching events. You'll add these using `geom_segment()`, but you'll make these line segments short and thick, so that they appear more like dots on the plot: -```{r} - -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 an x-axis title + xlab("Time of day (HH:MM)") + - # Add the custom linetype values to this plot - scale_linetype_manual(values = ltys) + - - # Remove the y-axis label - ylab("") + - - # Add a y-axis label - xlab("Time") + - - # 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) from each panel + # Remove the y-axis grid lines (major and minor) inside of each panel 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() - ) + + ) + +gg - # 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" - ) + +There's still one more important piece of information to add to the plot: the perching events. You'll add these using `geom_segment()`, but you'll make these line segments short and thick, so that they appear more like dots on the plot: +```{r} + +gg <- gg + # Add the perching events as rounded segments, and now encode color through the column individual_initiated geom_segment( @@ -450,12 +404,50 @@ ggplot(data = scored_clusters_gg3) + # Add the custom colors 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 legend. 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 legend, you'll use the argument `legend.margin` inside of the function `theme()`. ```{r} -gg <- ggplot(data = scored_clusters_gg3) + +# 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 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 + +``` + +Finally, you can save this plot as an image file: +```{r} + +gg + +# Save the image file to your computer +ggsave(file.path(path, "behavioral_inferences.tiff"), width = 8, height = 6, units = "in", dpi = 300) + +``` + +You can continue to modify minor aesthetics to this image file to create a high-quality figure for a publication. For instance, you can change the final size of the image file (`width`, `height`), as well as the resolution (`dpi`). You can also change the size of the text on each axis and the axis titles, or the legend position, as you play around with the final image size. + +This is the all of the code used for the final plot, condensed and reorganized: +```{r} + +ggplot(data = scored_clusters_gg3) + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( @@ -466,7 +458,7 @@ gg <- ggplot(data = scored_clusters_gg3) + linewidth = 0.5 ) + - # Add a vertical line for each non-perching event assigned to the first individual + # 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"), @@ -475,22 +467,49 @@ gg <- ggplot(data = scored_clusters_gg3) + 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("") + - # Add a y-axis label - xlab("Time") + + # 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) from each panel - # Increase the legend text size + # 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(), @@ -499,46 +518,6 @@ gg <- ggplot(data = scored_clusters_gg3) + panel.grid.minor.y = element_blank(), legend.text = element_text(size = 10), legend.margin = margin(-1, -1, -1, -1, unit = "pt") - ) + - - # 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 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) + - - guides( - linetype = guide_legend(title = "Direction"), - color = guide_legend(title = "Individual") ) -gg - ``` - -Finally, you can save this plot as an image file: -```{r} - -gg - -# Save the image file to your computer -ggsave(file.path(path, "behavioral_inferences.tiff"), width = 8, height = 6, units = "in", dpi = 300) - -``` - -You can continue to modify minor aesthetics to this image file to create a high-quality figure for a publication. For instance, you can change the final size of the image file (`width`, `height`), as well as the resolution (`dpi`). You can also change the size of the text on each axis and the axis titles, or the legend position, as you play around with the final image size. \ No newline at end of file From b115ecf2d92c14f3aee1274ee6728a3bfeeaab8f Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Tue, 30 Jan 2024 15:28:38 -0500 Subject: [PATCH 26/69] Added some missing pieces of information to first 3 vignettes --- R/vignettes/README.md | 23 +++++-------- R/vignettes/Vignette_01_Introduction.Rmd | 22 ++++++++++--- R/vignettes/Vignette_02_Setup.Rmd | 33 ++++++++++++++++--- R/vignettes/Vignette_03_SimulateData.Rmd | 4 ++- .../Vignette_06_FinishAnalysisPipeline.Rmd | 33 +++++++++++++++++-- 5 files changed, 87 insertions(+), 28 deletions(-) diff --git a/R/vignettes/README.md b/R/vignettes/README.md index 5c0630e..12d6a46 100644 --- a/R/vignettes/README.md +++ b/R/vignettes/README.md @@ -7,15 +7,10 @@ Grace Smith-Vidaurre, PhD: gsvidaurre[at]Overview -The goal of these tutorials is to disseminate basic R coding skills in a biological context through the ABISSMAL behavioral tracking system. I want to have 5 short tutorials in English and Spanish. I want an Rmd file per tutorial in each language, plus an accompanying video of the screen as I work through each one (ideally I'll have a script of what I'll say per video too). +The goal of these tutorials is to disseminate basic R coding skills in a biological context through the ABISSMAL behavioral tracking system. I want to have 6 short tutorials in English and Spanish. I want an Rmd file per tutorial in each language, plus an accompanying video of the screen as I work through each one (ideally I'll have a script of what I'll say per video too). 1. Introduction to RStudio, download ABISSMAL GitHub repository, introduction to the ABISSMAL data processing / analysis workflow -- Need to add to the Intro how to report bugs in vignettes as GitHub issues -- Also add how to troubleshoot when an expression isn't complete in the console -- Tab completion, keyboard shortcuts -- The fact that there are many different ways to do any one thing in R, and this is meant to be one set of examples, not at all exhaustive - 2. Setup a virtual workspace (global environment, package installation, working directory, RMarkdown files) 3. Create simulated data, including entrances, exits, and perching events @@ -24,16 +19,14 @@ The goal of these tutorials is to disseminate basic R coding skills in a biologi 5. Combine the raw data, detect perching events and pre-process the raw data, make simple barcode plots of the raw and per-processed data, and highlight how to encode color in these plots -6. For vignette 06, I want to detect clusters, score clusters, generate summary statistics, and make complex barcode style visuals: 1) the raw combined RFID data, perching events and RFID pre-processed,2) IRBB raw combined and pre-processed, then 3) pre-processed RFID and IRBB together. All of these will be faceted plots with ggplot, and with a complicated legend +6. For vignette 06, I want to detect clusters, score clusters, generate summary statistics, and make a complex barcode style visuals + + +To do: -Things that would be important to add: -- Errors and troubleshooting online -- Color code chunks that are for testing code versus chunks that are for creating necessary objects (https://bookdown.org/yihui/rmarkdown-cookbook/chunk-styling.html) -- an explanation about how simulating the data replaces the process that ABISSMAL does to collect data from live animals -- NA missing values +- Update file descriptions in README -I want to make a pre- and post-vignette Google form to asses the content and style of the vignettes for disseminating basic R coding skills in a biological context +- Make a pre- and post-vignette Google form to asses the content and style of the vignettes for disseminating basic R coding skills in a biological context -Note that code for processing and analyzing biological data collected from zebra finches, and code to make figures, is published online as part of a submitted manuscript for the ABISSMAL tracking system. +- Translate text/comments in each vignette to Spanish -Also note that the knitted output for these tutorials must be visible from the ABISSMAL website. This is especially important for accessibility of the Introduction for people who haven't downloaded the ABISSMAL repo yet \ No newline at end of file diff --git a/R/vignettes/Vignette_01_Introduction.Rmd b/R/vignettes/Vignette_01_Introduction.Rmd index cbf56fe..1fac297 100644 --- a/R/vignettes/Vignette_01_Introduction.Rmd +++ b/R/vignettes/Vignette_01_Introduction.Rmd @@ -12,8 +12,12 @@ This first vignette will take you through a brief introduction to RStudio, downl 1. How to configure your RStudio session 2. How to create a local version of a GitHub repository 3. The data processing and analysis steps provided by the ABISSMAL R functions +4. Troubleshoot errors online +5. How to report bugs as GitHub issues -

1. Configure your RStudio session

+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 particular biological context. + +

Configure your RStudio session

If you are new to using R (a programming language for statistical analysis) and RStudio (a graphical user interface for R), then I recommend checking out the introductory lesson [R for Reproducible Scientific Analysis](https://swcarpentry.github.io/r-novice-gapminder/) provided by [Software Carpentry](https://software-carpentry.org/). @@ -43,7 +47,7 @@ Another useful change to the RStudio configuration is soft-wrapping lines of tex You can also play with 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. -

2. Create a local version of an existing GitHub repository

+

Create a local version of an existing GitHub repository

If you are new to using GitHub, then I recommend downloading [GitHub Desktop](https://desktop.github.com/). This software is a graphical user interface for the GitHub version control platform. We will work through how to download the GitHub repository for ABISSMAL so that you can access the ABISSMAL R functions on your local computer. If you are already familiar with GitHub and using Git through the command line, then check out the installation instructions on the [ABISSMAL README](https://github.com/lastralab/ABISSMAL). @@ -74,7 +78,7 @@ Once the repository has been installed on your computer, you should see the foll 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 R vignettes as well as automated unit testing scripts. -

3. ABISSMAL data processing and analysis

+

ABISSMAL data processing and analysis

ABISSMAL provides 5 different R functions for data processing and analysis. These functions are briefly described below in the order in which they should generally be used. The functions are also described in more detail in the R folder README as well as the accompanying methods manuscript: @@ -92,6 +96,16 @@ ABISSMAL provides 5 different R functions for data processing and analysis. Thes ![Figure 4 from the ABISSMAL methods manuscript under review, that shows a general workflow across these 5 functions](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/Figure4_ComputationalAnalyses.png) * Figure from Smith-Vidaurre, G., Molina, T., Jarvis, E.D., and E.A. Hobson. 2023. Automated tracking of avian parental care. Under review. -In the next vignette, we will create simulated datasets of raw movement data to learn how to use the different functions described above. +

Troubleshoot errors online

+ +You will run into errors while writing and running code, and while this can be frustrating, errors are an important part of the overall learning experience. Errors can arise for many different reasons, including typos that you introduced while writing code, issues with package versions that you installed, or perhaps missing packages. Sometimes, errors may be due to issues with the custom ABISSMAL functions that you will use in the subsequent vignettes, or issues with the code in the vignettes themselves. + +Whenever you run into an error with your code, it's important to troubleshoot the error and figure out out how to solve it before assuming that the error is due to the custom ABISSMAL functions or issues with the 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 if the error is associated with a specific package or function. + +Once you have thoroughly researched a given error, and you're sure that the error you're getting isn't due to a typo or issues with data structure on your end, then it's time to consider whether the error is due to a bug in a custom ABISSMAL function or the code provided in a vignette. These are bugs that you can report to the ABISSMAL developers on GitHub (see below). + +

Report bugs on GitHub

+As you work through each vignette, you may encounter "bugs" or errors with the code, including code that does not work or that yields incorrect outcomes. These bugs can occur with the ABISSMAL functions themselves, or with the code in a given vignette. When you encounter a bug with an ABISSMAL function, you can create an Issue through the GitHub repository. To create a new Issue, you can select "New Issue" on [Issues page](https://github.com/lastralab/ABISSMAL/issues) of the main ABISSMAL repository, then follow the instructions in the Issue template to add the information needed for the code developers to address the bug effectively. You can also add the tag "bug" to your issue. If you encounter an issue with the ABISSMAL vignettes, then you can submit an issue through the GitHub repository for these vignettes. +In the next vignette, we will create simulated datasets of raw movement data to learn how to use the different functions described above. \ No newline at end of file diff --git a/R/vignettes/Vignette_02_Setup.Rmd b/R/vignettes/Vignette_02_Setup.Rmd index 6e69d96..661b942 100644 --- a/R/vignettes/Vignette_02_Setup.Rmd +++ b/R/vignettes/Vignette_02_Setup.Rmd @@ -21,15 +21,14 @@ In this second vignette, we will set up a virtual workspace for coding sessions 4. Learning about R functions and documentation 5. Installing and loading packages 6. Commenting code -7. Creating and using a working directory +7. Coding shortcuts in RStudio +8. Creating and using a working directory

Using RMarkdown files

Each vignette in this series is available as an RMarkdown file (extension .Rmd) and as an HTML file that can be viewed in your default Internet browser. Each HTML file was generated by knitting the output of an RMarkdown file. You can check out the [RMarkdown documentation](https://rmarkdown.rstudio.com/lesson-1.html) for more information about how to use this file format to write code and knit the code output into reports. -RMarkdown files facilitate sharing your code and the output of your code with others. If you're new to using RMarkdown, there are some different ways that you can use these files to work through the series of vignettes. First, 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. - -However, 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 yourself and also write out 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 have the vignette RMarkdown file and your own RMarkdown file side by side in RStudio by adding a third column in the pane layout: +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 have the vignette RMarkdown file and your own RMarkdown file side by side in RStudio by adding a third column in the pane layout: * Tools * Global Options @@ -37,6 +36,8 @@ However, the best way to work through these vignettes will be to create a new RM * Select the Add Column option to add another Source pane in a third column * Open the original vignette RMarkdown file in one source pane, and your own RMarkdown file in the other source pane +If you're already familiar with writing R code and RMarkdown files, then you can open the original RMarkdown file for each vignette and run the code inside each file as you work through each vignette (with some modification of paths to reflect folders and files on your local computer). If you want to preserve the original code in each RMarkdown file, you can create a copy of each vignette and modify the copies instead. +

Clean your global environment

Your global environment is your virtual workspace in R, and can hold different packages and objects to facilitate your data analysis and programming objectives. You can see the different packages and objects currently loaded in your environment by clicking on the tab "Environment" in the pane that also includes the "History" and "Connections" tab. @@ -62,7 +63,7 @@ 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. 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. +The code above to clean your global environment is a nested expression with 2 functions: `rm()` and `ls()`. The `()` notation is used for functions. 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.

Access R function documentation

@@ -104,6 +105,28 @@ library(tidyverse) ``` +

Coding shortcuts

+ +RStudio has a number of useful keyboard shortcuts for writing code. You can find these shortcuts by going to Tools, 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 lines of code or create new comments, and "Ctrl + Alt + I", which automatically creates a new RMarkdown chunk. + +Another useful type of shortcut provided by RStudio is tab completion of code. For instance, in the chunk below, after writing `libr` and pressing Tab, you should see a small popup window appear that lists all of the available functions, packages, or objects that start with the pattern "libr". You can use the arrow keys to select the option that you'd like and hit Enter to complete the line (for instance, to write `library()` for loading a package). +```{r eval = FALSE} + +libr + +``` + +

Troubleshooting incomplete statements

+ +As you write and run code in RStudio, it's important to keep an eye on the state of the console. When you see the symbol `>` in the console, this means that the console finished running code and is ready for another operation. However, when you see the symbol `+` in the console, this means that the statement you just ran was incomplete. Incomplete lines of code can occur when you have a typo so that a statement isn't fully closed, like when you miss an opening or closing parenthesis in a function. Below is an example of an incomplete statement: +```{r eval = FALSE} + +library(tidyverse + +``` + +When you run the code above, you should see a `+` symbol 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 ready the console for additional code. It's good practice to keep an eye on the console to check that the code you're running is producing output. If you run a lot of code and don't see the output that you expect, it may be because you included an incomplete statement, and it would be better to clean the console and check your code before running it again. +

Create your working directory

The next important step for setting up your virtual workspace is to create (or decide on) your working directory. A directory is a location (like a folder) on your computer. A working directory is the location on your computer where R will search for files to read in, and also where the program will save files that you write out. diff --git a/R/vignettes/Vignette_03_SimulateData.Rmd b/R/vignettes/Vignette_03_SimulateData.Rmd index de834aa..e0b54e3 100644 --- a/R/vignettes/Vignette_03_SimulateData.Rmd +++ b/R/vignettes/Vignette_03_SimulateData.Rmd @@ -18,7 +18,9 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE)

Vignette Overview and Learning Objectives

-In this third vignette, we will create simulated datasets of animal movements detected by different sensors. Throughout the process of creating these simulated datasets, you will continue to use coding skills that you learned in vignette 02, and you will learn additional skills that include: +In this third vignette, we will create simulated datasets of animal movements detected by different sensors. Simulating these datasets replaces the data collection process that ABISSMAL undertakes in order to collect data from live animals. Using 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 code used to analyze those datasets, then check out the [methods manuscript preprint](https://ecoevorxiv.org/repository/view/6268/) that has links to publicly available data and code. + +Throughout the process of creating simulated datasets in this vignette, you will continue to use coding skills that you learned in vignette 02, and you will learn additional skills that include: 1. Creating objects like vectors and data frames 2. Data types in R diff --git a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd index d863e6c..d6ac2ba 100644 --- a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd +++ b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd @@ -78,7 +78,35 @@ glimpse(scored_clusters) ``` -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 1) make a new column with information about the day per timestamp, 2) drop rows with missing data for direction scored, 3) group the data frame by day and direction scored, and 4) count the number of rows per group. +How many entrances and exits were scored per day? + +

Missing data in R

+ +In order to count the number of each of these events scored per day, you need to know how to account for missing data in R. Missing data is often indicated using the value `NA` or "not available", which is a specific type of logical value. 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 column. +```{r} + +?is.na() + +x <- c(1, NA, 2, 3, NA) + +is.na(x) + +``` + +In the code above, you created a vector called `x` that has 2 NA values. The function `is.na()` checks whether each element of `x` is equivalent to `NA`, and returns `TRUE` when that condition is met. + +Because `is.na()` is a conditional statement, you can also use other special symbols relevant to conditional statements, such as the `!` symbol, which will invert a conditional statement. For instance, in the code below, by adding `!` before `is.na()`, you're now asking whether each element of `x` is *not* equivalent to NA: +```{r} + +!is.na(x) + +``` + +As you can see, adding the `!` infront 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` along a given column, and will retain a row whenever it encounters a value of `TRUE`. So 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`. + +

Count events per day

+ +Now you can write code to count the number of entrance and exit events scored per day. You will need to 1) make a new column with information about the day per timestamp, 2) drop rows with missing data for direction scored, 3) group the data frame by day and direction scored, and then 4) count the number of rows per group. ```{r} scored_clusters %>% @@ -86,8 +114,7 @@ scored_clusters %>% dplyr::mutate( day = lubridate::day(start) ) %>% - # Drop rows with missing values for direction_scored - # 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 this function, 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) + # 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) %>% From 6b75a63898a0053736cfb3c8c914ff3b6b1f6602 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Tue, 30 Jan 2024 15:29:46 -0500 Subject: [PATCH 27/69] deleted files --- R/vignettes/Vignette_02_Setup.html | 634 ----- R/vignettes/Vignette_03_SimulateData.html | 2059 ----------------- R/vignettes/Vignette_04_SaveData.html | 1170 ---------- .../Vignette_07_FinalAnalysesPlots.Rmd | 739 ------ 4 files changed, 4602 deletions(-) delete mode 100644 R/vignettes/Vignette_02_Setup.html delete mode 100644 R/vignettes/Vignette_03_SimulateData.html delete mode 100644 R/vignettes/Vignette_04_SaveData.html delete mode 100644 R/vignettes/Vignette_07_FinalAnalysesPlots.Rmd diff --git a/R/vignettes/Vignette_02_Setup.html b/R/vignettes/Vignette_02_Setup.html deleted file mode 100644 index f3b5527..0000000 --- a/R/vignettes/Vignette_02_Setup.html +++ /dev/null @@ -1,634 +0,0 @@ - - - - - - - - - - - - - - - -Vignette 02: Setup - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - -

-Vignette Overview and Learning Objectives -

-

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

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

-Using RMarkdown files -

-

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

-

RMarkdown files facilitate sharing your code and the output of your -code with others. If you’re new to using RMarkdown, there are some -different ways that you can use these files to work through the series -of vignettes. First, 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.

-

However, 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 yourself and also write out -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 have the -vignette RMarkdown file and your own RMarkdown file side by side in -RStudio by adding a third column in the pane layout:

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

-Clean your global environment -

-

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

-

If you just 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. 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.

-

-Access R function documentation -

-

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

-
?rm
-
?ls
-

For each function, the documentation holds specific sections that can -be useful for understanding how the function works, especially the -function’s arguments. A function’s arguments are the values that it -expects the user to provide in order to 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 instance, the function rm() has an argument called -list (followed by an =). 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 lists -the names of all of the objects in your global environment.

-

-Install and load packages -

-

After cleaning the global environment, you still need to set up your -virtual workspace for the upcoming coding session. One important setup -step 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 load the package into your -global environment in order to access functions across the tidyverse -packages:

-
library(tidyverse)
-

-Create your working directory -

-

The next important step for setting up your virtual workspace is to -create (or decide on) your working directory. A directory is a location -(like a folder) on your computer. A working directory is the location on -your computer where R will search for files to read in, and also where -the program will save files that you write out.

-

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. -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 the directory 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 these paths to reflect 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 string (a formal term for text characters 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")
-

This 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 just copied to your -working directory:

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

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

- - - - -
- - - - - - - - - - - - - - - diff --git a/R/vignettes/Vignette_03_SimulateData.html b/R/vignettes/Vignette_03_SimulateData.html deleted file mode 100644 index d179ea0..0000000 --- a/R/vignettes/Vignette_03_SimulateData.html +++ /dev/null @@ -1,2059 +0,0 @@ - - - - - - - - - - - - - - - -Vignette 03: Simulate Data - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-
-
-
-
- -
- - - - - - - -

-Vignette Overview and Learning Objectives -

-

In this third vignette, we will create simulated datasets of animal -movements detected by different sensors. Throughout the process of -creating these simulated datasets, you will continue to use coding -skills that you learned in vignette 02, and you will learn additional -skills that include:

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

-Load packages -

-

In the previous vignette, you installed a set of data science -packages called the tidyverse. You also learned about -working directories and created a directory on your computer to save -data that you generate from this and subsequent vignettes.

-

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

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

-Create a path object -

-

The next setup step is to specify your working directory. In vignette -02, you used a string (text contained in double quotes to indicate -character information) to specify your working directory while using -different functions. Using the same long character string over and over -is not very efficient, so you can instead save the character string for -your working directory inside a new object in R.

-

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

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

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

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

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

-

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

-

-Create timestamps of simulated detections of animal movements -

-

The primary data collected by the ABISSMAL tracking system are -timestamps indicating the moment of time when a movement sensor -triggered. 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 and 1 pair of infrared beam breakers is mounted in -front of the RFID antenna. In this simulated setup, the infrared beam -breakers will trigger first when a bird enters the nest container, and -the RFID antenna should trigger first when a bird leaves the nest -container.

-

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

-

These timestamps are supplied to the function c(). This -function 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 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 events. We can -choose timestamps for the infrared beam breakers that precede the RFID -timestamps to simulate an entrance event, and infrared beam breaker -timestamps that follow the RFID timestamps to simulate an exit event. We -will offset detections from each sensor within the entrance and exit -events by 1 second.

-
# Simulate timestamps for an entrance, an exit, and then another entrance and exit
-irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01")
-

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 trigger when birds leave nesting material hanging in -the entrance of the container. In both of these cases, the infrared beam -breakers should trigger when the RFID antenna does not.

-
# Simulate some RFID detection failures
-irbb_ts <- c(irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25")
-
-glimpse(irbb_ts)
-
##  chr [1:8] "09:59:59" "10:05:01" "10:59:59" "11:05:01" "06:05:05" ...
-
# Simulate some stray beam breaker detections
-irbb_ts <- c(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(irbb_ts)
-
##  chr [1:19] "09:59:59" "10:05:01" "10:59:59" "11:05:01" "06:05:05" ...
-

You’ve created simulated datasets of detections of animal movements, -but currently, these datasets are in vector format only 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 identity of each PIT tag detected.

-

Right now, the data for both sensors is in vector format. 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
-c("1", 1, TRUE, FALSE)
-
## [1] "1"     "1"     "TRUE"  "FALSE"
-
# Create a vector with numeric and binary data types
-c(1, 1, TRUE, FALSE)
-
## [1] 1 1 1 0
-

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

-

-Create data frames with primary data and metadata -

-

You need to be able to create metadata of different data types that -can accompany the timestamps for each sensor. To do this, 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 different data types in the same data frame. You can also write -out data frames to spreadsheets that exist as physical files on your -computer.

-

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. You’ll also use -the function length() to get the length of -rfid_ts, and then feed that result to rep() to -set the number of times to repeat the information.

-
# 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 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 run code like this 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: -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 this conditional statement, you’re using the symbols -== to ask if the two vectors 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
-length(rfid_ts) != length(exp_rep)
-
## [1] FALSE
-

The vectors rfid_ts and exp_rep must be the -same length in order to combine these vectors into a data frame. To -check that this is true, you can use square bracket index to filter out -4 elements of the vector rfid_ts so that this vector is 10 -elements long.

-
# 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
-
# 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"
-

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(): “rep("2023", length(rfid_ts))”. The -slashes are symbols automatically used by R to ignore special symbols -(here those special symbols are the double quotes, which are used as -part of the column name rather than symbols to indicate the character -data type). You can use the function names() and square -bracket indexing to change the column name:

-
# 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) # 3 columns in this data frame
-
## [1] 3
-
# This expression gets you the name of the last column
-names(sim_dats)[ncol(sim_dats)]
-
## [1] "rep(2023, length(rfid_ts))"
-
# Then you can overwrite the last column name with a new name
-names(sim_dats)[ncol(sim_dats)] <- "year"
-
-# Confirm that the name was changed correctly
-names(sim_dats)
-
## [1] "replicate"  "timestamps" "year"
-
glimpse(sim_dats)
-
## Rows: 14
-## Columns: 3
-## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
-## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
-## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
-

-Create data frames using the tidyverse -

-

In the chunk above, you used base R code 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 a better name in fewer lines of -code here. 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. -Sometimes I use other packages that also have functions called -mutate(), and if I don’t specify which package I want to -use, then I end up with immediate errors (the code fails to run) or -worse, running 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 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
-
## 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 learned a new way to repeat a value within a -piping operation by using the notation ‘.’ inside 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 sim_dats
-  dplyr::mutate(
-    year = 2023
-  ) %>%
-  glimpse() %>% # See the structure of 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 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 (the output was printed to the -console only). In the next vignette, you will write out this simulated -RFID and IRBB data to spreadsheets and then you’ll use this data to -start the ABISSMAL data processing and analysis workflow.

- - - -
-
- -
- - - - - - - - - - - - - - - - diff --git a/R/vignettes/Vignette_04_SaveData.html b/R/vignettes/Vignette_04_SaveData.html deleted file mode 100644 index 69d614b..0000000 --- a/R/vignettes/Vignette_04_SaveData.html +++ /dev/null @@ -1,1170 +0,0 @@ - - - - - - - - - - - - - - - -Vignette 04: Process Data - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - -

-Vignette Overview and Learning Objectives -

-

In this fourth vignette, you will write out spreadsheets of simulated -detections of animal movements to your computer. You’ll begin using this -data in the ABISSMAL data processing and analysis workflow, including -combining raw data across days and pre-processing the raw data. You will -continue to use coding skills that you learned in the previous -vignettes, and you will learn additional skills that include:

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

-Load packages and your working directory path -

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

-Create the simulated data -

-

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

-
# Create a vector of 4 RFID timestamps in HH:MM:SS format
-rfid_ts <- c("10:00:00", "10:05:00", "11:00:00", "11:05:00")
-
-# Add perching events to the RFID data
-rfid_ts <- c(rfid_ts, "08:00:00", "08:00:01", "08:00:02", "08:00:03", "11:30:00", "11:30:01", "11:30:02", "11:30:03", "11:30:04", "11:30:05")
-
-glimpse(rfid_ts)
-
##  chr [1:14] "10:00:00" "10:05:00" "11:00:00" "11:05:00" "08:00:00" ...
-
# For the beam breakers, simulate timestamps for an entrance, an exit, and then another entrance and exit
-irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01")
-
-# Simulate some RFID detection failures for the beam breaker data
-irbb_ts <- c(irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25")
-
-# Simulate some stray beam breaker detections
-irbb_ts <- c(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(irbb_ts)
-
##  chr [1:19] "09:59:59" "10:05:01" "10:59:59" "11:05:01" "06:05:05" ...
-

-Simulate 2 days of RFID data collection -

-

In the code below, you’ll use 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 = "Pair_01", times = length(rfid_ts))
-
-# Make the data frame with the experimental replicate metadata and the timestamps
-sim_dats_rfid <- data.frame(replicate = 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
-  ) %>%
-  # Allocate the first half of the detections to the first individual using nrow(.)/2, and the second half of the detctions to the second individual using nrow(.)/2 again. Both of the rep() expressions are combined into a single vector using the c() function
-  dplyr::mutate(
-    PIT_tag = c(rep("1357aabbcc", nrow(.)/2), rep("2468zzyyxx", nrow(.)/2))
-  ) %>% 
-  dplyr::mutate(
-    sensor_id = "RFID"
-  )
-
-glimpse(sim_dats_rfid)
-
## Rows: 14
-## Columns: 7
-## $ 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
-## $ 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 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.

-
sim_dats_rfid <- sim_dats_rfid %>% 
-  bind_rows(
-    sim_dats_rfid %>% 
-      dplyr::mutate(
-        day = 02
-      )
-  )
-
-glimpse(sim_dats_rfid) # Double the number of rows, looks good
-
## Rows: 28
-## Columns: 7
-## $ 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, 8, 8, 8, 8, 8, 8,…
-## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2,…
-## $ PIT_tag    <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabbcc", "13…
-## $ sensor_id  <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
-

-Check data frame columns with base R -

-

You checked that simulated data frame has data collected over two -days 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
-
# 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
-
# 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) # Two days, looks good
-
## [1] 1 2
-
unique(sim_dats_rfid[["day"]]) # Two days, looks good
-
## [1] 1 2
-

-Check data frame columns with the tidyverse -

-

You can also check the unique values in a column using functions from -the tidyverse. In the expression below, you’re piping -sim_dats_rfid into the function pull(), which -lets you pull the column day out of the data frame as a -vector, and then that vector is piped into the function -unique() to check the unique values contained in the -vector. 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.

-
# Two days, looks good
-sim_dats_rfid %>% 
-  pull(day) %>% 
-  unique()
-
## [1] 1 2
-

-Simulate 2 days of beam breaker data collection -

-

Next, repeat this process of creating a data frame with metadata for -the infrared beam breaker dataset. 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 two days as the RFID -system.

-
# Overwrite the vector exp_rep with a new vector the same length as irbb_ts
-exp_rep <- rep(x = "Pair_01", times = length(irbb_ts))
-
-sim_dats_irbb <- data.frame(replicate = exp_rep, timestamps = irbb_ts)
-
-sim_dats_irbb <- sim_dats_irbb %>%
-  dplyr::mutate(
-    year = 2023,
-    month = 08,
-    day = 01,
-    sensor_id = "Beam breakers"
-  )
-
-glimpse(sim_dats_irbb)
-
## Rows: 19
-## Columns: 6
-## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
-## $ 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
-## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
-## $ sensor_id  <chr> "Beam breakers", "Beam breakers", "Beam breakers", "Beam br…
-
sim_dats_irbb <- sim_dats_irbb %>% 
-  bind_rows(
-    sim_dats_irbb %>% 
-      dplyr::mutate(
-        day = 02
-      )
-  )
-
-glimpse(sim_dats_irbb) # Double the number of rows, looks good
-
## Rows: 38
-## Columns: 6
-## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
-## $ 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, 2,…
-## $ sensor_id  <chr> "Beam breakers", "Beam breakers", "Beam breakers", "Beam br…
-
# Two days, looks good
-sim_dats_irbb %>% 
-  pull(day) %>% 
-  unique()
-
## [1] 1 2
-

-Save a data frame as a physical file -

-

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")
-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 the function will use 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. 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. In the second usage of list.files() below, -you’re using a new argument called pattern that makes it -possible to customize a search. Using the argument pattern -here is similar to searching for a specific word inside of a text -document. Below, 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 all files in the given path
-list.files(path)
-
-# List only files that end in the pattern ".csv" in the given path
-list.files(path, pattern = ".csv$")
-

-Read in a spreadsheet -

-

% glimpse()

-

-Now that you've practiced using `write.csv()` and `read.csv()`, you can delete the temporary file that you created by feeding the `rfid_file` object to the function `file.remove()`.
-
-```r
-rfid_file <- file.path(path, "test_file.csv")
-rfid_file
-
-file.remove(rfid_file)
-

-Save simulated data for the ABISSMAL workflow -

-

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

-

-Filter a data frame -

-

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

-
# Pipe the data frame into the filter() function
-sim_dats_rfid %>% 
-  # Filter the data frame by pulling out all rows in which the day column was equal to 1
-  dplyr::filter(day == 1) %>%
-  glimpse()
-
## Rows: 14
-## Columns: 7
-## $ 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
-## $ 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 can similar results by inverting the conditional statement -inside of dplyr::filter() to remove days that are not the -first day of data collection.

-
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) %>%
-  glimpse()
-
## Rows: 14
-## Columns: 7
-## $ 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
-## $ 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) %>%
-  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 the raw RFID data
-file.path(path, "RFID") # Check the new path
-dir.create(file.path(path, "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, "RFID", "test.csv")
-rfid_file
-
-sim_dats_rfid %>% 
-  dplyr::filter(day == 1) %>% 
-  # Write out the 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 by day 1
-  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, "RFID"), pattern = ".csv$")
-
-rfid_file <- file.path(path, "RFID", "test.csv")
-rfid_file
-
-file.remove(rfid_file)
-

-Practicing writing a loop -

-

In order to write out a data frame for each day of data collection -per sensor, you could repeat the code above 4 times (twice per sensor). -But it’s good to avoid repeating 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, "RFID", "test1.csv"), file.path(path, "RFID", "test2.csv"))
-
-files
-
## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/RFID/test1.csv"
-## [2] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/RFID/test2.csv"
-
length(files)
-
## [1] 2
-
# 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 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 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
-

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 and -setting x as the iterating variable will not affect other -lines of code outside of the loop.

-

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/RFID/test1.csv"
-## 
-## [[2]]
-## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/RFID/test2.csv"
-

You can also modify the code inside 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){
-  
-  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 .csv files were written out:

-
list.files(file.path(path, "RFID"))
-
## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
-## [2] "RFID_simulated_Pair-01_2023_08_02.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 add the data frame filtering that -you learned above to the loop. In the code below, you’ll create another -vector object called days, and then use the iterating -variable to filter sim_dats_rfid by each day in -days.

-
days <- c(1, 2)
-
-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.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. The string that -you passed 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, "RFID"), pattern = "^test", full.names = TRUE)
-rem_files
-
-file.remove(rem_files)
-

-Use a loop to save RFID spreadsheets -

-

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

-
# Make a vector of the custom file names to write out
-files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv")
-
-# Add the file path for the correct directory
-files <- file.path(path, "RFID", files) 
-files
-
-# Make a vector of the days to write out (1 day per iteration of the loop)
-days <- c(1, 2)
-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.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 as an object in R. 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.

-
# Both new .csv files for each day of RFID data are present, looks good
-list.files(file.path(path, "RFID"))
-

You can remove these files.

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

You can continue practicing loops by repeating this process for the -infrared beam breaker dataset.

-

-Write a nested loop to save spreadsheets -

-

If you want to cut down on the amount of code that you’re writing, -you could write out both the RFID and beam breaker data in the same -loop. First you’ll need to create another directory for the beam breaker -data:

-
dir.create(file.path(path, "IRBB"))
-

To carry out the file filtering and writing, you’ll use a type of -object called a list, and then use these lists inside of a -nested loop:

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

Lists are useful objects to use in R because they’re very flexible. -Unlike vectors, a single list can hold many different types of data. -Unlike a data frame, the elements of a list do not need to be the same -dimension. The elements of a list can also be different data structures -or object types. For instance, lists can contain vectors, data frames, -and other lists all inside of the same object. The list that you created -above has 2 elements, and each element is a vector of 2 strings -containing 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"
-
glimpse(files[1])
-
## List of 1
-##  $ : chr [1:2] "RFID_simulated_Pair-01_2023_08_01.csv" "RFID_simulated_Pair-01_2023_08_02.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"
-
glimpse(files[[1]])
-
##  chr [1:2] "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"),
-  `IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv")
-)
-
-glimpse(files)
-
## List of 2
-##  $ RFID: chr [1:2] "RFID_simulated_Pair-01_2023_08_01.csv" "RFID_simulated_Pair-01_2023_08_02.csv"
-##  $ IRBB: chr [1:2] "IRBB_simulated_Pair-01_2023_08_01.csv" "IRBB_simulated_Pair-01_2023_08_02.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"
-
# 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"
-
files[["RFID"]]
-
## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
-## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
-

Lists are useful data structures for nested loops. For instance, if -you want to write out a spreadsheet per day per sensor type, you need a -loop to 1) iterate over sensor types, and then a loop to 2) iterate over -days per sensor. You can use lists to create nested data structures to -supply to a nested loop to 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 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"),
-  `IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv")
-)
-
-files
-
## $RFID
-## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
-## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
-## 
-## $IRBB
-## [1] "IRBB_simulated_Pair-01_2023_08_01.csv"
-## [2] "IRBB_simulated_Pair-01_2023_08_02.csv"
-
# Make a list of file paths per sensor that will be used inside of the loop
-file_dirs <- list(
-  `RFID` = file.path(path, "RFID"),
-  `IRBB` = file.path(path, "IRBB") 
-)
-
-file_dirs
-
## $RFID
-## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/RFID"
-## 
-## $IRBB
-## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/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),
-  `IRBB` = c(1, 2)
-)
-
-days
-
## $RFID
-## [1] 1 2
-## 
-## $IRBB
-## [1] 1 2
-
# 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, and you only need to specify a data frame per sensor type here
-dats <- list(
-  `RFID` = sim_dats_rfid,
-  `IRBB` = sim_dats_irbb
-)
-
-glimpse(dats)
-
## List of 2
-##  $ RFID:'data.frame':    28 obs. of  7 variables:
-##   ..$ replicate : chr [1:28] "Pair_01" "Pair_01" "Pair_01" "Pair_01" ...
-##   ..$ timestamps: chr [1:28] "10:00:00" "10:05:00" "11:00:00" "11:05:00" ...
-##   ..$ year      : num [1:28] 2023 2023 2023 2023 2023 ...
-##   ..$ month     : num [1:28] 8 8 8 8 8 8 8 8 8 8 ...
-##   ..$ day       : num [1:28] 1 1 1 1 1 1 1 1 1 1 ...
-##   ..$ PIT_tag   : chr [1:28] "1357aabbcc" "1357aabbcc" "1357aabbcc" "1357aabbcc" ...
-##   ..$ sensor_id : chr [1:28] "RFID" "RFID" "RFID" "RFID" ...
-##  $ IRBB:'data.frame':    38 obs. of  6 variables:
-##   ..$ replicate : chr [1:38] "Pair_01" "Pair_01" "Pair_01" "Pair_01" ...
-##   ..$ timestamps: chr [1:38] "09:59:59" "10:05:01" "10:59:59" "11:05:01" ...
-##   ..$ year      : num [1:38] 2023 2023 2023 2023 2023 ...
-##   ..$ month     : num [1:38] 8 8 8 8 8 8 8 8 8 8 ...
-##   ..$ day       : num [1:38] 1 1 1 1 1 1 1 1 1 1 ...
-##   ..$ sensor_id : chr [1:38] "Beam breakers" "Beam breakers" "Beam breakers" "Beam breakers" ...
-

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

-

After writing out this loop, but before running it, 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 (here, the first iteration for each loop layer).

-

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.

-

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:

-
# Testing
-x <- 1
-y <- 1
-
-# 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
-  
-  # 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 each sensor, iterate over days
-  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 and single bracket filtering to pull out the right file name
-      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 4 files now exist inside -each directory per sensor within your working directory.

-
list.files(file.path(path, "RFID"))
-
-list.files(file.path(path, "IRBB"))
-

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

- - - - -
- - - - - - - - - - - - - - - diff --git a/R/vignettes/Vignette_07_FinalAnalysesPlots.Rmd b/R/vignettes/Vignette_07_FinalAnalysesPlots.Rmd deleted file mode 100644 index 1dcc0e1..0000000 --- a/R/vignettes/Vignette_07_FinalAnalysesPlots.Rmd +++ /dev/null @@ -1,739 +0,0 @@ ---- -title: "Vignette 05: Process Data" -author: "Grace Smith-Vidaurre" -date: "2023-12-27" -output: html_document ---- - -```{r setup, include = FALSE} - -knitr::opts_chunk$set(echo = TRUE, eval = TRUE) - -``` - -

Vignette Overview and Learning Objectives

- -In this sixth vignette... 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: - -TKTK change code before plots to be about detecting and scoring clusters - -1. TKTK -2. TKTK -3. TKTK - -

Load packages and your working directory path

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

Load ABISSMAL functions

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

Access ABISSMAL function information

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

Combine raw data

- -Once you've loaded the ABISSMAL functions, you can start using the first function, `combine_raw_data()`, to combine data collected across days per sensor into a single spreadsheet per sensor. You'll start by combining raw data for the RFID sensor collected over different days into a single spreadsheet. - -Here goes more information about the arguments you are supplying to the function below: - -* `sensors` is a vector containing the labels of the sensors for which you want to combine raw data. Below you're specifying RFID as a single sensor - -* `path` is your general working directory - -* `data_dir` is the folder that holds data inside of your working directory - -* `out_dir` is the folder where you want to save the combined raw data spreadsheet. The function will create this folder if it does not already exist - -* `tz` is the timezone for converting timestamps to POSIXct format. The default is "America/New York", and you can check out the "Time zones" section in the documentation for DateTimeClasses in R for more information (`?DateTimeClasses`) - -* `POSIXct_format` is a string containing the POSIX formatting information for how dates and timestamps should be combined into a single column. The default is to encode the year as a 4 digit number and the month and day as 2 digit numbers, separated by dashes. The date is followed by a space, then the 2-digit hour, minute, and decimal second (separated by colons) -```{r} - -combine_raw_data(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 single spreadsheet of raw RFID data to the new directory `raw_combined`: -```{r} - -list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") - -``` - -You can read this combined .csv file (called "combined_raw_data_RFID.csv") back into R to check out the structure of this spreadsheet: -```{r} - -rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) - -glimpse(rfid_data) - -``` - -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 hace a unique identifier in `sensor_id`). - -The function created a new timestamps column in POSIXct format for downsteam processing and analysis, but it kept the original timestamps column. The function also 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 not removed 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`: -```{r} - -combine_raw_data(sensors = c("RFID", "IRBB"), path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") - -``` - -The RFID data will be overwritten, and you should see an additional spreadsheet with the raw IRBB data: -```{r} - -list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") - -``` - -

Detect perching events

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

Pre-process raw data

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

Plot raw and pre-processed data

- -Now that you've combined the raw data per sensor, detected perching events in the raw data, and pre-processed the raw data, it's time to visualize these different datasets. Making visualizations in R is important for generating high-quality figures for publications and presentations, but it's also important for checking your work as you process and analyze data. - -In the code below, you'll learn how to use functions from the `ggplot2` package to make barcode style visualizations of the raw and processed detection datasets. - -Start by reading in the raw data for each sensor, and convert the timestamps to POSIX format. -```{r} - -rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) %>% - # The timestamps must be converted to POSIX format every time that the data is read back into R for plotting - dplyr::mutate( - timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) - ) - -# You should see that the timestamp_ms is in "dttm" format, which means that the POSIX conversion was done correctly -glimpse(rfid_raw) - -irbb_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_IRBB.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")) - ) - -glimpse(irbb_raw) - -``` - -Next, start plotting this raw data using functions from `ggplot2`. This package is part of the `tidyverse`, but can also be installed and used separately. You can check out this [link](https://ggplot2.tidyverse.org/) for more resources to learn how to use ggplot 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. - -Thde `ggplot2` package has a unique syntax for building plots, in which you start making a plot by calling the function `ggplot()`, and then add features by layering on other `ggplot2` functions with the `+` symbol. - -If you call `ggplot()`, you'll see that the function immediately draws a blank plot in your Plots pane in RStudio. -```{r} - -ggplot() - -``` - -The plot will still remain blank even if you supply information about your data in order to set up plot aesthetics. -```{r} - -ggplot(data = rfid_raw) - -``` - -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 (see a plot in the next vignette). - - - - -TKTK the code below complicates adding a legend. Consider this code to be a more complex version of the figure...maybe save for another script? And here add just a single sensor - -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 x-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, information on the x-axis only). -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - geom_segment( - data = rfid_raw, - aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1), - color = "blue", - linewidth = 0.3 - ) - -``` - -In the code above, you used the argument `data` inside of `geom_segment()` to tell the function where to get data for drawing the line segments. - -In the argument `aes()`, which is a standard `ggplot2` function for encoding plot aesthetics, you specified that the data to plot on the x-axis are the timestamps for each detection, by providing the column name `timestamp_ms` to the inner argument `x`. You also specified what data should be plotted on the y-axis using the argument `y`. In this case, you can set `y` to any value that you want, since you aren't plotting data along this axis. - -The arguments `x` and `y` determine where the beginning of each line 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 `timestamp_ms` to the argument `xend`, you're indicating that you want the line segment to start and end on the same timestamp. On the y-axis, the number that you supply to `yend` will determine the height of each line. Since you're adding data from a single sensor here, the height of the line does not matter much. If you changed the code to read `yend = 0.1`, the plot would look very similar, but the y-axis would be scaled to have 0.1 as the maximum value instead of 1. - -The last two arguments inside of `geom_segment()` control the color of the line segments (`color`), as well as the width of ech line (`linewidth`). Specifying the width of each line is different than specifying where each line segment starts and ends. - -You can continue adding information to this plot by layering on the raw beam breaker detections: -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - geom_segment( - data = rfid_raw, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 1), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - geom_segment( - data = irbb_raw, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 1), - color = "orange", - linewidth = 0.3 - ) - -``` - -This plot has more information, but it's difficult to interpret. Some of the detections for each sensor type occurred so close in time that they're layered nearly directly over one another. You can change the position of the line segements on the y-axis in order to have better resolution for visualizng the datasets from both sensors. - -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - geom_segment( - data = rfid_raw, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - geom_segment( - data = irbb_raw, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) - -``` - -This change to the start and end coordinates of how segments are drawn on the y-axis offset the two sets of line segments per sensor dataset. The plot is now less crowded and it's easier to visualize patterns over time and between datasets now. - -The plot is looking a lot better, but there's a problem with the x-axis. The x-axis currently holds two types of information: 1) the day of data collection, and 2) the time of day. It's difficult to see when detections were recorded on each day, and whether detections were recorded over similar or different time periods between days. To make the x-axis more interpretable, you can change the plot to be "faceted", so that you have 1 panel per day of data collection. -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - geom_segment( - data = rfid_raw, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - geom_segment( - data = irbb_raw, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) + - - # Facet the plot by day (e.g. create a panel per day) - facet_wrap(~ day, nrow = 2, strip.position = "left") - -``` - -The overall plot structure has improved a lot, but are still some modifications that would help interpretability. 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. label each hour). - -You can start by modifying the data frame of raw data for each senor 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 2 days of data collection that need recoding. - -First you can practice using `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 day is the first day of the month), the value you want to add to the vector if the condition is true ("Day 1" of data collection), and then the value that you want to add if the condition is false ("Day 2" of data collection, since there are only 2 dates in each dataset). -```{r} - -ifelse(test = rfid_raw$day == 1, yes = "Day 1", no = "Day 2") - -``` - -Now you can create a new column per data frame using an `ifelse` statement. -```{r} - -# Create a new column in the raw data for the date of data collection -rfid_raw2 <- rfid_raw %>% - dplyr::mutate( - day_label = ifelse(day == 1, "Day 1", "Day 2") - ) - -# You should see that the new column "day_label" was created -# You'll see that a new year, month, and day were appended to the updated timestamps, but this is expected (see above) -glimpse(rfid_raw2) - -# Repeat this process for the beam breaker data -irbb_raw2 <- irbb_raw %>% - dplyr::mutate( - day_label = ifelse(day == 1, "Day 1", "Day 2") - ) - -glimpse(irbb_raw2) - -``` - -Now you can update the code to include the new date labels: -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = rfid_raw2, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = irbb_raw2, - aes(x = timestamp_ms, xend = timestamp_ms, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) + - - # Facet the plot by day (e.g. create a panel per day) - # Use the new day labels here - facet_wrap(~ day_label, nrow = 2, 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 timestamps column. The code to convert the timestamps to a different format is nested and repetitive, but the timestamp conversion will be performed correctly. When you prompt the R to convert the timestamps to hours, minutes, and seconds only, R adds a default year, month, and day beforehand (likely the current date that you rant the code). This is expected, and it is not an error, but rather makes it possible for the timestamps to align correctly over days in the plot (since R sees all timestamps occurring on a single day). -```{r} - -rfid_raw3 <- rfid_raw2 %>% - dplyr::mutate( - timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%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(rfid_raw3) - -# Repeat this process for the beam breaker data -irbb_raw3 <- irbb_raw2 %>% - dplyr::mutate( - timestamp = as.POSIXct(strptime(format(as.POSIXct(timestamp_ms), "%H:%M:%S"), format = "%H:%M:%S")) - ) - -glimpse(irbb_raw3) - -``` - -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: -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = rfid_raw3, - aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = irbb_raw3, - aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) + - - # Facet the plot by day (e.g. create a panel per day) - # Use the new day labels here - facet_wrap(~ day_label, nrow = 2, strip.position = "left") + - - # Change the aesthetics of the x-axis labels - scale_x_datetime( - date_breaks = "30 mins", - date_labels = "%H:%M" - ) - -``` - -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 (since the height of each segment does not reflect data that you want to interpret). -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = rfid_raw3, - aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = irbb_raw3, - aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) + - - # Facet the plot by day (e.g. create a panel per day) - # Use the new day labels here - facet_wrap(~ day_label, nrow = 2, strip.position = "left") + - - # Change the x and y axis labels - xlab("Time") + - - # Now you can add a y-axis label - ylab("Day of data collection") + - - # Change the aesthetics of the x-axis labels - scale_x_datetime( - date_breaks = "30 mins", - date_labels = "%H:%M" - ) + - - # 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 - theme( - axis.text.y = element_blank(), - axis.ticks.y = element_blank() - ) - -``` - -Finally, you need to add a legend to this plot so you can communicate what the two different colors mean. Adding a legend to this plot is tricky because you manually coded the colors of the line segments in two separate uses in `geom_segment()`. `ggplot2` functions have specific requirements for making a legend, and they can require automated coding of aesthetic values like color. - -In order to automatically create a legend in a plot, it helps to have a layer of the plot in which color is automatically encoded. The way that you would do this is to move the `color` argument inside of the function `aes()` for one layer of the plot, and then supply `color` with a column of data type `factor`...TKTK continue -```{r} - -all_sensors <- rfid_raw3 %>% - bind_rows( - irbb_raw3 - ) %>% - dplyr::mutate( - sensor_id = factor(sensor_id) - ) - -glimpse(all_sensors) - -``` - -Then make the plot. -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = rfid_raw3, - aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = irbb_raw3, - aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) + - - # Add the fake layer of lines so that you can create a legend - # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below - geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + - - # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding - guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + - - # Facet the plot by day (e.g. create a panel per day) - # Use the new day labels here - facet_wrap(~ day_label, nrow = 2, strip.position = "left") + - - # Change the x and y axis labels - xlab("Time") + - - # Now you can add a y-axis label - ylab("Day of data collection") + - - # Change the aesthetics of the x-axis labels - scale_x_datetime( - date_breaks = "30 mins", - date_labels = "%H:%M" - ) + - - # 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" - ) - -``` - -You added a legend to the plot, but the colors in the legend don't line up with the colors of the line segments per sensor. You can use the function `scale_color_manual()` to match colors between the plot and legend. -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = rfid_raw3, - aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = irbb_raw3, - aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) + - - # Add the fake layer of lines so that you can create a legend - # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below - geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + - - # Set the colors that will be used to color the lines in the legend itself - # The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column - # If you do not supply your own color values, then R will assign default colors to each factor level - scale_color_manual(values = c("blue", "orange")) + - - # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding - guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + - - # Facet the plot by day (e.g. create a panel per day) - # Use the new day labels here - facet_wrap(~ day_label, nrow = 2, strip.position = "left") + - - # Change the x and y axis labels - xlab("Time") + - - # Now you can add a y-axis label - ylab("Day of data collection") + - - # Change the aesthetics of the x-axis labels - scale_x_datetime( - date_breaks = "30 mins", - date_labels = "%H:%M" - ) + - - # 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" - ) - -``` - -Now that you've made the plot in R, you can save the plot as an image file on your computer using the function `ggsave()`. -```{r} - -ggplot() + - - # Add a vertical line for each RFID timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = rfid_raw3, - aes(x = timestamp, xend = timestamp, y = 0, yend = 0.45), - color = "blue", - linewidth = 0.3 - ) + - - # Add a vertical line for each beam breaker timestamp - # Use the new version of this data frame with the new day_label column - # Make sure to use the new timestamp column - geom_segment( - data = irbb_raw3, - aes(x = timestamp, xend = timestamp, y = 0.5, yend = 1), - color = "orange", - linewidth = 0.3 - ) + - - # Add the fake layer of lines so that you can create a legend - # The argument color here must be supplied with the name of a column that is the data type "factor" in R. This will trigger automatic coding of the color values that you specify below - geom_line(data = all_sensors, aes(x = timestamp, y = 1, color = sensor_id), linewidth = 0) + - - # Set the colors that will be used to color the lines in the legend itself - # The length of the vector of color values that you supply here must be the same length as the number of "levels" (or unique values) in the factor column - # If you do not supply your own color values, then R will assign default colors to each factor level - scale_color_manual(values = c("blue", "orange")) + - - # Specify that you want to create a color legend, and override the linewidth of 0 above so that the lines show up in the legend. You can also customize the legend title, otherwise it will use the column name linked to the color encoding - guides(color = guide_legend(override.aes = list(linewidth = 2), title = "Sensor type")) + - - # Facet the plot by day (e.g. create a panel per day) - # Use the new day labels here - facet_wrap(~ day_label, nrow = 2, strip.position = "left") + - - # Change the x and y axis labels - xlab("Time") + - - # Now you can add a y-axis label - ylab("Day of data collection") + - - # Change the aesthetics of the x-axis labels - scale_x_datetime( - date_breaks = "30 mins", - date_labels = "%H:%M" - ) + - - # 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" - ) - -# Save the image file to your computer -ggsave(file.path(path, "raw_detections.tiff"), width = 8, height = 6, units = "in", dpi = 300) - -``` - -You just learned how to make a plot in R, and also how to save it to your computer. 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. \ No newline at end of file From 5c60e6c0eec4e7882556417e361dfbcee46c00fe Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Thu, 21 Mar 2024 13:17:14 -0400 Subject: [PATCH 28/69] Checked out possible bug in preprocess_detections(), added another unit test as another layer of checking, changed repo name where needed --- R/tests/testthat/run_all_testthat_tests.Rmd | 2 +- R/tests/testthat/test_combine_raw_data.R | 4 +- R/tests/testthat/test_detect_clusters.R | 4 +- .../testthat/test_detect_perching_events.R | 4 +- R/tests/testthat/test_preprocess_detections.R | 143 +++++- R/tests/testthat/test_score_clusters.R | 4 +- R/vignettes/.Rhistory | 478 +++++++++--------- .../Vignette_05_ProcessData_BuildPlots.Rmd | 13 +- 8 files changed, 396 insertions(+), 256 deletions(-) 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 index d0f6c23..fd81f25 100644 --- a/R/vignettes/.Rhistory +++ b/R/vignettes/.Rhistory @@ -1,28 +1,20 @@ -scale_x_datetime( -date_breaks = "1 hour", -date_labels = "%H:%M" -) -# Update the dataset in the base layer of the plot, which will be used by all layers that do not have a specific data argument themselves -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 %>% +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.3 +linewidth = 0.5 ) + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( -data = scored_clusters_gg3 %>% +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.3 +linewidth = 0.5 ) + # Add the custom linetype values to this plot scale_linetype_manual(values = ltys) + -# Remove the y-axis legend +# Remove the y-axis label ylab("") + # Use this function to convert the plot background to black and white theme_bw() + @@ -35,21 +27,22 @@ 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" +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")) ) -# Update the dataset in the base layer of the plot, which will be used by all layers that do not have a specific data argument themselves -ggplot(data = scored_clusters_gg3) + +# 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.3 +linewidth = 0.5 ) + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( @@ -57,11 +50,11 @@ 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.3 +linewidth = 0.5 ) + # Add the custom linetype values to this plot scale_linetype_manual(values = ltys) + -# Remove the y-axis legend +# Remove the y-axis label ylab("") + # Use this function to convert the plot background to black and white theme_bw() + @@ -79,150 +72,86 @@ facet_wrap(~ day_label, nrow = 3, strip.position = "left") + scale_x_datetime( date_breaks = "30 mins", date_labels = "%H:%M" +) + +# 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() ) -# Update the dataset in the base layer of the plot, which will be used by all layers that do not have a specific data argument themselves -ggplot(data = scored_clusters_gg3) + -# Add a vertical line for each non-perching event assigned to the first individual +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(individual_initiated == "1357aabbcc"), -aes(x = start_gg, y = 0, xend = end_gg, yend = 0.5, linetype = direction_scored), -color = "orange", -linewidth = 0.3 +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 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.3 +# 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") ) + -# Add the custom linetype values to this plot -scale_linetype_manual(values = ltys) + +# Change the legend titles +guides( +linetype = guide_legend(title = "Direction"), +color = guide_legend(title = "Individual") +) +gg # Remove the y-axis label ylab("") + -# Add a y-axis label -ylab("Time") + -# 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" -) -levels(scored_clusters_gg$individual_initiated) -# 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") -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.3 ) + -# 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.3 -) + -# Add the custom linetype values to this plot -scale_linetype_manual(values = ltys) + -# Remove the y-axis label -ylab("") + -# Add a y-axis label -ylab("Time") + +# 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" -) + -# 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") + +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"), +) +# 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" ) + -# Add the perching events, 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.3, color = individual_initiated), -linewidth = 0.8 -) + -# Add the custom colors -scale_color_manual(values = cols) -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.3 -) + -# 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.3 -) + -# Add the custom linetype values to this plot -scale_linetype_manual(values = ltys) + -# Remove the y-axis label -ylab("") + -# Add a y-axis label -ylab("Time") + +# 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" -) + -# 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 the perching events, 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 = 0.8 -) + -# Add the custom colors -scale_color_manual(values = cols) +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") +) ggplot(data = scored_clusters_gg3) + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( @@ -230,7 +159,7 @@ 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.3 +linewidth = 0.5 ) + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( @@ -238,41 +167,50 @@ 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.3 +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") +) + # Remove the y-axis label ylab("") + -# Add a y-axis label -ylab("Time") + +# 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" -) + -# 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 the perching events, 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.3, color = individual_initiated), -linewidth = 4 -) + -# Add the custom colors -scale_color_manual(values = cols) -?geom_segment +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( @@ -280,7 +218,7 @@ 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.3 +linewidth = 0.5 ) + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( @@ -288,48 +226,57 @@ 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.3 +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") +) + # Remove the y-axis label ylab("") + -# Add a y-axis label -ylab("Time") + +# 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" -) + -# 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 the perching events, 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.3, color = individual_initiated), -linewidth = 4, lineend = "round" -) + -# Add the custom colors -scale_color_manual(values = cols) -ggplot(data = scored_clusters_gg3) + +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") +) +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.3 +linewidth = 0.5 ) + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( @@ -337,14 +284,12 @@ 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.3 +linewidth = 0.5 ) + # Add the custom linetype values to this plot scale_linetype_manual(values = ltys) + # Remove the y-axis label ylab("") + -# Add a y-axis label -ylab("Time") + # 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 @@ -362,23 +307,16 @@ scale_x_datetime( date_breaks = "30 mins", date_labels = "%H:%M" ) + -# Add the perching events, 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 = 4, lineend = "round" -) + -# Add the custom colors -scale_color_manual(values = cols) -ggplot(data = scored_clusters_gg3) + +# Add an x-axis title +xlab("Time of day (HH:MM)") +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.3 +linewidth = 0.5 ) + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( @@ -386,14 +324,12 @@ 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.3 +linewidth = 0.5 ) + # Add the custom linetype values to this plot scale_linetype_manual(values = ltys) + # Remove the y-axis label ylab("") + -# Add a y-axis label -ylab("Time") + # 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 @@ -411,6 +347,15 @@ 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 %>% @@ -420,6 +365,22 @@ 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( @@ -427,7 +388,7 @@ 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.3 +linewidth = 0.5 ) + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( @@ -435,41 +396,52 @@ 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.3 +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("") + -# Add a y-axis label -xlab("Time") + +# 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" -) + -# 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 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) -# Update the dataset in the base layer of the plot, which will be used by all layers that do not have a specific data argument themselves +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( @@ -477,7 +449,7 @@ 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.3 +linewidth = 0.5 ) + # Add a vertical line for each non-perching event assigned to the first individual geom_segment( @@ -485,28 +457,56 @@ 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.3 +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("") + -# Add a y-axis label -xlab("Time") + +# 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" -) + -# 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" +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) diff --git a/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd b/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd index e90e9f0..5d8b0ae 100644 --- a/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd +++ b/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd @@ -77,27 +77,34 @@ Here goes more information about the arguments you are supplying to the function * `POSIXct_format` is a string containing the POSIX formatting information for how dates and timestamps should be combined into a single column. The default is to encode the year as a 4 digit number and the month and day as 2 digit numbers, separated by dashes. The date is followed by a space, then the 2-digit hour, minute, and decimal second (separated by colons) ```{r} +# Combine raw data for the RFID and infrared beam breaker sensors separately combine_raw_data(sensors = "RFID", path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") +combine_raw_data(sensors = "IRBB", 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 single spreadsheet of raw RFID data to the new directory `raw_combined`: +You can check that `combine_raw_data()` wrote separate spreadsheets of raw RFID and beam breaker data to the new directory `raw_combined`: ```{r} list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") ``` -You can read this combined .csv file (called "combined_raw_data_RFID.csv") back into R to check out the structure of this spreadsheet: +You can read thw combined .csv files (called "combined_raw_data_RFID.csv" and "combined_raw_data_IRBB.csv") back into R to check out the structure of this spreadsheet: ```{r} rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) glimpse(rfid_data) +irbb_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_IRBB.csv")) + +glimpse(irbb_data) + ``` -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 hace a unique identifier in `sensor_id`). +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 `sensor_id`). The function created a new timestamps column in POSIXct format for downsteam processing and analysis, but it kept the original timestamps column. The function also 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 not removed not overwritten. From 2edffb00d6d742777a17ca943310f1d1c91a5b1b Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Thu, 21 Mar 2024 15:16:04 -0400 Subject: [PATCH 29/69] [PCT-472]: Run-through of vignettes 1-3 --- R/vignettes/Vignette_01_Introduction.Rmd | 29 ++-- R/vignettes/Vignette_02_Setup.Rmd | 45 +++--- R/vignettes/Vignette_03_SimulateData.Rmd | 142 ++++++++++-------- R/vignettes/Vignette_04_SaveData.Rmd | 2 +- .../Vignette_05_ProcessData_BuildPlots.Rmd | 2 +- .../Vignette_06_FinishAnalysisPipeline.Rmd | 2 +- 6 files changed, 123 insertions(+), 99 deletions(-) diff --git a/R/vignettes/Vignette_01_Introduction.Rmd b/R/vignettes/Vignette_01_Introduction.Rmd index 1fac297..b5af88b 100644 --- a/R/vignettes/Vignette_01_Introduction.Rmd +++ b/R/vignettes/Vignette_01_Introduction.Rmd @@ -2,24 +2,29 @@ title: "Vignette 01: Introduction" author: "Grace Smith-Vidaurre" date: "2023-12-27" -output: html_document +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 --- -

Vignette Overview and Learning Objectives

+

Vignette overview and learning objectives

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

Configure your RStudio session

-If you are new to using R (a programming language for statistical analysis) and RStudio (a graphical user interface for R), then I recommend checking out the introductory lesson [R for Reproducible Scientific Analysis](https://swcarpentry.github.io/r-novice-gapminder/) provided by [Software Carpentry](https://software-carpentry.org/). +If you are new to using R (a programming language for statistical analysis) and RStudio (a graphical user interface for R), then I recommend checking out the introductory lesson [R for Reproducible Scientific Analysis](https://swcarpentry.github.io/r-novice-gapminder/) provided by the [Software Carpentry](https://software-carpentry.org/). Once you have R and RStudio installed on your computer, you can open RStudio by clicking on the software icon. The default pane configuration for RStudio should look like this (the background color will probably be different): @@ -43,9 +48,9 @@ The RStudio pane layout should now look like this:
![A screenshot of the updated RStudio pane configuration, with the console pane to the right of the source pane](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/RStudio_02.png) -Another useful change to the RStudio configuration is soft-wrapping lines of text and code, so that you don't have to scroll horizontally to see long lines of text in your Source pane. To do this, go to "Tools", then "Global Options", then "Code", and check the box next to the option "Soft-wrap R source files", then select "Apply" and "Ok". +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 play with 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. +You can also change the font size, font color, and background color of your RStudio workspace. After selecting "Tools" and "Global Options", go to "Appearance" to see some different options.

Create a local version of an existing GitHub repository

@@ -76,7 +81,7 @@ Once the repository has been installed on your computer, you should see the foll
![A screenshot of the directory that is the local ABISSMAL repository](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/ABISSMAL_localrepo.png) -The files that we'll be working with throughout the following vignettes are inside of the "R" folder. This folder contains 6 R files (extension ".R"), a README file that contains more information about each R file, and folders with the R vignettes as well as automated unit testing scripts. +The files that we'll be working with throughout the following vignettes are inside of the "R" folder. This folder contains 6 R files (extension ".R"), a README file that contains more information about each R file, and folders with the vignettes in RMarkdown format (extension ".Rmd") as well as automated unit testing scripts.

ABISSMAL data processing and analysis

@@ -94,18 +99,18 @@ ABISSMAL provides 5 different R functions for data processing and analysis. Thes
![Figure 4 from the ABISSMAL methods manuscript under review, that shows a general workflow across these 5 functions](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/Figure4_ComputationalAnalyses.png) -* Figure from Smith-Vidaurre, G., Molina, T., Jarvis, E.D., and E.A. Hobson. 2023. Automated tracking of avian parental care. Under review. +* Figure from Smith-Vidaurre, G., Molina, T., Jarvis, E.D., and E.A. Hobson. 2023. Automated tracking of avian parental care. [EcoEvoRxiv preprint]((https://ecoevorxiv.org/repository/view/6268/)).

Troubleshoot errors online

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

Report bugs on GitHub

-As you work through each vignette, you may encounter "bugs" or errors with the code, including code that does not work or that yields incorrect outcomes. These bugs can occur with the ABISSMAL functions themselves, or with the code in a given vignette. When you encounter a bug with an ABISSMAL function, you can create an Issue through the GitHub repository. To create a new Issue, you can select "New Issue" on [Issues page](https://github.com/lastralab/ABISSMAL/issues) of the main ABISSMAL repository, then follow the instructions in the Issue template to add the information needed for the code developers to address the bug effectively. You can also add the tag "bug" to your issue. If you encounter an issue with the ABISSMAL vignettes, then you can submit an issue through the GitHub repository for these vignettes. +As you work through each vignette, you may encounter "bugs" or errors with the code, including code that does not work or that yields incorrect outcomes. These bugs can occur with the ABISSMAL functions themselves, or with the code in a given vignette. When you encounter a bug with an ABISSMAL function, you can create an Issue through the GitHub repository. To create a new Issue, you can select "New Issue" on the [Issues page](https://github.com/lastralab/ABISSMAL/issues) of the main ABISSMAL repository, then follow the instructions in the Issue template to add the information needed for the code developers to address the bug effectively. You can also add the tag "bug" to your issue. If you encounter an issue with the ABISSMAL vignettes, then you can submit an issue through the main ABISSMAL GitHub repository as well, as long as you clarify that the bug is related to code in a given vignette. -In the next vignette, we will create simulated datasets of raw movement data to learn how to use the different functions described above. \ No newline at end of file +In the next vignette, we will create simulated datasets of raw movement data to learn how to use the different ABISSMAL functions described above. \ No newline at end of file diff --git a/R/vignettes/Vignette_02_Setup.Rmd b/R/vignettes/Vignette_02_Setup.Rmd index 661b942..fbe8ea7 100644 --- a/R/vignettes/Vignette_02_Setup.Rmd +++ b/R/vignettes/Vignette_02_Setup.Rmd @@ -2,7 +2,12 @@ title: "Vignette 02: Setup" author: "Grace Smith-Vidaurre" date: "2023-12-27" -output: html_document +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 --- ```{r setup, include = FALSE} @@ -11,13 +16,13 @@ knitr::opts_chunk$set(echo = TRUE, eval = FALSE) ``` -

Vignette Overview and Learning Objectives

+

Vignette overview and learning objectives

-In this second vignette, we will set up a virtual workspace for coding sessions with the ABISSMAL R functions. You will learn basic R programming skills and open science tips for writing code for widespread sharing, including: +In this second vignette, we will set up a virtual workspace for coding sessions with the ABISSMAL R functions. You will learn basic R programming skills and open science tips for writing code, including: 1. How to use RMarkdown files 2. Cleaning your global environment -3. Running code inside an RMarkdown chunk +3. Running code inside of an RMarkdown chunk 4. Learning about R functions and documentation 5. Installing and loading packages 6. Commenting code @@ -28,7 +33,7 @@ In this second vignette, we will set up a virtual workspace for coding sessions Each vignette in this series is available as an RMarkdown file (extension .Rmd) and as an HTML file that can be viewed in your default Internet browser. Each HTML file was generated by knitting the output of an RMarkdown file. You can check out the [RMarkdown documentation](https://rmarkdown.rstudio.com/lesson-1.html) for more information about how to use this file format to write code and knit the code output into reports. -RMarkdown files facilitate sharing your code and the output of your code with others. If you're new to using RMarkdown, the best way to work through these vignettes will be to create a new RMarkdown file for each vignette and write out the code from each vignette yourself. You will get the most coding practice out of these vignettes if you write out the code and comments in your own words (outside and inside of chunks). You can open the knitted output for each vignette in your Internet browser as a guide while you write code in your own RMarkdown file. Or you can have the vignette RMarkdown file and your own RMarkdown file side by side in RStudio by adding a third column in the pane layout: +RMarkdown files facilitate sharing your code and the output of your code with others. If you're new to using RMarkdown, the best way to work through these vignettes will be to create a new RMarkdown file for each vignette and write out the code from each vignette yourself. You will get the most coding practice out of these vignettes if you write out the code and comments in your own words (outside and inside of chunks). You can open the knitted output for each vignette in your Internet browser as a guide while you write code in your own RMarkdown file. Or you can open the vignette RMarkdown file and your own RMarkdown file side by side in RStudio by adding a third column in the pane layout: * Tools * Global Options @@ -36,7 +41,7 @@ RMarkdown files facilitate sharing your code and the output of your code with ot * Select the Add Column option to add another Source pane in a third column * Open the original vignette RMarkdown file in one source pane, and your own RMarkdown file in the other source pane -If you're already familiar with writing R code and 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. +If you're already familiar with writing R code and using RMarkdown files, then you can open the original RMarkdown file for each vignette and run the code inside each file as you work through each vignette (with some modification of paths to reflect folders and files on your local computer). If you want to preserve the original code in each RMarkdown file, you can create a copy of each vignette and modify the copies instead.

Clean your global environment

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

Access R function documentation

@@ -80,13 +85,13 @@ You can access the documentation for these functions by clicking on the "Help" t ``` -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 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 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 `=`). 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 lists the names of all of the objects in your global environment. +For instance, the function `rm()` has an argument called `list` (followed by an `=`, which is used to pass a specific value to the argument). In order for `rm()` to clean your global environment in the way that you want, you need to provide information after the argument `list`. To remove everything in your global environment, we're using the output of the function `ls()`, which is a function that lists the names of all of the objects in your global environment.

Install and load packages

-After cleaning the global environment, you still need to set up your virtual workspace for the upcoming coding session. One important setup step is to make sure you have access to functions that you need but which aren't accessible through the base R functions. For instance, [the `tidyverse`](https://www.tidyverse.org/) is a set of R packages that provide useful functions and expressions for data science purposes. +After cleaning the global environment, you still need to set up your virtual workspace for the upcoming coding session. One important step here is to make sure you have access to functions that you need but which aren't accessible through the base R functions. For instance, [the `tidyverse`](https://www.tidyverse.org/) is a set of R packages that provide useful functions and expressions for data science purposes. If you don't already have tidyverse installed on your local computer, then you need to install the tidyverse in order to access these functions. The code below installs the tidyverse from [CRAN](https://cran.r-project.org/), the Comprehensive R Archive Network that holds thousands of R packages online. ```{r} @@ -98,7 +103,7 @@ 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 load the package into your global environment in order to access functions across the tidyverse packages: +Once you've installed the tidyverse on your computer, you do not need to install it again. But you do need to use the function `library()` to load the package into your global environment in order to access functions that are held inside of the tidyverse packages: ```{r} library(tidyverse) @@ -107,7 +112,7 @@ library(tidyverse)

Coding shortcuts

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

Troubleshooting incomplete statements

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

Create your working directory

-The next important step for setting up your virtual workspace is to create (or decide on) your working directory. A directory is a location (like a folder) on your computer. A working directory is the location on your computer where R will search for files to read in, and also where the program will save files that you write out. +The next important step for setting up your virtual workspace is to decide on the working directory that you will use for the coding session. A directory is a location (a folder) on your computer where R will search for files to read in. When you write out files from R, those files can also be written to your working directory. You can use the function `getwd()` to check your current working directory. ```{r eval = TRUE} @@ -149,9 +154,9 @@ 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. There are other ways in which you can specify your working directory throughout your code without using `setwd()`. +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 the directory 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 these paths to reflect where the vignettes are saved on your computer, as well as your own working directory: +For instance, let's say that we want to make a copy of the first vignette and then save that copy inside of the directory that we created above. We can use a base R functions to copy and save the file to the correct working directory. You will need to update the paths below to reflect the folder where the vignettes are saved on your computer, as well as your own working directory: ```{r} file.copy( @@ -161,16 +166,16 @@ file.copy( ``` -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 string (a formal term for text characters in R code). +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". +When you run the line of code above, the output in the console should read "[1] TRUE" if the file was successfully copied and saved. You can check that the function worked correctly by opening a file window on your computer in this new directory. You can also use the base R function `list.files()` to check whether the copied file exists in the working directory. The output of `list.files()` is a list of all of the files contained within the new directory, and that list should contain a single file: "Vignette_01_Introduction_copy.Rmd". ```{r} list.files(path = "/home/gsvidaurre/Desktop/ABISSMAL_vignettes") ``` -This 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: +The code above is an example of how you can specify your working directory in the code that you write without needing to use `setwd()`. In the following vignettes, you will see other examples of how to specify your working directory without `setwd()`. Before moving on to the next vignette, you can remove the file that you copied to your working directory: ```{r} diff --git a/R/vignettes/Vignette_03_SimulateData.Rmd b/R/vignettes/Vignette_03_SimulateData.Rmd index e0b54e3..5d4aaa1 100644 --- a/R/vignettes/Vignette_03_SimulateData.Rmd +++ b/R/vignettes/Vignette_03_SimulateData.Rmd @@ -16,9 +16,9 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE) ``` -

Vignette Overview and Learning Objectives

+

Vignette overview and learning objectives

-In this third vignette, we will create simulated datasets of animal movements detected by different sensors. Simulating these datasets replaces the data collection process that ABISSMAL undertakes in order to collect data from live animals. Using 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 code used to analyze those datasets, then check out the [methods manuscript preprint](https://ecoevorxiv.org/repository/view/6268/) that has links to publicly available data and code. +In this third vignette, we will create simulated datasets of animal movements detected by different sensors. Simulating these datasets replaces the data collection process pipeline used by ABISSMAL to collect data from live animals. Generating simulated datasets will provide you more opportunities to practice basic coding skills, and will also allow you to have more control over the process of data collection in order to understand the subsequent data analysis steps. If you want to see movement detection datasets collected from live birds with ABISSMAL, and the code used to analyze those datasets, then check out the [methods manuscript preprint](https://ecoevorxiv.org/repository/view/6268/) that has links to publicly available data and code. Throughout the process of creating simulated datasets in this vignette, you will continue to use coding skills that you learned in vignette 02, and you will learn additional skills that include: @@ -30,10 +30,10 @@ Throughout the process of creating simulated datasets in this vignette, you will

Load packages

-In the previous vignette, you installed a set of data science packages called the `tidyverse`. You also learned about working directories and created a directory on your computer to save data that you generate from this and subsequent vignettes. +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 setup your virtual workspace before starting data analysis. In the chunk below, you will clean your global environment and load the tidyverse. This is code that you already saw in vignette 02, but now these lines of code are together in one chunk. -```{r class.source = "bg-info", message = FALSE, warning = FALSE} +Whenever you start a new RMarkdown file or R script, it's important to set up your virtual workspace before starting data analysis. In the chunk below, you will clean your global environment and load the tidyverse. This is code that you already saw in vignette 02, but now these lines of code are together in one chunk. +```{r message = FALSE, warning = FALSE} rm(list = ls()) # Clean global environment @@ -43,17 +43,17 @@ library(tidyverse) # Load the set of tidyverse packages

Create a path object

-The next setup step is to specify your working directory. In vignette 02, you used a string (text contained in double quotes to indicate character information) to specify your working directory while using different functions. Using the same long character string over and over is not very efficient, so you can instead save the character string for your working directory inside a new object in R. +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 the `path` object by writing out the name of the object on the left (without quotes), then the object creation syymbols `<-`, and then the information that you want to store in this object (your working directory as a character string in quotes). -```{r class.source = "bg-info", eval = TRUE} +You can create a `path` object by writing out the name of the object on the left (without quotes), then the object creation symbols `<-`, and then the information that you want to store in this object (your working directory as a character string in quotes). +```{r eval = TRUE} path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" ``` In the code above, you created an object called `path`. You can see contents of the object `path` by writing out the object name and running that code in the console: -```{r class.source = "bg-warning", eval = TRUE} +```{r eval = TRUE} path @@ -65,22 +65,22 @@ You can practice removing the `path` object from your global environment by writ

Create timestamps of simulated detections of animal movements

-The primary data collected by the ABISSMAL tracking system are timestamps indicating the moment of time when a movement sensor triggered. 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. +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 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 the container entrance. In this simulated setup, the outer beam breakers will trigger first 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 trigger first, followed by the RFID antenna and then the outer beam breakers. +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 is called `rfid_ts`, and it holds 4 timestamps in hours:minutes:seconds format. Each timestamp is surrounded by double quotes to denote that we are using character information here. +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 supplied to the function `c()`. This function concatenates values separated by commas into a vector or list object. Above you created an object called `path` without using `c()`, but that object had a single value. Using `c()` allows you to combine multiple values into a vector that will have more than 1 element. -```{r class.source = "bg-info"} +These timestamps are combined inside of the same object by using the function `c()`. This function `c()` concatenates values separated by commas into a vector or list object. Above you created an object called `path` without using `c()`, but that object had a single value. Using `c()` allows you to combine multiple values into a vector that will have more than 1 element. +```{r} -# Create a vector of 4 RFID timestamps in HH:MM:SS format +# Create a vector of 4 RFID timestamps or 4 elements in HH:MM:SS format rfid_ts <- c("10:00:00", "10:05:00", "11:00:00", "11:05:00") ``` -You can check out the different properties of the `rfid_ts` object after it's made. -```{r class.source = "bg-warning"} +You can check out the different properties of the `rfid_ts` object after it's made: +```{r} rfid_ts # Run the object name to see its contents @@ -92,8 +92,8 @@ length(rfid_ts) # This vector has 4 elements ``` -Let's continue by simulating 2 entrance and 2 exit events. We can choose timestamps for the infrared beam breakers that precede the RFID timestamps to simulate an entrance event, and infrared beam breaker timestamps that follow the RFID timestamps to simulate an exit event. We will offset detections from each sensor within the entrance and exit events by 1 second. -```{r class.source = "bg-info"} +Let's continue by simulating 2 entrance and 2 exit movement events. We can choose timestamps for the infrared beam breakers that precede the RFID timestamps to simulate an entrance event, and infrared beam breaker timestamps that follow the RFID timestamps to simulate an exit event. We will offset detections from each sensor within the entrance and exit movement events by 1 second. +```{r} # Simulate outer and inner beam breaker timestamps for an entrance, an exit, and then another entrance and exit o_irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") @@ -102,22 +102,22 @@ i_irbb_ts <- c("10:00:01", "10:04:59", "11:00:01", "11:04:59") ``` Birds will sometimes perch in the nest container entrance, and the sensors should pick up this behavior. You can add some perching events to the simulated dataset. Here we will simulate perching events for the RFID data only. -```{r class.source = "bg-info"} +```{r} rfid_ts <- c(rfid_ts, "08:00:00", "08:00:01", "08:00:02", "08:00:03", "11:30:00", "11:30:01", "11:30:02", "11:30:03", "11:30:04", "11:30:05") ``` In the code above, we modified the object `rfid_ts` by using the function `c()` to add 10 more timestamps to this vector, for a total of 14 elements. Check out the structure of the modified `rfid_ts` object using the function `glimpse()`: -```{r class.source = "bg-warning"} +```{r} # See the object structure in a format that includes the data type ("chr"), the number of elements ([1:14]), and a preview of the first few elements glimpse(rfid_ts) ``` -Another important type of information to simulate is noise in the sensor detections. For instance, the RFID antenna can fail to detect an individual's passive integrated transponder (PIT) tag, and the infrared beam breakers can trigger when birds leave nesting material hanging in the entrance of the container. In both of these cases, the infrared beam breakers should trigger when the RFID antenna does not. -```{r class.source = "bg-info"} +Another important type of information to simulate is noise in the sensor detections. For instance, the RFID antenna can fail to detect an individual's passive integrated transponder (PIT) tag, and the infrared beam breakers can activate when birds leave nesting material hanging in the entrance of the container. In both of these cases, the infrared beam breakers should activate when the RFID antenna does not. +```{r} # Simulate some RFID detection failures across both beam breaker pairs # These RFID detection failures occurred over 2 additional simulated entrances and 2 additional simulated exits @@ -134,68 +134,70 @@ glimpse(o_irbb_ts) ``` -You've created simulated datasets of detections of animal movements, but currently, these datasets are in vector format only 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. +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). -Right now, the data for both sensors is in vector format. Vectors are useful data structures in R, but one limitation of vectors is that you cannot combine different data types into the same object. You can try this out with the code below: -```{r class.source = "bg-warning"} +Vectors are useful data structures in R, but one limitation of vectors is that you cannot combine different data types into the same object. You can try this out with the code below: +```{r} # Create a vector with character, numeric, and binary data types # Since you're not saving the output into an object, the result will print directly to the console +# You should see that all elements are forced to be character strings in double quotes c("1", 1, TRUE, FALSE) # Create a vector with numeric and binary data types +# You should see that all elements have been forced to numeric. The TRUE and FALSE binary values are converted to the underlying numeric values used to store binary information (TRUE is stored as 1, and FALSE is stored as 0) c(1, 1, TRUE, FALSE) ``` When you try to combine character, numeric, and binary data types in the same vector, all vector elements will be forced to the character data type. A similar thing happens when you try to combine numeric and binary data types, but the vector is forced to numeric. Note that the binary values TRUE and FALSE are equivalent to the numeric values 1 and 0, respectively. -

Create data frames with primary data and metadata

- -You need to be able to create metadata of different data types that can accompany the timestamps for each sensor. To do this, 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 different data types in the same data frame. You can also write out data frames to spreadsheets that exist as physical files on your computer. +

Create metadata vectors

-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. +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. You'll also use the function `length()` to get the length of `rfid_ts`, and then feed that result to `rep()` to set the number of times to repeat the information. -```{r class.source = "bg-info"} +Make a vector of metadata about the experimental replicate. Here, instead of writing out the same information many times, you'll use the function `rep()` to repeat the information about the experimental replicate several times. In order to set the number of times that the experimental replicate information is repeated, you'll also use the function `length()` to get the length of the `rfid_ts` vector, and then feed that result to `rep()`. Creating a metadata vector that is the same length as the `rfid_ts` vector will be useful for combining these vectors into a single object later. +```{r} -# The documentation tells us that rep() expects two arguments, x and times +# 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 information that will be repeated -# The argument times specifies the number of times that the information will be repeated +# The argument `x` contains the metadata information that will be repeated +# The argument `times` specifies the number of times that the information will be repeated exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) glimpse(exp_rep) -# You can run code like this without writing out the argument names, as long as the arguments are written in the same order that the function expects: +# You can also run code without writing out the argument names, as long as the arguments are written in the same order that the function expects: exp_rep <- rep("Pair_01", length(rfid_ts)) glimpse(exp_rep) ``` -Using `times = length(rfid_ts)` is better practice than manually entering the length of `rfid_ts`: `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. +Using `times = length(rfid_ts)` is better practice than manually entering the length of `rfid_ts` (e.g. `times = 14`). When you manually enter a value for `times`, you're assuming that the `rfid_ts` has not changed within and between coding sessions, which can be a dangerous assumption. By using `times = length(rfid_ts)`, you are ensuring that the code above will create a vector of metadata the same length as `rfid_ts` regardless of whether you made additional modifications to `rfid_ts` above. You can use a conditional statement to confirm that the vector of metadata is the same length as the vector of RFID timestamps. Conditional statements can be useful ways to check assumptions in your code, or to efficiently build new datasets and functions. -```{r class.source = "bg-warning"} +```{r} # If this condition is met, then the result in the console should be "[1] TRUE" length(rfid_ts) == length(exp_rep) ``` -In this conditional statement, you're using the symbols `==` to ask if the two vectors are the same length. You can also use the symbols `!=` to ask if the two vectors are *not* the same length: -```{r class.source = "bg-warning"} +In the conditional statement above, you're using the symbols `==` to ask if the two vectors `rfid_ts` and `exp_rep` are the same length. + +You can also use the symbols `!=` to ask if the two vectors are *not* the same length: +```{r} -# This statement should yield FALSE +# This statement should yield FALSE, because these vectors are the same length length(rfid_ts) != length(exp_rep) ``` -The vectors `rfid_ts` and `exp_rep` must be the same length in order to combine these vectors into a data frame. To check that this is true, you can use square bracket index to filter out 4 elements of the vector `rfid_ts` so that this vector is 10 elements long. -```{r class.source = "bg-warning"} +As an example, you can modify `rfid_ts` so that it is a different length than `exp_rep`. Below you'll see some different ways that you can filter out 4 elements of the vector `rfid_ts` so that this vector is 10 elements long. +```{r} # You can use : to create a sequence of numbers from indices 5 to the length of rfid_ts 5:length(rfid_ts) @@ -218,8 +220,20 @@ rfid_ts[-c(1:4)] # the numbers must be wrapped in `c()` in order for this invert ``` -When you try making a data frame with vectors of different length, you should get an error message stating that the two arguments provided have different numbers of rows. -```{r class.source = "bg-warning", eval = FALSE} +Next, you can check whether the modified version of `rfid_ts` is the same length as `exp_rep`: +```{r} + +# This statement should yield TRUE, because these vectors are not the same length +length(rfid_ts[-c(1:4)]) != length(exp_rep) + +``` + +

Create data frames with primary data and metadata

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

Create data frames using the tidyverse

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

Vignette Overview and Learning Objectives

+

Vignette overview and learning objectives

In this fourth vignette, you will write out spreadsheets of simulated detections of animal movements to your computer. You will continue to use coding skills that you learned in the previous vignettes, and you will learn additional skills that include: diff --git a/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd b/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd index 5d8b0ae..df463bf 100644 --- a/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd +++ b/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd @@ -11,7 +11,7 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE) ``` -

Vignette Overview and Learning Objectives

+

Vignette overview and learning objectives

In this fifth vignette, you will begin using the simulated detections of animal movements in the ABISSMAL data processing and analysis workflow, including combining raw data across days and pre-processing the raw data. You will also make visualizations of the processed data. You will continue to use coding skills that you learned in the previous vignettes, and you will learn additional skills that include: diff --git a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd index d6ac2ba..f1413ed 100644 --- a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd +++ b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd @@ -11,7 +11,7 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE) ``` -

Vignette Overview and Learning Objectives

+

Vignette overview and learning objectives

In this sixth 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. From 25e38027c25801fef9aaf04f91a5ca9274fefe93 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Thu, 21 Mar 2024 16:05:30 -0400 Subject: [PATCH 30/69] [PCT-472]: Run-through of vignette 4 --- R/vignettes/Vignette_04_SaveData.Rmd | 118 +- R/vignettes/Vignette_04_SaveData.html | 2459 +++++++++++++++++ .../Vignette_05_ProcessData_BuildPlots.Rmd | 7 +- .../Vignette_06_FinishAnalysisPipeline.Rmd | 7 +- 4 files changed, 2543 insertions(+), 48 deletions(-) create mode 100644 R/vignettes/Vignette_04_SaveData.html diff --git a/R/vignettes/Vignette_04_SaveData.Rmd b/R/vignettes/Vignette_04_SaveData.Rmd index 0b07367..e1e16a7 100644 --- a/R/vignettes/Vignette_04_SaveData.Rmd +++ b/R/vignettes/Vignette_04_SaveData.Rmd @@ -2,7 +2,12 @@ title: "Vignette 04: Save Data" author: "Grace Smith-Vidaurre" date: "2023-12-27" -output: html_document +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 --- ```{r setup, include = FALSE} @@ -13,7 +18,7 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE)

Vignette overview and learning objectives

-In this fourth vignette, you will write out spreadsheets of simulated detections of animal movements to your computer. You will continue to use coding skills that you learned in the previous vignettes, and you will learn additional skills that include: +In this fourth vignette, you will write out spreadsheets of simulated detections of animal movements to your computer. You will continue to use coding skills that you learned in the previous vignettes, and you'll learn additional skills that include: 1. Indexing and filtering data frames 2. Checking data frame structure with base R and the tidyverse @@ -35,7 +40,7 @@ path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Initialize an object wit

Create the simulated data

-In the code below, you'll recreate the simulated RFID and beam breaker datasets that you learned to build in the previous vignette. Here the code from vignette 03 has been condensed into fewer chunks. +In the code below, you'll recreate the simulated RFID and beam breaker datasets that you learned to build in the previous vignette. Here, the code from vignette 03 has been condensed into fewer chunks: ```{r} # Create a vector of 4 RFID timestamps in HH:MM:SS format @@ -106,7 +111,7 @@ glimpse(sim_dats_rfid) Next, you can use this data frame to simulate data collection over more than one day. To create more observations (rows) for two additional days, you can append rows from a modified copy of `sim_dats_rfid` to itself. -In the code below, you're piping `sim_dats_rfid` into `bind_rows()`, which indicates that this is the original object to which you want to append 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. +In the code below, you're piping `sim_dats_rfid` into `bind_rows()`, which indicates that this is the original object to which you want to append or add new rows. Then the code inside of `bind_rows()` indicates the data frame (the new rows) that will be appended to `sim_dats_rfid`. In this case, the code inside of `bind_rows()` pipes `sim_dats_rfid` into `dplyr::mutate()`, which is a function that you're using to modify the `day` column to reflect a subsequent day of data collection. Then you'll repeat this process to simulate a third day of data collection: ```{r} sim_dats_rfid <- sim_dats_rfid %>% @@ -129,7 +134,7 @@ glimpse(sim_dats_rfid) # Triple the number of rows, looks good

Check data frame columns with base R

-You checked that simulated data frame has data collected over two days 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: +You checked that simulated data frame has data collected over three days by looking at the structure of the new object. You can also check the unique values present in the column `day`. Below is a way of checking the unique values contained in a column of a data frame, using two different examples of base R notation for accessing columns: ```{r} # The name of a data frame object, followed by a $ sign and then the name of a column allows you to pull out one column at a time from a data frame. A data frame column is a vector, so when you run this code you will see a vector of values print to the console @@ -147,7 +152,7 @@ unique(sim_dats_rfid[["day"]]) # Three days, looks good

Check data frame columns with the tidyverse

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

Simulate 3 days of beam breaker data collection

-Next, repeat this process of creating a data frame with metadata for the infrared beam breaker datasets. Since the beam breakers do not collect unique individual identity information, you will add columns for the year, month, day, and sensor type. You will also simulate data collection for the beam breakers over the same two days as the RFID system. +Next, repeat this process of creating a data frame with metadata for the infrared beam breaker datasets. Since the beam breakers do not collect unique individual identity information, you will add columns for the year, month, day, and sensor type. You will also simulate data collection for the beam breakers over the same three days as the RFID system. ```{r} # Overwrite the vector exp_rep with a new vector the same length as o_irbb_ts and i_irbb_ts together @@ -205,7 +210,7 @@ sim_dats_irbb %>%

Save a data frame as a physical file

-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: +The data frames that you create and manipulate in R can be saved as physical files in your working directory. You have many different file type options for saving data frames, but I recommend using .csv format since this file type is compatible with R, Microsoft Word, and other software. You can use the function `write.csv()` to save data frames to .csv spreadsheets on your computer: ```{r eval = FALSE} ?write.csv @@ -218,6 +223,8 @@ In order to write a physical file to your working directory, you need to tell R # Create a custom file name by combining the path for your working directory with the file name that you want to write out # The function file.path() will combine both pieces of information into a single file path rfid_file <- file.path(path, "test_file.csv") + +# This object contains the location where the file will be saved, followed by the file name rfid_file ``` @@ -227,27 +234,32 @@ Next, you can pipe the data frame to `write.csv()` and specify additional inform sim_dats_rfid %>% # Write out the data frame as a .csv spreadsheet. Do not include row names - #The "." below means the function will use the object that is piped in, which here is the data frame sim_dats_rfid + #The "." below means that the function `write.csv()` will operate 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. If you already created this file then it will be overwritten when you run the function again. +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. In the second usage of `list.files()` below, you're using a new argument called `pattern` that makes it possible to customize a search. Using the argument `pattern` here is similar to searching for a specific word inside of a text document. Below, 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". +Next, you can check that `write.csv()` worked as expected by using `list.files()` to check the files in your working directory. ```{r eval = FALSE} # List all files in the given path list.files(path) +``` + +You can also use `list.files()` to customize a search with the argument called `pattern`. Using the argument `pattern` here is similar to searching for a specific word inside of a text document. The dollar sign placed after ".csv" means that you're telling the function to search specifically for all files that *end* in the pattern ".csv": +```{r eval = FALSE} + # List only files that end in the pattern ".csv" in the given path list.files(path, pattern = ".csv$") ``` -

Read in a spreadsheet

Read in a spreadsheet -You can read one these files back into R with the function `read.csv()`. In the code below, you're piping the output of `read.csv()` directly into `glimpse()` to check out the structure of the resulting data frame. The output of the code is printed to the console but is not saved inside of an object for later manipulation. Instead, you will be using a custom function from ABISSMAL that automatically reads in each spreadsheet. +You can read one these files back into R with the function `read.csv()`. In the code below, you're piping the output of `read.csv()` directly into `glimpse()` to check out the structure of the resulting data frame. The output of the code is printed to the console, but is not saved inside of an object. ```{r eval = FALSE} read.csv(file.path(path, "test_file.csv")) %>% @@ -288,7 +300,7 @@ sim_dats_rfid %>% ``` -You can can similar results by inverting the conditional statement inside of `dplyr::filter()` to remove days that are not the first day of data collection. +You can obtain similar results by inverting the conditional statement inside of `dplyr::filter()` to remove days that are **not** the second day of data collection. ```{r} sim_dats_rfid %>% @@ -304,7 +316,7 @@ sim_dats_rfid %>% ``` -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. +Now that you've practiced filtering a data frame, you can combine this filtering step with writing out a .csv file that contains the filtered data frame with data collected on a single day: ```{r eval = FALSE} # Make a new directory inside of your working directory for saving data @@ -320,10 +332,11 @@ dir.create(file.path(path, "Data", "RFID")) # Create the new path 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 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 by 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) ``` @@ -342,7 +355,7 @@ file.remove(rfid_file)

Practicing writing a loop

-In order to write out a data frame for each day of data collection per sensor, you could repeat the code above 4 times (twice per sensor). But it's good to avoid repeating 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. +In order to write out a data frame for each day of data collection per sensor, you could repeat the code above 4 times (twice per sensor). But it's good to avoid repeating the same code over and over, since this can lead to messy scripts as well as a greater risk for errors in data processing and analysis. Whenever you need to run the same code many times, you can write a loop. Loops are an important coding skill and we'll work through building a loop step by step. To start, you can practice building a loop with the function `lapply()`. ```{r} @@ -355,11 +368,14 @@ files <- c(file.path(path, "Data/RFID", "test1.csv"), file.path(path, "Data/RFID files length(files) -# 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 in each iteration of the loop +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 -# 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 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 +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 in each iteration of the loop. +```{r} + +# In this loop, the iterating variable `x` will take on each value in the vector supplied to the `X` argument. This means that in the first loop iteration, `x` will take on the numeric value of 1. In the second loop iteration, `x` will hold the numeric value 2. To test that this is true, you can run the loop below, which will print the value of x per iteration to the console lapply(X = 1:length(files), FUN = function(x){ x @@ -368,11 +384,13 @@ lapply(X = 1:length(files), FUN = function(x){ ``` -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 and setting `x` as the iterating variable will not affect other lines of code outside of the loop. +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. +A useful property of the iterating variable is that you can use it to index vectors, data frames, or other objects that you created outside of the loop. For instance, you can use `x` and square bracket indexing to print the name of each file that you want to save: ```{r} lapply(X = 1:length(files), FUN = function(x){ @@ -383,11 +401,12 @@ lapply(X = 1:length(files), FUN = function(x){ ``` -You can also modify the code inside 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()`. +You can also modify the code inside of the custom function to save each of these files, by placing the expression `files[x]` inside of the `write.csv()` function and piping a data frame to `write.csv()`. ```{r eval = FALSE} lapply(X = 1:length(files), FUN = function(x){ + # In each iteration of the loop, you will save the data frame `sim_dats_rfid` as a separate spreadsheet with the file name specified in the given iteration sim_dats_rfid %>% write.csv(file = files[x], row.names = FALSE) @@ -395,14 +414,14 @@ lapply(X = 1:length(files), FUN = function(x){ ``` -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 .csv files were written out: +You should see 2 "NULL" outputs printed to the console if the loop runs correctly. When you check the contents of the nested RFID folder, you should see that both of the testing .csv files were written out: ```{r} list.files(file.path(path, "Data/RFID")) ``` -You just wrote out 2 spreadsheets using 1 loop, but you wrote out the same data frame to each spreadsheet. In order to write out a different data frame to each spreadsheet, you add the data frame filtering that you learned above to the loop. In the code below, you'll create another vector object called `days`, and then use the iterating variable to filter `sim_dats_rfid` by each day in `days`. +You just wrote out 2 spreadsheets using 1 loop, but you wrote out the same data frame to each spreadsheet. In order to write out a different data frame to each spreadsheet, you can add the data frame filtering that you learned above to the loop. In the code below, you'll also create another vector object called `days`, and then use the iterating variable to filter `sim_dats_rfid` and write out a spreadsheet by each day in `days`. ```{r eval = FALSE} days <- c(1, 2, 3) @@ -411,14 +430,15 @@ 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]) %>% + dplyr::filter(day == days[x]) %>% + # Write out data frame filtered by the given data 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. The string that you passed 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. +You can delete these files that you created for testing. In the code below, you're seeing another pattern searching example while checking whether the function ran correctly. The string that you pass to the argument `pattern` starts with the symbol `^`, which is a symbol that indicates you're searching for all files that *start* with the pattern "test". You're also telling the function to return the full location of each file along with the file name, so that `file.remove()` knows exactly where to look for each file. ```{r eval = FALSE} rem_files <- list.files(file.path(path, "Data/RFID"), pattern = "^test", full.names = TRUE) @@ -434,7 +454,11 @@ Now you can put all of these pieces together and use the loop to write out a spr ```{r eval = FALSE} # Make a vector of the custom file names to write out -files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv") +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) @@ -449,23 +473,24 @@ 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]) %>% + 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 as an object in R. 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. +In the code above, you'll see one additional change that we made to the loop by wrapping it in the function `invisible()`. This function silences the output printed to the console that you saw several times above, in which the output of each `lapply()` iteration is enclosed in double square brackets, and then a single pair of square brackets. `lapply()` is a function that returns a list, and when the function is carried out correctly but there is not output to print, the function will return `NULL` values (indicating empty output). This is the expected behavior of `lapply()` for our purposes, because we used `lapply()` to write out physical files rather than to return output to the R console. Since you can check that the function worked by running `list.files()`, using `invisible()` helps clean up the amount of text that you need to check in your console. ```{r eval = FALSE} -# Both new .csv files for each day of RFID data are present, looks good +# 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. +You can remove these files for now, since you will work on writing a nested loop structure that automatically writes out the data for each sensor type per day. ```{r eval = FALSE} files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv") @@ -478,21 +503,21 @@ file.remove(files) ``` -You can continue practicing loops by repeating this process for the infrared beam breaker dataset. +If you want more practice writing loops, you can write out a loop structure to save a spreadsheet for each day of data collection for the infrared beam breaker dataset.

Write a nested loop to save spreadsheets

-If you want to cut down on the amount of code that you're writing, you could write out both the RFID and beam breaker data in the same loop. First you'll need to create another directory for the beam breaker data: +If you want to cut down on the amount of code that you're writing to save the raw data per sensor and day of data collection, you could write out both the RFID and beam breaker data in the same looping structure. To continue, you'll need to create another directory for the beam breaker data: ```{r eval = FALSE} dir.create(file.path(path, "Data", "IRBB")) ``` -To carry out the file filtering and writing, you'll use a type of object called a `list`, and then use these lists inside of a nested loop: +To carry out the file filtering and writing for both sensor types across days, you'll use a type of object called a `list`. You will then use these lists inside of a nested loop structure: ```{r} -# Make a list of the custom file names to write out +# 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") @@ -502,7 +527,7 @@ glimpse(files) ``` -Lists are useful objects to use in R because they're very flexible. Unlike vectors, a single list can hold many different types of data. Unlike a data frame, the elements of a list do not need to be the same dimension. The elements of a list can also be different data structures or object types. For instance, lists can contain vectors, data frames, and other lists all inside of the same object. The list that you created above has 2 elements, and each element is a vector of 2 strings containing file names that you supplied using the function `c()`. +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. For instance, the list that you created above has 2 elements, and each element is a vector of 3 character strings containing the file names that you supplied using the function `c()`. You can index lists in a manner that is similar to indexing vectors and data frames, but indexing lists can be done with single or double square brackets for different outcomes: ```{r} @@ -543,7 +568,7 @@ files[["RFID"]] ``` -Lists are useful data structures for nested loops. For instance, if you want to write out a spreadsheet per day per sensor type, you need a loop to 1) iterate over sensor types, and then a loop to 2) iterate over days per sensor. You can use lists to create nested data structures to supply to a nested loop to 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 each list element): +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 a loop to 1) iterate over sensor types, and then a loop to 2) iterate over days per sensor. You can use lists to create nested data structures to supply to a nested loop, which will help you make sure that each layer of the loop runs in the way that you expect. For instance, the list called `files` reflects the nested loop that you need because files are listed first by sensor type (each element of the list) and then by date (each element of the vector inside of each list element): ```{r} # Make a vector of sensor labels @@ -576,7 +601,7 @@ days <- list( days -# Finally, you need to make a list of the data frames that you want to write out. Since you already know how to filter data frames by day, that code can go inside of the loop, and you only need to specify a data frame per sensor type here +# 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 @@ -586,11 +611,11 @@ glimpse(dats) ``` -Once you've set up the data structures that you want to loop over, you can write out the nested loop. Since this is a complex loop structure, it can be helpful to test the loop with set values of each iterating variable (`x` and `y`). +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 it, 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 (here, the first iteration for each loop layer). +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. +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. 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: ```{r eval = FALSE} @@ -645,14 +670,15 @@ invisible(lapply(1:length(sensors), function(x){ # This indexing is important to set up the next loop correctly days_tmp <- days[[sensors[x]]] - # For each sensor, iterate over days + # 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 and single bracket filtering to pull out the right file name + # 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) }) @@ -670,4 +696,4 @@ list.files(file.path(path, "Data/IRBB")) ``` -You learned more about filtering data frames, writing them out to spreadsheets, and writing loops in this vignette. In the next vignette, you'll use the spreadsheets of simulated RFID and IRBB data to start the ABISSMAL data processing and analysis workflow. \ No newline at end of file +You learned more about filtering data frames, writing them out to spreadsheets, and writing single-layer and nested loops in this vignette. In the next vignette, you'll use the spreadsheets of simulated RFID and IRBB data to start the ABISSMAL data processing and analysis workflow. \ No newline at end of file diff --git a/R/vignettes/Vignette_04_SaveData.html b/R/vignettes/Vignette_04_SaveData.html new file mode 100644 index 0000000..61ee9ff --- /dev/null +++ b/R/vignettes/Vignette_04_SaveData.html @@ -0,0 +1,2459 @@ + + + + + + + + + + + + + + + +Vignette 04: Save Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Vignette overview and learning objectives +

+

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

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

+Load packages and your working directory path +

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

+Create the simulated data +

+

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

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

+Simulate 3 days of RFID data collection +

+

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

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

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

+

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

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

+Check data frame columns with base R +

+

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

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

+Check data frame columns with the tidyverse +

+

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

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

+Simulate 3 days of beam breaker data collection +

+

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

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

+Save a data frame as a physical file +

+

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

+
?write.csv
+

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

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

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

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

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

+

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

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

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

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

+Read in a spreadsheet +

+

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

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

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

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

+Save simulated data for the ABISSMAL workflow +

+

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

+

+Filter a data frame +

+

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

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

You can obtain similar results by inverting the conditional statement +inside of dplyr::filter() to remove days that are +not the first day of data collection.

+
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) %>%
+  glimpse()
+
## Rows: 28
+## 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, 3, 3, 3, 3, 3, 3,…
+## $ 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) %>%
+  pull(day) %>% 
+  unique()
+
## [1] 1 3
+

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

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

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

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

+Practicing writing a loop +

+

In order to write out a data frame for each day of data collection +per sensor, you could repeat the code above 4 times (twice 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 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
+

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 data to a separate spreadsheet 
+    write.csv(file = files[x], row.names = FALSE)
+  
+})
+

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

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

+Use a loop to save RFID spreadsheets +

+

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

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

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

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

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

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

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

+

+Write a nested loop to save spreadsheets +

+

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

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

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

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

Lists are useful objects because they’re very flexible. Unlike +vectors, a single list can hold many different types of data. And then +unlike a data frame, the elements of a list do not need to have the same +dimensions. The elements of a list can also be different data structures +or object types themselves. For instance, lists can contain vectors, +data frames, and other lists all inside of the same larger list object. +For instance, 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 a loop to 1) iterate over sensor types, and +then a loop to 2) 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.

+

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:

+
# Testing
+x <- 1
+y <- 1
+
+# 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
+  
+  # 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 4 files now exist inside +each directory per sensor within your working directory.

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

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

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd b/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd index df463bf..b9cd245 100644 --- a/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd +++ b/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd @@ -2,7 +2,12 @@ title: "Vignette 05: Process and Plot Data" author: "Grace Smith-Vidaurre" date: "2023-12-27" -output: html_document +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 --- ```{r setup, include = FALSE} diff --git a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd index f1413ed..b3abbce 100644 --- a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd +++ b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd @@ -2,7 +2,12 @@ title: "Vignette 06: Finish Analysis Pipeline" author: "Grace Smith-Vidaurre" date: "2023-12-27" -output: html_document +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 --- ```{r setup, include = FALSE} From 22f5695d5156ddf9b45dca09309ca4f86e992747 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Thu, 21 Mar 2024 16:21:14 -0400 Subject: [PATCH 31/69] [PCT-472]: Run-through of fifth vignette --- R/vignettes/Vignette_04_SaveData.html | 2459 ----------------- .../Vignette_05_ProcessData_BuildPlots.Rmd | 47 +- .../Vignette_06_FinishAnalysisPipeline.Rmd | 2 +- 3 files changed, 23 insertions(+), 2485 deletions(-) delete mode 100644 R/vignettes/Vignette_04_SaveData.html diff --git a/R/vignettes/Vignette_04_SaveData.html b/R/vignettes/Vignette_04_SaveData.html deleted file mode 100644 index 61ee9ff..0000000 --- a/R/vignettes/Vignette_04_SaveData.html +++ /dev/null @@ -1,2459 +0,0 @@ - - - - - - - - - - - - - - - -Vignette 04: Save Data - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-
-
-
-
- -
- - - - - - - -

-Vignette overview and learning objectives -

-

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

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

-Load packages and your working directory path -

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

-Create the simulated data -

-

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

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

-Simulate 3 days of RFID data collection -

-

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

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

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

-

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

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

-Check data frame columns with base R -

-

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

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

-Check data frame columns with the tidyverse -

-

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

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

-Simulate 3 days of beam breaker data collection -

-

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

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

-Save a data frame as a physical file -

-

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

-
?write.csv
-

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

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

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

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

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

-

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

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

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

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

-Read in a spreadsheet -

-

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

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

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

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

-Save simulated data for the ABISSMAL workflow -

-

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

-

-Filter a data frame -

-

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

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

You can obtain similar results by inverting the conditional statement -inside of dplyr::filter() to remove days that are -not the first day of data collection.

-
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) %>%
-  glimpse()
-
## Rows: 28
-## 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, 3, 3, 3, 3, 3, 3,…
-## $ 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) %>%
-  pull(day) %>% 
-  unique()
-
## [1] 1 3
-

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

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

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

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

-Practicing writing a loop -

-

In order to write out a data frame for each day of data collection -per sensor, you could repeat the code above 4 times (twice 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 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
-

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 data to a separate spreadsheet 
-    write.csv(file = files[x], row.names = FALSE)
-  
-})
-

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

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

-Use a loop to save RFID spreadsheets -

-

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

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

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

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

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

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

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

-

-Write a nested loop to save spreadsheets -

-

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

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

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

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

Lists are useful objects because they’re very flexible. Unlike -vectors, a single list can hold many different types of data. And then -unlike a data frame, the elements of a list do not need to have the same -dimensions. The elements of a list can also be different data structures -or object types themselves. For instance, lists can contain vectors, -data frames, and other lists all inside of the same larger list object. -For instance, 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 a loop to 1) iterate over sensor types, and -then a loop to 2) 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.

-

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:

-
# Testing
-x <- 1
-y <- 1
-
-# 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
-  
-  # 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 4 files now exist inside -each directory per sensor within your working directory.

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

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

- - - -
-
- -
- - - - - - - - - - - - - - - - diff --git a/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd b/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd index b9cd245..e0e454d 100644 --- a/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd +++ b/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd @@ -59,7 +59,7 @@ source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R")

Access ABISSMAL function information

-After running the lines of code above, you should see that a whole set of functions have been loaded into your global environment (check the `Environment` pane). Many of these functions start with `check_`, and those are utility functions. If you scroll down, you'll see that the three main functions above (`combine_raw_data`, `detect_perching_events`, `preprocess_detections`) are all loaded in your global environment. In the column to the right of the function names you can also see a preview of each function's arguments. +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. @@ -67,7 +67,7 @@ To get more information about each of these three main functions, you can click 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 you are supplying to the function below: +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 @@ -85,33 +85,27 @@ Here goes more information about the arguments you are supplying to the function # 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") -combine_raw_data(sensors = "IRBB", 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 separate spreadsheets of raw RFID and beam breaker data to the new directory `raw_combined`: +You can check that `combine_raw_data()` wrote a spreadsheet of raw RFID data to the new directory `raw_combined`: ```{r} list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") ``` -You can read thw combined .csv files (called "combined_raw_data_RFID.csv" and "combined_raw_data_IRBB.csv") back into R to check out the structure of this spreadsheet: +You can read the combined .csv file (called "combined_raw_data_RFID.csv") back into R to check out the structure of this spreadsheet: ```{r} rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) glimpse(rfid_data) -irbb_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_IRBB.csv")) - -glimpse(irbb_data) - ``` -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 `sensor_id`). +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 created a new timestamps column in POSIXct format for downsteam processing and analysis, but it kept the original timestamps column. The function also 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 not removed not overwritten. +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`: ```{r} @@ -129,7 +123,7 @@ list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$")

Detect perching events

-You can use the combined raw data from different sensors in subsequent ABISSMAL functions to start making behavioral inferences from events in 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. +You can use the combined raw data from different sensors in subsequent ABISSMAL functions to start making behavioral inferences from the movement detection datasets. For instance, you can detect perching events in the raw RFID data with the function `detect_perching_events()`. You can read more about each argument in the R script that contains this function. ```{r} detect_perching_events(file_nm = "combined_raw_data_RFID.csv", threshold = 2, run_length = 2, sensor_id_col_nm = "sensor_id", timestamps_col_nm = "timestamp_ms", PIT_tag_col_nm = "PIT_tag", rfid_label = "RFID", general_metadata_cols = c("chamber_id", "sensor_id"), path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", out_file_prefix = "perching_events", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") @@ -216,7 +210,7 @@ glimpse(irbb_pp) 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 barcode style visualizations of the raw and pre-processed RFID detection datasets. +In the code below, you'll learn how to use functions from the `ggplot2` package to make a barcode style figure of the raw and pre-processed RFID detection datasets. Start by reading in the raw and pre-processed RFID data, as well as the RFID perching events, and convert the timestamps to POSIX format. ```{r} @@ -225,7 +219,8 @@ rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFI # 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")) - ) %>% + ) %>% + # Arrange the timestamps from oldest to most 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 @@ -276,7 +271,7 @@ glimpse(rfid_combined) You will start plotting the data using functions from `ggplot2`. This package is part of the `tidyverse`, but can also be installed and used separately. You can check out this [link](https://ggplot2.tidyverse.org/) for more resources to learn how to use ggplot 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. -Thde `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. +The `ggplot2` package has a unique syntax for building plots, in which you start making a plot by calling the function `ggplot()`, and then add features by layering on other `ggplot2` functions with the `+` symbol. If you call `ggplot()`, you'll see that the function immediately draws a blank plot in your Plots pane in RStudio. ```{r} @@ -292,9 +287,9 @@ 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 (see a plot in the next vignette). +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 x-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, information on the x-axis only). +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 x-axis). In the plot you'll make below, you'll use `geom_segment()` to add lines to the plot that contain temporal information in one dimension (timestamps that provide information on the x-axis only). ```{r} ggplot(data = rfid_combined) + @@ -307,9 +302,9 @@ ggplot(data = rfid_combined) + ``` -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 holding the dataset labels. The `color` argument must be inside of the `aes()` function in order for this color assignment by dataset to work correctly. +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 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()`: +The line segment colors are automatically assigned by `ggplot` using default colors, but you can change these colors using the function `scale_color_manual()`: ```{r} ggplot(data = rfid_combined) + @@ -379,7 +374,7 @@ ggplot(data = rfid_combined) + 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 3 detections for each of the raw and pre-processed datasets: +From this point of view though, it's hard to see how the raw and pre-processed datasets differ. You can filter the data with `tidyverse` functions to plot the first 2 detections for each of the raw and pre-processed datasets: ```{r} ggplot(data = rfid_combined %>% @@ -403,7 +398,7 @@ ggplot(data = rfid_combined %>% You should be able to see that the second timestamp in the raw data was dropped from the pre-processed dataset. -Next, you can add the perching data to the plot. 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 function called `geom_segment()`. This function 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 x-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 (on the x-axis only) about when perching events started and ended. +Next, you can add the perching data to the plot. This dataset doesn't have a single timestamps column, but rather has two columns that indicate the start and the end of each perching event, respectively. You can add this dataset to the full plot by using another `geom_segment()` layer. You'll use `geom_segment()` to add lines to the plot that contain temporal information about when perching events started and ended. ```{r} ggplot(data = rfid_combined) + @@ -429,9 +424,9 @@ ggplot(data = rfid_combined) + ``` -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. +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.positon` 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. +There are some additional changes you can make to this plot to make it more interpretable. You can change the position of the legend using the argument `legend.position` inside of the general function `theme()`. Below you will also save the plot inside of an object, so that you don't have to write out all of the code over and over. ```{r} gg <- ggplot(data = rfid_combined) + @@ -498,4 +493,6 @@ ggsave(file.path(path, "raw_processed_perching.tiff"), width = 8, height = 6, un ``` -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. \ No newline at end of file +You can continue to modify minor aesthetics to this image file to create a high-quality figure for a publication. For instance, you can change the final size of the image file (`width`, `height`), as well as the resolution (`dpi`). You can also change the size of the text on each axis and the axis titles, or the legend position, as you play around with the final image size. + +In the next vignette, you will continue the ABISSMAL data analysis pipeline and make a more complex and refined barcode style figure. \ No newline at end of file diff --git a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd index b3abbce..cbdcba6 100644 --- a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd +++ b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd @@ -18,7 +18,7 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE)

Vignette overview and learning objectives

-In this sixth 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. +In this sixth and last vignette, you will finish using the simulated detections of animal movements in the ABISSMAL data processing and analysis workflow. You will detect clusters of detections that represent distinct movement events, and then score behavioral inferences about the movement events. You will also make plots to visualize these behavioral inferences. You will continue to use coding skills that you learned in the previous vignettes, and you will additionally learn how to build more complex visualizations with ggplot.

Load packages and your working directory path

From 85dcc8c4fe8e5f6e5af81020b66264bd772f021f Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Thu, 21 Mar 2024 16:43:18 -0400 Subject: [PATCH 32/69] [PCT-472]: Run-through of sixth vignette --- R/vignettes/Vignette_01_Introduction.Rmd | 2 ++ .../Vignette_06_FinishAnalysisPipeline.Rmd | 34 +++++++++++-------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/R/vignettes/Vignette_01_Introduction.Rmd b/R/vignettes/Vignette_01_Introduction.Rmd index b5af88b..dae6b3c 100644 --- a/R/vignettes/Vignette_01_Introduction.Rmd +++ b/R/vignettes/Vignette_01_Introduction.Rmd @@ -12,6 +12,8 @@ output:

Vignette overview and learning objectives

+Prior to starting this vignette, it would be very helpful if you could complete the brief Google form for a pre-vignette evaluation that will help us learn more about your experience in R and data science with biological datasets prior to completing these vignettes, and what types of skills you're hoping to improve. Your responses in this pre-vignette form 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. + This first vignette will take you through a brief introduction to RStudio, downloading a local version of the ABISSMAL GitHub repository, and an introduction to the data analysis workflow provided by ABISSMAL. Through this tutorial, you will learn: 1. How to configure your RStudio session diff --git a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd index cbdcba6..dd361cc 100644 --- a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd +++ b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd @@ -76,7 +76,8 @@ scored_clusters <- read.csv(file.path(path, "Data/processed", "scored_detectionC 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) @@ -87,7 +88,7 @@ How many entrances and exits were scored per day?

Missing data in R

-In order to count the number of each of these events scored per day, you need to know how to account for missing data in R. Missing data is often indicated using the value `NA` or "not available", which is a specific type of logical value. 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 column. +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 column. ```{r} ?is.na() @@ -107,11 +108,13 @@ Because `is.na()` is a conditional statement, you can also use other special sym ``` -As you can see, adding the `!` infront 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` along a given column, and will retain a row whenever it encounters a value of `TRUE`. So 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`. +As you can see, adding the `!` in front of `is.na()` results in an inverted output compared to `is.na()` alone, such that each value that was previously `TRUE` is now `FALSE`. Together, the ability to invert the `is.na()` conditional statement, plus the binary output that `is.na()` returns, is really useful for finding and filtering rows of a data frame. + +For instance, the function `dplyr::filter()` will drop a row whenever it encounters a value of `FALSE` in a given column, and will retain a row whenever it encounters a value of `TRUE`. If you want to drop rows that contain NA values for a given column, you would add `!is.na(name_of_column)` inside of `dplyr::filter()`, which should return `FALSE` every time it encounters a row with `NA`, and will remove that row during filtering.

Count events per day

-Now you can write code to count the number of entrance and exit events scored per day. You will need to 1) make a new column with information about the day per timestamp, 2) drop rows with missing data for direction scored, 3) group the data frame by day and direction scored, and then 4) count the number of rows per group. +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, 3) group the data frame by day and direction scored, and then 4) count the number of rows per group. ```{r} scored_clusters %>% @@ -123,14 +126,14 @@ scored_clusters %>% 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) %>% - # The number of rows here is the number of exits or entrances scored per day + # Then summarize the data: the number of rows here is the number of exits or entrances scored per day dplyr::summarise( n = n() ) ``` -Four entrance and exit events were scored per day. Does this line up with the anticipated number of exits and entrances per day? If you go back to the code where you created the simulated raw datasets in vignettes 3 and 4, 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. +Four entrance and exit events were scored per day. Does this line up with the anticipated number of exits and entrances per day? If you go back to the code where you created the simulated raw datasets in vignettes 03 and 04, you should see that you started by simulating 2 entrances and exits per day across the RFID and beam breaker datasets. Then, you added 2 more entrance and 2 more exit events per day when you simulated RFID detection failures (e.g. movements captured by the beam breakers only). `score_clusters()` detected the correct number of entrances and exits per day. Next, check out the perching events. Start by filtering out all rows that were not scored as perching events (rows that have NA values in the column "perching_PIT_tag"). Then, select only the columns that have information that is useful to check: the PIT tag codes, as well as the start and end of each perching event. ```{r} @@ -143,7 +146,7 @@ scored_clusters %>% ``` -As specified in vignettes 3 and 4, 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). +As specified in vignettes 03 and 04, the first perching event each day was performed by the first individual (PIT tag 1357aabbcc), and the second was attributed to the second individual (PIT tag 2468zzyyxx). How many movement events that were not perching events were attributed to each individual? ```{r} @@ -163,17 +166,17 @@ scored_clusters %>% ``` -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 3 and 4). 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. +As you can see, 4 movement events that were not perching events were attributed to the first individual (PIT tag code 1357aabbcc) on each day. This is exactly what we expected when creating the simulated dataset (see vignettes 03 and 04). More non-perching movement events were detected across these days, but those events were not always captured by the RFID antenna (e.g. the simulated RFID detection failures that were captured by the beam breakers only). The beam breakers captured those movement events but cannot record individual identity.

Build complex barcode plot

-Now that you checked out the final results, you can visualize them. In the code below, you'll learn how to build a barcode plot that is more complex than the plot you made in vignette 5. 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. +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. +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 day is the first day of the month), the value you want to add to the vector if the condition is true ("Day 1" of data collection), and then the value that you want to add if the condition is false ("Day 2" of data collection, since there are only 2 dates in each dataset). +First you can practice using `is.na()` inside of `ifelse()` to create a new vector. In the code below, you're providing the conditional statement that you want to test (here you're testing whether the column holding the PIT tag code of the individual that initiated a movement has NAs), the value you want to add to the vector if the condition is true ("unassigned" when no PIT tag code is present), and then the value that you want to add if the condition is false (return the PIT tag code in the individual_initiated column if the given value is not NA). ```{r} # is.na() returns TRUE when it encounters NAs inside of a vector (or column), and FALSE when a value inside of the vector is not NA @@ -209,6 +212,8 @@ scored_clusters_gg %>% ``` +This output looks good. You should see NAs in this data frame, but they're in the column "direction_scored", which means that the direction of some movements could not be scored. + You can use this modified data frame to build the plot. In the code below, you'll add lines colored by individual identity and with line types encoding the direction of movement. First you'll specify the plot aesthetics: ```{r} @@ -264,8 +269,7 @@ ggplot() + ``` -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 end of the first set of segments. +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). @@ -477,7 +481,7 @@ ggsave(file.path(path, "behavioral_inferences.tiff"), width = 8, height = 6, uni You can continue to modify minor aesthetics to this image file to create a high-quality figure for a publication. For instance, you can change the final size of the image file (`width`, `height`), as well as the resolution (`dpi`). You can also change the size of the text on each axis and the axis titles, or the legend position, as you play around with the final image size. This is the all of the code used for the final plot, condensed and reorganized: -```{r} +```{r eval = FALSE} ggplot(data = scored_clusters_gg3) + @@ -553,3 +557,5 @@ ggplot(data = scored_clusters_gg3) + ) ``` + +You completed the final vignette of the ABISSMAL data processing and analysis pipeline. You also practiced your coding and data science skills in a biological context. Well done! It would be very helpful if you could complete the brief Google form for a post-vignette evaluation that will help us continue to improve these vignettes over time. A link to this Google form will be available in the README file for the vignettes. \ No newline at end of file From 4111e5594e3f84816d67e21103115a6d041e6682 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Mon, 25 Mar 2024 13:43:14 -0400 Subject: [PATCH 33/69] [PCT-472]: Started translation of first vignette, 2 sections left --- R/vignettes/.Rhistory | 520 +++++++++++------------ R/vignettes/Vignette_01_Introduccion.Rmd | 120 ++++++ 2 files changed, 380 insertions(+), 260 deletions(-) create mode 100644 R/vignettes/Vignette_01_Introduccion.Rmd diff --git a/R/vignettes/.Rhistory b/R/vignettes/.Rhistory index fd81f25..11ecdcc 100644 --- a/R/vignettes/.Rhistory +++ b/R/vignettes/.Rhistory @@ -1,70 +1,3 @@ -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 assigned to the first 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 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 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") + @@ -73,6 +6,8 @@ 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(), @@ -102,56 +37,9 @@ linetype = guide_legend(title = "Direction"), color = guide_legend(title = "Individual") ) gg -# 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"), -) -# 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") -) +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( @@ -185,6 +73,8 @@ 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 @@ -244,6 +134,8 @@ 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 @@ -269,20 +161,257 @@ panel.grid.minor.y = element_blank(), legend.text = element_text(size = 10), legend.margin = margin(-1, -1, -1, -1, unit = "pt") ) -gg <- ggplot(data = scored_clusters_gg3) + +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_gg3 %>% +data = scored_clusters_gg %>% dplyr::filter(individual_initiated == "1357aabbcc"), -aes(x = start_gg, y = 0, xend = end_gg, yend = 0.5, linetype = direction_scored), +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_gg3 %>% +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_gg, y = 0.6, xend = end_gg, yend = 1.1, linetype = direction_scored), +aes(x = start, y = 0.6, xend = end, yend = 1.1, linetype = direction_scored), color = "black", linewidth = 0.5 ) + @@ -301,14 +430,14 @@ 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)") +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( @@ -318,7 +447,7 @@ 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 +# 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"), @@ -381,132 +510,3 @@ 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) diff --git a/R/vignettes/Vignette_01_Introduccion.Rmd b/R/vignettes/Vignette_01_Introduccion.Rmd new file mode 100644 index 0000000..a860007 --- /dev/null +++ b/R/vignettes/Vignette_01_Introduccion.Rmd @@ -0,0 +1,120 @@ +--- +title: "Tutorial 01: Introducción" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +

Resumen del tutorial y objetivos de aprendizaje

+ +Antes de empezar este tutorial, si puedes completar esta encuesta a través de Google Forms seria una gran ayuda. La información que compartes nos ayudara saber mas de tu experiencia en R y analizando datos biologicos antes de empezar los tutoriales mismos, tanto como las habilidades que buscas mejorar a traves de completar esta coleccion de tutoriales, y tambien nos ayudara mejorar estos tutoriales en el futuro. Puedes encontrar el enlace para la encuesta en el archivo README de los tutoriales. + +En este primer tutorial, vas a leer sobre el programa RStudio, aprender como crear una version local de el repositorio de ABISSMAL en GitHub, y aprender mas sobre el flujo de analisis de datos en ABISSMAL. En este tutorial vas a aprender como: + +1. Configurar tu sesion de RStudio +2. Crear una version local de un repositorio de GitHub +3. Usar ABISSMAL para procesar y analizar datos +4. Solucionar problemas con tu codigo usando recursos en linea +5. Reportar problemas de codigo a GitHub + +Hay muchas formas de hacer una sola tarea o solucionar un problema en R, y deberias de mantener esto en mente mientras completas estos tutoriales. En cada tutorial, vas a aprender diferentes ejemplos sobre como manejar ciertas tares o solucionar problemas especificas con codigo, pero estos ejemplos no son un resumen exhaustivo de como manejar cada tarea ni solucionar cada problema. Mas bien recomiendo que uses estos tutoriales como una oportunidad para practicar como usar tus habilidades de escribir codigo en un contexto biologico. + +

Configurar tu sesion de RStudio

+ +Si nunca has usado R, un lenguaje de programacion para analisis estadisticos, ni RStudio, una interfaz grafica de usuario, recomiendo revisar el tutorial [R for Reproducible Scientific Analysis](https://swcarpentry.github.io/r-novice-gapminder-es/) de [Software Carpentry](https://software-carpentry.org/). + +Cuando hayas instalado R y RStudio en tu computadora, puedes hacer click en el iconon de RStudio para abrir el programa. La configuracion predeterminada de los paneles de RStudio se deberia de ver algo asi, aunque el color de fondo puede ser diferente: + +
+![Un imagen de la configuracion predeterminada de los paneles de RStudio, con el panel de la consola debajo del panel de la fuente](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/RStudio_01.png) +Con esta configuracion de paneles es dificil ver el codigo que escribes y guardas en archivos fisicos en el panel de la fuente, tanto como el resultado de ese codigo en el panel de la consola. Puedes reconfigurar los paneles para que el panel de la fuente este al lado izquierdo del panel de la consola, y asi sera mas facil de ver los resultados de tu codigo inmediatamente. Puedes seguir los siguientes pasos para reconfigurar los paneles en RStudio: + +* Seleciona la opcion "Tools" en el menu + +* Seleciona la opcion "Global Options" en el menu desplegable que sale en "Tools" + +* Se va a abrir otra ventana con opciones. Selecciona la opcion "Pane Layout" a la izquierda + +* Usa el menu desplegable para seleccionar "Source" como el panel en la primera fila y al lado izquierdo, y seleciona "Console" como el panel que ocupara la siguiente parte de la primera fila de paneles a mano derecha + +* Puedes seleccionar "Apply" para aplicar estos cambios y luego "Ok" para salir de esta ventana + +Tu configuracion de paneles en RStudio ahora se deberia de ver asi: + +
+![Un imagen de la configuracion modificada de los paneles de RStudio, con el panel de la consola al lado derecho y el panel de la fuente al lado izquierdo](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/RStudio_02.png) + +Otro cambio que puedes implementar en tu configuracion de RStudio es el retorno automatico, para no tener que hacer scroll a tu derecha cada vez que quieres leer una linea completa de texto o codigo. Para implementar este cambio, puedes ir a "Tools", luego "Global Options", seleccionar "Code", y seleccionar la caja para la opcion "Soft-wrap R source files", luego hacer click en "Apply" y "Ok". + + +Tambien puedes cambiar el tamano de la fuente del texto y codigo que escribes, tanto como el color de texto y codigo, y el color de fondo de tu ventana de RStudio. Despues de selecionar "Tools" y "Global Options", puedes seleccionar "Appearance" para ver diferentes opciones. + +

Crear una version local de un repositorio de GitHub

+ +Si nunca has usado GitHub, recomiendo bajar el programa [GitHub Desktop](https://docs.github.com/es/desktop). Este programa es una interfaz grafica de usuario para el plataforma de GitHub, que provee un sistema de control de versiones. Vamos a seguir unos pasos para crear una version local del repositorio de ABISSMAL que esta en GitHub para que puedas acceder las funciones de R de ABISSMAL en tu propia computadora. Si ya sabes como usar GitHub y como usar Git en el terminal, puedes seguir las instrucciones que tenemos en el [README de ABISSMAL](https://github.com/lastralab/ABISSMAL). + +Cuando hayas instalado GitHub Desktop, puedes: + +* Hacer click en el icono 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 click en el boton verde de "Code" y copiar la URL debajo de la opcion HTTPS en el menu desplegable + +* Ir al menu de GitHub Desktop window y seleccionar "File" + +* Seleccionar "Clone repository" + +* Seleccionar la pestana "URL" + +* Pegar la URL de ABISSMAL que copiaste de GitHub en la caja que pide "Repository URL or GitHub username and repository" + +* Revisar que el directorio en la caja "Local path" es la ubicacion 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 instalara directamente en tu Desktop + +* Seleccionar "Clone" cuando estes lista para crear una version local del repositorio de ABISSMAL en tu computadora + +Cuando el repositorio se ha instalado en tu computadora, deberias de poder ver el siguiente directorio y lista de archvios adentro de una carpeta llamada "ABISSMAL": + +
+![Un imagen del directorio de la version local del repositorio de ABISSMAL](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/ABISSMAL_localrepo.png) + +Los archivos que vamos a usar en los siguientes tutoriales estan adentro de la carpeta "R". Esta carpeta contiene 6 archivos de R (extension ".R"), un archivo README que contiene mas informacion sobre cada archivo de R, y carpetas con los tutoriales en formato RMarkdown (extension ".Rmd"), y tambien codigo para pruebas unitarias automatizadas. + +

Procesar y analizar datos con ABISSMAL

+ +ABISSMAL provee 5 funciones diferentes de R para procesar y analizar datos, y describimos estas funciones en mas detalle abajo, en el orden que se deberian de usar in general. Puedes encontrar mas informacion detallada sobre estas funciones en el archivo README de la carpeta "R", y tambien el manuscrito asociado con ABISSMAL: + +1. `combine_raw_data` automaticamente combina hojas de calculo de datos originales que fueron colectados por dia y los guarda en una sola hoja de calculo a traves de todos los dias de coleccion de datos. Esta concatenacion de datos se realiza por cada tipo de sensor, por ejemplo, los sensores de rayos infrarrojos o una camara de infrarrojo, y no cambia ni reemplaza los datos originales. Esta funcion puede usar datos de los sensors de RFID (radio frequency identification), rayos infrarrojo, la camara, o el sensor de temperatura de ABISSMAL. Las siguientes funciones no aceptan datos de temperatura + +2. `detect_perching_events` toma como entrada los resultados de `combine_raw_data` y detecta grupos de detecciones que occurrieron cerca en el tiempo como eventos de posar. Estos eventos de posa representan periodos de tiempo cuando un individuo estuvo posado en el antena de RFID (como percha) situado en la entrada del contenedor de nido. La function usa datos de los sensors ubicados en la entrada del contenedor (RFID o los sensors de infrarrojo) y devuelve una hoja de calculo de las coordenadas temporales de los eventos de posar inferidos + +3. `preprocess_detections` usa como entrada los resultados de `combine_raw_data` y remueve detecciones que occurrieron muy cerca en el tiempo. Esta funcion devuelve una hoja de calculo de datos pre-procesados por tipo de sensor, y en cada hoja de calculo, las detecciones consecutivas deberian de estar separadas por un umbral temporal predeterminado o mas. Por ejemplo, cuando usas un umbral de 1 segundo, solo una deteccion puede occurir por segundo en los datos pre-procesados + +4. `detect_clusters` usa como entrada los resultados de `preprocess_detections` obtenidos con 2 o mas tipos de sensores. La funcion identifica detecciones a traves de 2 o mas tipos de sensores que occurieron cerca en el tiempo, y devuelve informacion temporal y metadatos sobre cada grupo o cumulo. Cada cumulo de detecciones representa un evento discreto de movimiento de un individuo o mas que un individuo + +5. `score_clusters` usa como entrada los resultados de `detect_clusters`. Esta funcion tambien puede usar los resultados de `detect_perching_events` para integrar eventos de posar identificados en los datos originales de RFID o los sensores de infrarrojo, y puede usar los resultados de `preprocess_detections` para integrar eventos de grabacion de videos que no fueron incluidos en los cumulos detectados por `detect_clusters`. Esta funcion hace inferencias de comportamiento de los eventos de movimento, incluyendo la direccion de movimiento, el magnitud de movimiento, identidad de individuo (cuando datos de RFID se encontraron en un cumulo), y donde occurrio el inicio de la secuecia de movimiento (en la entrada o adentro del contenedor de nido). La funcion devuelve una hoja de calculo de inferencias de comportamientos y otros metadatos sobre cada evento de movimiento, y estos resultados se pueden usar para visualizaciones y analisis estadisticos. + +
+![Figure 4 del manuscrito de ABISSMAL con un flujo general de trabajo a traves de las 5 funciones primarias](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/Figure4_ComputationalAnalyses.png) +* La figura es de Smith-Vidaurre, G., Molina, T., Jarvis, E.D., and E.A. Hobson. 2023. Automated tracking of avian parental care. [EcoEvoRxiv preprint]((https://ecoevorxiv.org/repository/view/6268/)). + +TKTK continue translation + +

Troubleshoot errors online

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

Report bugs on GitHub

+ +As you work through each vignette, you may encounter "bugs" or errors with the code, including code that does not work or that yields incorrect outcomes. These bugs can occur with the ABISSMAL functions themselves, or with the code in a given vignette. When you encounter a bug with an ABISSMAL function, you can create an Issue through the GitHub repository. To create a new Issue, you can select "New Issue" on the [Issues page](https://github.com/lastralab/ABISSMAL/issues) of the main ABISSMAL repository, then follow the instructions in the Issue template to add the information needed for the code developers to address the bug effectively. You can also add the tag "bug" to your issue. If you encounter an issue with the ABISSMAL vignettes, then you can submit an issue through the main ABISSMAL GitHub repository as well, as long as you clarify that the bug is related to code in a given vignette. + +In the next vignette, we will create simulated datasets of raw movement data to learn how to use the different ABISSMAL functions described above. \ No newline at end of file From 18ad7728cf43a4ed02ec3ce788aed6dff7183d02 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Wed, 27 Mar 2024 11:36:15 -0400 Subject: [PATCH 34/69] [PCT-472]: Finished initial translation of first vignette; still need to correct spelling errors/accents --- R/vignettes/Vignette_01_Introduccion.Rmd | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/R/vignettes/Vignette_01_Introduccion.Rmd b/R/vignettes/Vignette_01_Introduccion.Rmd index a860007..2e550ae 100644 --- a/R/vignettes/Vignette_01_Introduccion.Rmd +++ b/R/vignettes/Vignette_01_Introduccion.Rmd @@ -101,20 +101,22 @@ ABISSMAL provee 5 funciones diferentes de R para procesar y analizar datos, y de
![Figure 4 del manuscrito de ABISSMAL con un flujo general de trabajo a traves de las 5 funciones primarias](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/Figure4_ComputationalAnalyses.png) -* La figura es de Smith-Vidaurre, G., Molina, T., Jarvis, E.D., and E.A. Hobson. 2023. Automated tracking of avian parental care. [EcoEvoRxiv preprint]((https://ecoevorxiv.org/repository/view/6268/)). +* Esta figura es de Smith-Vidaurre, G., Molina, T., Jarvis, E.D., and E.A. Hobson. 2023. Automated tracking of avian parental care. [EcoEvoRxiv preprint]((https://ecoevorxiv.org/repository/view/6268/)). -TKTK continue translation +

Solucionar errors en linea

-

Troubleshoot errors online

+Mientras escribes y ejecutas codigo encontraras errores que a veces pueden ser frustrantes. Experimentar y solucionar errors es una parte muy importante de tu proceso de aprendizaje en R (u otros lenguajes de programacion). Los errores surgen por diferentes razones, incluyendo errores de tipeo mientras escribes codigo, 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 errors a veces pueden surgir por problemas con las funciones de ABISSMAL que usaras en los siguientes tutoriales, or por errores con el codigo en los tutoriales mismos. -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. +Cuando experimentas un error con tu condigo, es importante intentar solucionar el error independientement antes de suponer que el error nacio de errores de las funciones de ABISSMAL o los tutoriales. Hay muchos recursos en linea 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 asi deberias de ver varias opciones de foros publicos donde otras personas han preguntado por y solucionado errores similares. Tambien deberias poder de usar herramientas de IA generativo como ChatGPT para buscar errores de tipeo u otros problemas con tu codigo. Otra opcion util es leer la documentation de R para investigar si el error que ves esta relacionado con un paquete o funcion que puede ser una dependencia de ABISSMAL. -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. +Ya cuando hayas investigado cuidadosamente un error, y estas segura que el error no se debe a un error de tipeo 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 seccion). -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). +

Reportar errores en GitHub

-

Report bugs on GitHub

+Es posible que encuentres errores mientras trabajas en cada tutorial, incluyendo errores con codigo que no ejecuta, y que devuelve resultados incorrectos. Estos errores pueden manifestar de las funciones de ABISSMAL o con el codigo de algun tutorial. Cuando encuentras un error en una funcion de ABISSMAL, puedes crear un "Issue" (asunto o problema) en el repositorio de GitHub. Para crear un asunto nuevo, puedes seleccionar "New Issue" en la pagina de [Issues](https://github.com/lastralab/ABISSMAL/issues) en el repositorio de ABISSMAL, y seguir las instrucciones en el esquema del "Issue" nuevo para anadir la informacion que las desarrolladoras necesitan para trabajar efectivamente en el error. Tambien puedes anadir 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 deberias de clarificar que el error esta relacionado al codigo de algun tutorial en particular. -As you work through each vignette, you may encounter "bugs" or errors with the code, including code that does not work or that yields incorrect outcomes. These bugs can occur with the ABISSMAL functions themselves, or with the code in a given vignette. When you encounter a bug with an ABISSMAL function, you can create an Issue through the GitHub repository. To create a new Issue, you can select "New Issue" on the [Issues page](https://github.com/lastralab/ABISSMAL/issues) of the main ABISSMAL repository, then follow the instructions in the Issue template to add the information needed for the code developers to address the bug effectively. You can also add the tag "bug" to your issue. If you encounter an issue with the ABISSMAL vignettes, then you can submit an issue through the main ABISSMAL GitHub repository as well, as long as you clarify that the bug is related to code in a given vignette. +En el siguiente tutorial, vamos a crear datos simulados de movimiento de animales para aprender como usar las cinco funciones primarias de ABISSMAL. -In the next vignette, we will create simulated datasets of raw movement data to learn how to use the different ABISSMAL functions described above. \ No newline at end of file +

Informacion sobre esta traduccion

+ +Este tutorial fue traducido al espanol por Grace Smith-Vidaurre, siguiendo las [convenciones de traduccion de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el genero femenino por defecto. Si encuentran errores de ortografia que impiden su habilidad de completar estos tutoriales, por favor reporten los errores de ortografia a GitHub usando los pasos arriba para reportar un "Issue" con los tutoriales. \ No newline at end of file From a96ebd7bc7b666aa6aaa99bb85b6b7ea1fa14160 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Wed, 27 Mar 2024 11:59:58 -0400 Subject: [PATCH 35/69] [PCT-472]: Finished spelling and grammar check in Spanish for first vignette --- R/vignettes/Vignette_01_Introduccion.Rmd | 82 ++++++++++++------------ 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/R/vignettes/Vignette_01_Introduccion.Rmd b/R/vignettes/Vignette_01_Introduccion.Rmd index 2e550ae..2636f64 100644 --- a/R/vignettes/Vignette_01_Introduccion.Rmd +++ b/R/vignettes/Vignette_01_Introduccion.Rmd @@ -12,111 +12,111 @@ output:

Resumen del tutorial y objetivos de aprendizaje

-Antes de empezar este tutorial, si puedes completar esta encuesta a través de Google Forms seria una gran ayuda. La información que compartes nos ayudara saber mas de tu experiencia en R y analizando datos biologicos antes de empezar los tutoriales mismos, tanto como las habilidades que buscas mejorar a traves de completar esta coleccion de tutoriales, y tambien nos ayudara mejorar estos tutoriales en el futuro. Puedes encontrar el enlace para la encuesta en el archivo README de los tutoriales. +Antes de empezar este tutorial, si puedes completar esta encuesta a través de Google Forms seria una gran ayuda. La información que compartes nos ayudara saber mas de tu experiencia en R y analizando datos biológicos antes de empezar los tutoriales mismos, tanto como las habilidades que buscas mejorar a través de completar esta colección de tutoriales, y también nos ayudara mejorar estos tutoriales en el futuro. Puedes encontrar el enlace para la encuesta en el archivo README de los tutoriales. -En este primer tutorial, vas a leer sobre el programa RStudio, aprender como crear una version local de el repositorio de ABISSMAL en GitHub, y aprender mas sobre el flujo de analisis de datos en ABISSMAL. En este tutorial vas a aprender como: +En este primer tutorial, vas a leer sobre el programa RStudio, aprender como crear una versión local de el repositorio de ABISSMAL en GitHub, y aprender mas sobre el flujo de análisis de datos en ABISSMAL. En este tutorial vas a aprender como: -1. Configurar tu sesion de RStudio -2. Crear una version local de un repositorio de GitHub +1. Configurar tu sesión de RStudio +2. Crear una versión local de un repositorio de GitHub 3. Usar ABISSMAL para procesar y analizar datos -4. Solucionar problemas con tu codigo usando recursos en linea -5. Reportar problemas de codigo a GitHub +4. Solucionar problemas con tu código usando recursos en linea +5. Reportar problemas de código a GitHub -Hay muchas formas de hacer una sola tarea o solucionar un problema en R, y deberias de mantener esto en mente mientras completas estos tutoriales. En cada tutorial, vas a aprender diferentes ejemplos sobre como manejar ciertas tares o solucionar problemas especificas con codigo, pero estos ejemplos no son un resumen exhaustivo de como manejar cada tarea ni solucionar cada problema. Mas bien recomiendo que uses estos tutoriales como una oportunidad para practicar como usar tus habilidades de escribir codigo en un contexto biologico. +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 como manejar ciertas tares o solucionar problemas especificas con código, pero estos ejemplos no son un resumen exhaustivo de como manejar cada tarea ni solucionar cada problema. Mas bien recomiendo que uses estos tutoriales como una oportunidad para practicar como usar tus habilidades de escribir código en un contexto biológico. -

Configurar tu sesion de RStudio

+

Configurar tu sesión de RStudio

-Si nunca has usado R, un lenguaje de programacion para analisis estadisticos, ni RStudio, una interfaz grafica de usuario, recomiendo revisar el tutorial [R for Reproducible Scientific Analysis](https://swcarpentry.github.io/r-novice-gapminder-es/) de [Software Carpentry](https://software-carpentry.org/). +Si nunca has usado R, un lenguaje de programación para análisis estadísticos, ni RStudio, una interfaz gráfica de usuario, recomiendo revisar el tutorial [R for Reproducible Scientific Analysis](https://swcarpentry.github.io/r-novice-gapminder-es/) de [Software Carpentry](https://software-carpentry.org/). -Cuando hayas instalado R y RStudio en tu computadora, puedes hacer click en el iconon de RStudio para abrir el programa. La configuracion predeterminada de los paneles de RStudio se deberia de ver algo asi, aunque el color de fondo puede ser diferente: +Cuando hayas instalado R y RStudio en tu computadora, puedes hacer clic en el icono 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:
![Un imagen de la configuracion predeterminada de los paneles de RStudio, con el panel de la consola debajo del panel de la fuente](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/RStudio_01.png) -Con esta configuracion de paneles es dificil ver el codigo que escribes y guardas en archivos fisicos en el panel de la fuente, tanto como el resultado de ese codigo en el panel de la consola. Puedes reconfigurar los paneles para que el panel de la fuente este al lado izquierdo del panel de la consola, y asi sera mas facil de ver los resultados de tu codigo inmediatamente. Puedes seguir los siguientes pasos para reconfigurar los paneles en RStudio: +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 este al lado izquierdo del panel de la consola, y así sera mas fácil de ver los resultados de tu código inmediatamente. Puedes seguir los siguientes pasos para reconfigurar los paneles en RStudio: -* Seleciona la opcion "Tools" en el menu +* Selecciona la opción "Tools" en el menú -* Seleciona la opcion "Global Options" en el menu desplegable que sale en "Tools" +* Selecciona la opción "Global Options" en el menú desplegable que sale en "Tools" -* Se va a abrir otra ventana con opciones. Selecciona la opcion "Pane Layout" a la izquierda +* Se va a abrir otra ventana con opciones. Selecciona la opción "Pane Layout" a la izquierda -* Usa el menu desplegable para seleccionar "Source" como el panel en la primera fila y al lado izquierdo, y seleciona "Console" como el panel que ocupara la siguiente parte de la primera fila de paneles a mano derecha +* 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 ocupara la siguiente parte de la primera fila de paneles a mano derecha * Puedes seleccionar "Apply" para aplicar estos cambios y luego "Ok" para salir de esta ventana -Tu configuracion de paneles en RStudio ahora se deberia de ver asi: +Tu configuración de paneles en RStudio ahora se debería de ver así:
![Un imagen de la configuracion modificada de los paneles de RStudio, con el panel de la consola al lado derecho y el panel de la fuente al lado izquierdo](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/RStudio_02.png) -Otro cambio que puedes implementar en tu configuracion de RStudio es el retorno automatico, para no tener que hacer scroll a tu derecha cada vez que quieres leer una linea completa de texto o codigo. Para implementar este cambio, puedes ir a "Tools", luego "Global Options", seleccionar "Code", y seleccionar la caja para la opcion "Soft-wrap R source files", luego hacer click en "Apply" y "Ok". +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 linea 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". -Tambien puedes cambiar el tamano de la fuente del texto y codigo que escribes, tanto como el color de texto y codigo, y el color de fondo de tu ventana de RStudio. Despues de selecionar "Tools" y "Global Options", puedes seleccionar "Appearance" para ver diferentes opciones. +También puedes cambiar el tamaño de la fuente del texto y código que escribes, tanto como el color de texto y código, y el color de fondo de tu ventana de RStudio. Después de seleccionar "Tools" y "Global Options", puedes seleccionar "Appearance" para ver diferentes opciones. -

Crear una version local de un repositorio de GitHub

+

Crear una versión local de un repositorio de GitHub

-Si nunca has usado GitHub, recomiendo bajar el programa [GitHub Desktop](https://docs.github.com/es/desktop). Este programa es una interfaz grafica de usuario para el plataforma de GitHub, que provee un sistema de control de versiones. Vamos a seguir unos pasos para crear una version local del repositorio de ABISSMAL que esta en GitHub para que puedas acceder las funciones de R de ABISSMAL en tu propia computadora. Si ya sabes como usar GitHub y como usar Git en el terminal, puedes seguir las instrucciones que tenemos en el [README de ABISSMAL](https://github.com/lastralab/ABISSMAL). +Si nunca has usado GitHub, recomiendo bajar el programa [GitHub Desktop](https://docs.github.com/es/desktop). Este programa es una interfaz gráfica de usuario para 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 esta en GitHub para que puedas acceder las funciones de R de ABISSMAL en tu propia computadora. Si ya sabes como usar GitHub y como usar Git en el terminal, puedes seguir las instrucciones que tenemos en el [README de ABISSMAL](https://github.com/lastralab/ABISSMAL). Cuando hayas instalado GitHub Desktop, puedes: -* Hacer click en el icono de GitHub Desktop para abrir el programa +* Hacer clic en el icono 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 click en el boton verde de "Code" y copiar la URL debajo de la opcion HTTPS en el menu desplegable +* 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 menu de GitHub Desktop window y seleccionar "File" +* Ir al menú de GitHub Desktop y seleccionar "File" * Seleccionar "Clone repository" -* Seleccionar la pestana "URL" +* Seleccionar la pestaña "URL" * Pegar la URL de ABISSMAL que copiaste de GitHub en la caja que pide "Repository URL or GitHub username and repository" -* Revisar que el directorio en la caja "Local path" es la ubicacion 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 instalara directamente en tu Desktop +* 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 instalara directamente en tu Desktop -* Seleccionar "Clone" cuando estes lista para crear una version local del repositorio de ABISSMAL en tu computadora +* Seleccionar "Clone" cuando estés lista para crear una versión local del repositorio de ABISSMAL en tu computadora -Cuando el repositorio se ha instalado en tu computadora, deberias de poder ver el siguiente directorio y lista de archvios adentro de una carpeta llamada "ABISSMAL": +Cuando el repositorio se ha instalado en tu computadora, deberías de poder ver el siguiente directorio y lista de archivos adentro de una carpeta llamada "ABISSMAL":
![Un imagen del directorio de la version local del repositorio de ABISSMAL](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/ABISSMAL_localrepo.png) -Los archivos que vamos a usar en los siguientes tutoriales estan adentro de la carpeta "R". Esta carpeta contiene 6 archivos de R (extension ".R"), un archivo README que contiene mas informacion sobre cada archivo de R, y carpetas con los tutoriales en formato RMarkdown (extension ".Rmd"), y tambien codigo para pruebas unitarias automatizadas. +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 mas información sobre cada archivo de R, y carpetas con los tutoriales en formato RMarkdown (extensión ".Rmd"), y también código para pruebas unitarias automatizadas.

Procesar y analizar datos con ABISSMAL

-ABISSMAL provee 5 funciones diferentes de R para procesar y analizar datos, y describimos estas funciones en mas detalle abajo, en el orden que se deberian de usar in general. Puedes encontrar mas informacion detallada sobre estas funciones en el archivo README de la carpeta "R", y tambien el manuscrito asociado con ABISSMAL: +ABISSMAL provee 5 funciones diferentes de R para procesar y analizar datos, y describimos estas funciones en mas detalle abajo, en el orden que se deberían de usar in general. Puedes encontrar mas información detallada sobre estas funciones en el archivo README de la carpeta "R", y también el manuscrito asociado con ABISSMAL: -1. `combine_raw_data` automaticamente combina hojas de calculo de datos originales que fueron colectados por dia y los guarda en una sola hoja de calculo a traves de todos los dias de coleccion de datos. Esta concatenacion de datos se realiza por cada tipo de sensor, por ejemplo, los sensores de rayos infrarrojos o una camara de infrarrojo, y no cambia ni reemplaza los datos originales. Esta funcion puede usar datos de los sensors de RFID (radio frequency identification), rayos infrarrojo, la camara, o el sensor de temperatura de ABISSMAL. Las siguientes funciones no aceptan datos de temperatura +1. `combine_raw_data` automáticamente combina hojas de calculo de datos originales que fueron colectados por día y los guarda en una sola hoja de calculo 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 infrarrojos 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"), rayos infrarrojo, la cámara, o el sensor de temperatura de ABISSMAL. Las siguientes funciones no aceptan datos de temperatura -2. `detect_perching_events` toma como entrada los resultados de `combine_raw_data` y detecta grupos de detecciones que occurrieron cerca en el tiempo como eventos de posar. Estos eventos de posa representan periodos de tiempo cuando un individuo estuvo posado en el antena de RFID (como percha) situado en la entrada del contenedor de nido. La function usa datos de los sensors ubicados en la entrada del contenedor (RFID o los sensors de infrarrojo) y devuelve una hoja de calculo de las coordenadas temporales de los eventos de posar inferidos +2. `detect_perching_events` toma como entrada los resultados de `combine_raw_data` y detecta grupos de detecciones que ocurrieron cerca en el tiempo como eventos de posar. Estos eventos de posa representan periodos de tiempo cuando un individuo estuvo posado en el 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 calculo de las coordenadas temporales de los eventos de posar inferidos -3. `preprocess_detections` usa como entrada los resultados de `combine_raw_data` y remueve detecciones que occurrieron muy cerca en el tiempo. Esta funcion devuelve una hoja de calculo de datos pre-procesados por tipo de sensor, y en cada hoja de calculo, las detecciones consecutivas deberian de estar separadas por un umbral temporal predeterminado o mas. Por ejemplo, cuando usas un umbral de 1 segundo, solo una deteccion puede occurir por segundo en los datos pre-procesados +3. `preprocess_detections` usa como entrada los resultados de `combine_raw_data` y remueve detecciones que ocurrieron muy cerca en el tiempo. Esta función devuelve una hoja de calculo de datos pre-procesados por tipo de sensor, y en cada hoja de calculo, las detecciones consecutivas deberían de estar separadas por un umbral temporal predeterminado o mas. Por ejemplo, cuando usas un umbral de 1 segundo, solo una detección puede ocurrir por segundo en los datos pre-procesados -4. `detect_clusters` usa como entrada los resultados de `preprocess_detections` obtenidos con 2 o mas tipos de sensores. La funcion identifica detecciones a traves de 2 o mas tipos de sensores que occurieron cerca en el tiempo, y devuelve informacion temporal y metadatos sobre cada grupo o cumulo. Cada cumulo de detecciones representa un evento discreto de movimiento de un individuo o mas que un individuo +4. `detect_clusters` usa como entrada los resultados de `preprocess_detections` obtenidos con 2 o mas tipos de sensores. La función identifica detecciones a través de 2 o mas tipos de sensores que ocurrieron cerca en el tiempo, y devuelve información temporal y metadatos sobre cada grupo o cumulo. Cada cumulo de detecciones representa un evento discreto de movimiento de un individuo o mas que un individuo -5. `score_clusters` usa como entrada los resultados de `detect_clusters`. Esta funcion tambien puede usar los resultados de `detect_perching_events` para integrar eventos de posar identificados en los datos originales de RFID o los sensores de infrarrojo, y puede usar los resultados de `preprocess_detections` para integrar eventos de grabacion de videos que no fueron incluidos en los cumulos detectados por `detect_clusters`. Esta funcion hace inferencias de comportamiento de los eventos de movimento, incluyendo la direccion de movimiento, el magnitud de movimiento, identidad de individuo (cuando datos de RFID se encontraron en un cumulo), y donde occurrio el inicio de la secuecia de movimiento (en la entrada o adentro del contenedor de nido). La funcion devuelve una hoja de calculo de inferencias de comportamientos y otros metadatos sobre cada evento de movimiento, y estos resultados se pueden usar para visualizaciones y analisis estadisticos. +5. `score_clusters` usa como entrada los resultados de `detect_clusters`. Esta función también puede usar los resultados de `detect_perching_events` para integrar eventos de posar identificados en los datos originales de RFID o los sensores de infrarrojo, y puede usar los resultados de `preprocess_detections` para integrar eventos de grabación de vídeos que no fueron incluidos en los cúmulos detectados por `detect_clusters`. Esta función hace inferencias de comportamiento de los eventos de movimiento, incluyendo la dirección de movimiento, el magnitud de movimiento, identidad de individuo (cuando datos de RFID se encontraron en un cumulo), y donde ocurrir el inicio de la secuencia de movimiento (en la entrada o adentro del contenedor de nido). La función devuelve una hoja de calculo de inferencias de comportamientos y otros metadatos sobre cada evento de movimiento, y estos resultados se pueden usar para visualizaciones y análisis estadísticos.
![Figure 4 del manuscrito de ABISSMAL con un flujo general de trabajo a traves de las 5 funciones primarias](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/Figure4_ComputationalAnalyses.png) * Esta figura es de Smith-Vidaurre, G., Molina, T., Jarvis, E.D., and E.A. Hobson. 2023. Automated tracking of avian parental care. [EcoEvoRxiv preprint]((https://ecoevorxiv.org/repository/view/6268/)). -

Solucionar errors en linea

+

Solucionar errores en linea

-Mientras escribes y ejecutas codigo encontraras errores que a veces pueden ser frustrantes. Experimentar y solucionar errors es una parte muy importante de tu proceso de aprendizaje en R (u otros lenguajes de programacion). Los errores surgen por diferentes razones, incluyendo errores de tipeo mientras escribes codigo, 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 errors a veces pueden surgir por problemas con las funciones de ABISSMAL que usaras en los siguientes tutoriales, or por errores con el codigo en los tutoriales mismos. +Mientras escribes y ejecutas código encontraras errores que a veces pueden ser frustrantes. Experimentar y solucionar errores 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 tipeo 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 usaras en los siguientes tutoriales, o por errores con el código en los tutoriales mismos. -Cuando experimentas un error con tu condigo, es importante intentar solucionar el error independientement antes de suponer que el error nacio de errores de las funciones de ABISSMAL o los tutoriales. Hay muchos recursos en linea 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 asi deberias de ver varias opciones de foros publicos donde otras personas han preguntado por y solucionado errores similares. Tambien deberias poder de usar herramientas de IA generativo como ChatGPT para buscar errores de tipeo u otros problemas con tu codigo. Otra opcion util es leer la documentation de R para investigar si el error que ves esta relacionado con un paquete o funcion que puede ser una dependencia de ABISSMAL. +Cuando experimentas un error con tu condigo, 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 linea 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 donde 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 tipeo 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 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 tipeo 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 seccion). +Ya cuando hayas investigado cuidadosamente un error, y estas segura que el error no se debe a un error de tipeo o problemas con la estructura de tus datos, luego puedes considerar si el error se debe a un problema con las funciones de ABISSMAL o los tutoriales mismos, y puedes reportar el error a las desarolladoras de ABISSMAL en GitHub (ver la siguiente sección).

Reportar errores en GitHub

-Es posible que encuentres errores mientras trabajas en cada tutorial, incluyendo errores con codigo que no ejecuta, y que devuelve resultados incorrectos. Estos errores pueden manifestar de las funciones de ABISSMAL o con el codigo de algun tutorial. Cuando encuentras un error en una funcion de ABISSMAL, puedes crear un "Issue" (asunto o problema) en el repositorio de GitHub. Para crear un asunto nuevo, puedes seleccionar "New Issue" en la pagina de [Issues](https://github.com/lastralab/ABISSMAL/issues) en el repositorio de ABISSMAL, y seguir las instrucciones en el esquema del "Issue" nuevo para anadir la informacion que las desarrolladoras necesitan para trabajar efectivamente en el error. Tambien puedes anadir 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 deberias de clarificar que el error esta relacionado al codigo de algun tutorial en particular. +Es posible que encuentres errores mientras trabajas en cada tutorial, incluyendo errores con código que no ejecuta, y que devuelve resultados incorrectos. Estos errores pueden manifestar de las funciones de ABISSMAL o con el código de algún tutorial. Cuando encuentras un error en una función de ABISSMAL, puedes crear un "Issue" (asunto o problema) en el repositorio de GitHub. Para crear un asunto nuevo, puedes seleccionar "New Issue" en la pagina de [Issues](https://github.com/lastralab/ABISSMAL/issues) en el repositorio de ABISSMAL, y seguir las instrucciones en el esquema del "Issue" nuevo para añadir la información que las desarrolladoras necesitan para trabajar efectivamente en el error. También puedes añadir la etiqueta de "bug" (o error) a tu "Issue". Si encuentras un error con los tutoriales puedes crear un "Issue" usando los mismos pasos, y deberías de clarificar que el error esta 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 como usar las cinco funciones primarias de ABISSMAL. -

Informacion sobre esta traduccion

+

Información sobre esta traducción

-Este tutorial fue traducido al espanol por Grace Smith-Vidaurre, siguiendo las [convenciones de traduccion de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el genero femenino por defecto. Si encuentran errores de ortografia que impiden su habilidad de completar estos tutoriales, por favor reporten los errores de ortografia a GitHub usando los pasos arriba para reportar un "Issue" con los tutoriales. \ No newline at end of file +Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las [convenciones de traduccion de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el genero femenino por defecto. Si encuentran errores de ortografía que impiden su habilidad de completar estos tutoriales, por favor reporten los errores de ortografía a GitHub usando los pasos arriba para reportar un "Issue" con los tutoriales. \ No newline at end of file From 66617c99d4fe255d5e2f441547697441564b9989 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Wed, 27 Mar 2024 15:09:54 -0400 Subject: [PATCH 36/69] [PCT-472]: Fixing accents Just realized that the RStudio spellcheck in Spanish is not catching all of the words that need accents --- R/vignettes/Vignette_01_Introduccion.Rmd | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/R/vignettes/Vignette_01_Introduccion.Rmd b/R/vignettes/Vignette_01_Introduccion.Rmd index 2636f64..b6391c4 100644 --- a/R/vignettes/Vignette_01_Introduccion.Rmd +++ b/R/vignettes/Vignette_01_Introduccion.Rmd @@ -12,9 +12,9 @@ output:

Resumen del tutorial y objetivos de aprendizaje

-Antes de empezar este tutorial, si puedes completar esta encuesta a través de Google Forms seria una gran ayuda. La información que compartes nos ayudara saber mas de tu experiencia en R y analizando datos biológicos antes de empezar los tutoriales mismos, tanto como las habilidades que buscas mejorar a través de completar esta colección de tutoriales, y también nos ayudara mejorar estos tutoriales en el futuro. Puedes encontrar el enlace para la encuesta en el archivo README de los tutoriales. +Sería una gran ayuda si antes de empezar este tutorial puedes completar una encuesta a través de Google Forms. La información que compartes nos ayudará saber más de tu experiencia en R y analizando datos biológicos antes de empezar los tutoriales mismos, tanto como las habilidades que buscas mejorar a través de completar esta colección de tutoriales, y también nos ayudara mejorar estos tutoriales en el futuro. Puedes encontrar el enlace para la encuesta en el archivo README de los tutoriales. -En este primer tutorial, vas a leer sobre el programa RStudio, aprender como crear una versión local de el repositorio de ABISSMAL en GitHub, y aprender mas sobre el flujo de análisis de datos en ABISSMAL. En este tutorial vas a aprender como: +En este primer tutorial, vas a leer sobre el programa RStudio, aprender como crear una versión local de el repositorio de ABISSMAL en GitHub, y aprender más sobre el flujo de análisis de datos en ABISSMAL. En este tutorial vas a aprender como: 1. Configurar tu sesión de RStudio 2. Crear una versión local de un repositorio de GitHub @@ -22,7 +22,7 @@ En este primer tutorial, vas a leer sobre el programa RStudio, aprender como cre 4. Solucionar problemas con tu código usando recursos en linea 5. Reportar problemas de código a GitHub -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 como manejar ciertas tares o solucionar problemas especificas con código, pero estos ejemplos no son un resumen exhaustivo de como manejar cada tarea ni solucionar cada problema. Mas bien recomiendo que uses estos tutoriales como una oportunidad para practicar como usar tus habilidades de escribir código en un contexto biológico. +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 como manejar ciertas tares o solucionar problemas especificas con código, pero estos ejemplos no son un resumen exhaustivo de como manejar cada tarea ni solucionar cada problema. Más bien recomiendo que uses estos tutoriales como una oportunidad para practicar como usar tus habilidades de escribir código en un contexto biológico.

Configurar tu sesión de RStudio

@@ -32,7 +32,7 @@ Cuando hayas instalado R y RStudio en tu computadora, puedes hacer clic en el ic
![Un imagen de la configuracion predeterminada de los paneles de RStudio, con el panel de la consola debajo del panel de la fuente](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/RStudio_01.png) -Con esta configuración de paneles 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 este al lado izquierdo del panel de la consola, y así sera mas fácil de ver los resultados de tu código inmediatamente. Puedes seguir los siguientes pasos para reconfigurar los paneles en RStudio: +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 este al lado izquierdo del panel de la consola, y así sera 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ú @@ -83,21 +83,21 @@ Cuando el repositorio se ha instalado en tu computadora, deberías de poder ver
![Un imagen del directorio de la version local del repositorio de ABISSMAL](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/ABISSMAL_localrepo.png) -Los archivos que vamos a usar en los siguientes tutoriales están adentro de la carpeta "R". Esta carpeta contiene 6 archivos de R (extensión ".R"), un archivo README que contiene mas 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. +Los archivos que vamos a usar en los siguientes tutoriales están adentro de la carpeta "R". Esta carpeta contiene 6 archivos de R (extensión ".R"), un archivo README que contiene más información sobre cada archivo de R, y carpetas con los tutoriales en formato RMarkdown (extensión ".Rmd"), y también código para pruebas unitarias automatizadas.

Procesar y analizar datos con ABISSMAL

-ABISSMAL provee 5 funciones diferentes de R para procesar y analizar datos, y describimos estas funciones en mas detalle abajo, en el orden que se deberían de usar in general. Puedes encontrar mas información detallada sobre estas funciones en el archivo README de la carpeta "R", y también el manuscrito asociado con ABISSMAL: +ABISSMAL provee 5 funciones diferentes de R para procesar y analizar datos, y describimos estas funciones en más detalle abajo, en el orden que se deberían de usar in general. Puedes encontrar más información detallada sobre estas funciones en el archivo README de la carpeta "R", y también el manuscrito asociado con ABISSMAL: -1. `combine_raw_data` automáticamente combina hojas de calculo de datos originales que fueron colectados por día y los guarda en una sola hoja de calculo 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 infrarrojos 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"), rayos infrarrojo, la cámara, o el sensor de temperatura de ABISSMAL. Las siguientes funciones no aceptan datos de temperatura +1. `combine_raw_data` automáticamente combina hojas de cálculo de 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 infrarrojos 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"), rayos infrarrojo, la cámara, o el sensor de temperatura de ABISSMAL. Las siguientes funciones no aceptan datos de temperatura -2. `detect_perching_events` toma como entrada los resultados de `combine_raw_data` y detecta grupos de detecciones que ocurrieron cerca en el tiempo como eventos de posar. Estos eventos de posa representan periodos de tiempo cuando un individuo estuvo posado en el 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 calculo de las coordenadas temporales de los eventos de posar inferidos +2. `detect_perching_events` toma como entrada los resultados de `combine_raw_data` y detecta grupos de detecciones que ocurrieron cerca en el tiempo como eventos de posar. Estos eventos de posa representan periodos de tiempo cuando un individuo estuvo posado en el antena de RFID (como percha) situado en la entrada del contenedor de nido. La función usa datos de los sensores ubicados en la entrada del contenedor (RFID o los sensores de infrarrojo) y devuelve una hoja de cálculo de las coordenadas temporales de los eventos de posar inferidos -3. `preprocess_detections` usa como entrada los resultados de `combine_raw_data` y remueve detecciones que ocurrieron muy cerca en el tiempo. Esta función devuelve una hoja de calculo de datos pre-procesados por tipo de sensor, y en cada hoja de calculo, las detecciones consecutivas deberían de estar separadas por un umbral temporal predeterminado o mas. Por ejemplo, cuando usas un umbral de 1 segundo, solo una detección puede ocurrir por segundo en los datos pre-procesados +3. `preprocess_detections` usa como entrada los resultados de `combine_raw_data` y remueve detecciones que ocurrieron muy cerca en el tiempo. Esta función devuelve una hoja de cálculo de datos pre-procesados por tipo de sensor, y en cada hoja de cálculo, las detecciones consecutivas deberían de estar separadas por un umbral temporal predeterminado o más. Por ejemplo, cuando usas un umbral de 1 segundo, solo una detección puede ocurrir por segundo en los datos pre-procesados -4. `detect_clusters` usa como entrada los resultados de `preprocess_detections` obtenidos con 2 o mas tipos de sensores. La función identifica detecciones a través de 2 o mas tipos de sensores que ocurrieron cerca en el tiempo, y devuelve información temporal y metadatos sobre cada grupo o cumulo. Cada cumulo de detecciones representa un evento discreto de movimiento de un individuo o mas que un individuo +4. `detect_clusters` usa como entrada los resultados de `preprocess_detections` obtenidos con 2 o más tipos de sensores. La función identifica detecciones a través de 2 o más tipos de sensores que ocurrieron cerca en el tiempo, y devuelve información temporal y metadatos sobre cada grupo o cúmulo. Cada cúmulo de detecciones representa un evento discreto de movimiento de un individuo o más que un individuo -5. `score_clusters` usa como entrada los resultados de `detect_clusters`. Esta función también puede usar los resultados de `detect_perching_events` para integrar eventos de posar identificados en los datos originales de RFID o los sensores de infrarrojo, y puede usar los resultados de `preprocess_detections` para integrar eventos de grabación de vídeos que no fueron incluidos en los cúmulos detectados por `detect_clusters`. Esta función hace inferencias de comportamiento de los eventos de movimiento, incluyendo la dirección de movimiento, el magnitud de movimiento, identidad de individuo (cuando datos de RFID se encontraron en un cumulo), y donde ocurrir el inicio de la secuencia de movimiento (en la entrada o adentro del contenedor de nido). La función devuelve una hoja de calculo de inferencias de comportamientos y otros metadatos sobre cada evento de movimiento, y estos resultados se pueden usar para visualizaciones y análisis estadísticos. +5. `score_clusters` usa como entrada los resultados de `detect_clusters`. Esta función también puede usar los resultados de `detect_perching_events` para integrar eventos de posar identificados en los datos originales de RFID o los sensores de infrarrojo, y puede usar los resultados de `preprocess_detections` para integrar eventos de grabación de vídeos que no fueron incluidos en los cúmulos detectados por `detect_clusters`. Esta función hace inferencias de comportamiento de los eventos de movimiento, incluyendo la dirección de movimiento, el magnitud de movimiento, identidad de individuo (cuando datos de RFID se encontraron en un cúmulo), y donde ocurrir 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 comportamientos y otros metadatos sobre cada evento de movimiento, y estos resultados se pueden usar para visualizaciones y análisis estadísticos.
![Figure 4 del manuscrito de ABISSMAL con un flujo general de trabajo a traves de las 5 funciones primarias](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/Figure4_ComputationalAnalyses.png) @@ -119,4 +119,4 @@ En el siguiente tutorial, vamos a crear datos simulados de movimiento de animale

Información sobre esta traducción

-Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las [convenciones de traduccion de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el genero femenino por defecto. Si encuentran errores de ortografía que impiden su habilidad de completar estos tutoriales, por favor reporten los errores de ortografía a GitHub usando los pasos arriba para reportar un "Issue" con los tutoriales. \ No newline at end of file +Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las [convenciones de traduccion de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar estos tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos arriba para reportar un "Issue". \ No newline at end of file From f083d4f58164a7907301a38669bff6098590bd68 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Wed, 27 Mar 2024 17:19:26 -0400 Subject: [PATCH 37/69] Translating second vignette A little over halfway through the rough translation --- R/vignettes/Vignette_01_Introduccion.Rmd | 10 +- R/vignettes/Vignette_02_Configuracion.Rmd | 192 ++++++++++++++++++++++ 2 files changed, 197 insertions(+), 5 deletions(-) create mode 100644 R/vignettes/Vignette_02_Configuracion.Rmd diff --git a/R/vignettes/Vignette_01_Introduccion.Rmd b/R/vignettes/Vignette_01_Introduccion.Rmd index b6391c4..084908a 100644 --- a/R/vignettes/Vignette_01_Introduccion.Rmd +++ b/R/vignettes/Vignette_01_Introduccion.Rmd @@ -10,6 +10,10 @@ output: toc_depth: 4 --- +

Información sobre esta traducción

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

Resumen del tutorial y objetivos de aprendizaje

Sería una gran ayuda si antes de empezar este tutorial puedes completar una encuesta a través de Google Forms. La información que compartes nos ayudará saber más de tu experiencia en R y analizando datos biológicos antes de empezar los tutoriales mismos, tanto como las habilidades que buscas mejorar a través de completar esta colección de tutoriales, y también nos ayudara mejorar estos tutoriales en el futuro. Puedes encontrar el enlace para la encuesta en el archivo README de los tutoriales. @@ -115,8 +119,4 @@ Ya cuando hayas investigado cuidadosamente un error, y estas segura que el error Es posible que encuentres errores mientras trabajas en cada tutorial, incluyendo errores con código que no ejecuta, y que devuelve resultados incorrectos. Estos errores pueden manifestar de las funciones de ABISSMAL o con el código de algún tutorial. Cuando encuentras un error en una función de ABISSMAL, puedes crear un "Issue" (asunto o problema) en el repositorio de GitHub. Para crear un asunto nuevo, puedes seleccionar "New Issue" en la pagina de [Issues](https://github.com/lastralab/ABISSMAL/issues) en el repositorio de ABISSMAL, y seguir las instrucciones en el esquema del "Issue" nuevo para añadir la información que las desarrolladoras necesitan para trabajar efectivamente en el error. También puedes añadir la etiqueta de "bug" (o error) a tu "Issue". Si encuentras un error con los tutoriales puedes crear un "Issue" usando los mismos pasos, y deberías de clarificar que el error esta 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 como usar las cinco funciones primarias de ABISSMAL. - -

Información sobre esta traducción

- -Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las [convenciones de traduccion de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar estos tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos arriba para reportar un "Issue". \ No newline at end of file +En el siguiente tutorial, vamos a crear datos simulados de movimiento de animales para aprender como usar las cinco funciones primarias de ABISSMAL. \ No newline at end of file diff --git a/R/vignettes/Vignette_02_Configuracion.Rmd b/R/vignettes/Vignette_02_Configuracion.Rmd new file mode 100644 index 0000000..ddffc78 --- /dev/null +++ b/R/vignettes/Vignette_02_Configuracion.Rmd @@ -0,0 +1,192 @@ +--- +title: "Vignette 02: Configuración" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = FALSE) + +``` + +

Información sobre esta traducción

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

Resumen del tutorial y objetivos de aprendizaje

+ +Vamos a configurar tu espacio de trabajo virtual para sesiones de escribir codigo en R y usar las funciones de ABISSMAL en este segundo tutorial. Vas a aprender habilidades basicas de programacion en R y buenas costumbres de la ciencia abierta para escribir codigo, incluyendo como: + +1. Usar archivos de RMarkdown +2. Limpiar tu ambiente global +3. Ejecutar codigo adentro de un "trozo" de codigo de RMarkdown +4. Aprender sobre funciones en R y su documentacion +5. Instalar y acceder paquetes +6. Comentar tu codigo +7. Atajos de RStudio para escribir codigo +8. Crear y usar una carpeta o un directorio de trabajo + +

Usar archivos de RMarkdown

+ +Cada tutorial en esta serie de tutoriales esta disponsible como un archivo de RMarkdown (extension .Rmd) y tambien 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 codigo junto con los resultados del codigo en un reporte en formato HTML. Puedes leer [la documentación de RMarkdown](https://rmarkdown.rstudio.com/lesson-1.html) o este tutorial por [Teresa Boca en RPubs](https://rpubs.com/tereboca/informe_rmakrdown) para aprender mas sobre como usar RMarkdown para escribo codigo y generar reportes. + +Los archivos de RMarkdown facilitan el proceso de compartir tu codigo y resultados. Si nunca has usado RMarkdown, la mejor forma de completar los tutoriales sera crear un archivo de RMarkdown nuevo para cada tutorial y escribir el codigo por ti mismo. Aprenderas mas si escribes el codigo y los comentarios (adentro y afuera de cada trozo de codigo) en tus propias palabras. Tambien puedes abrir el reporte HTML de cada tutorial en tu navegador para tener una guia mientras escribes el codigo 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 anades una tercera columna a la configuracion de tus paneles de RStudio. Puedes seleccionar las siguientes opciones: + +* "Tools" +* "Global Options" +* "Pane Layout" +* "Add Column" para anadir otro panel de fuente en una tercera columna +* Luego puedes abrir el tutorial original en un panel de fuente, y tu propio archivo de RMarkdown en el otro panel de fuente + +Si ya tienes experiencia con escribir codigo en R y usar archivos de RMarkdown, puedes abrir el archivo original de RMarkdown de un tutorial y ejecutar el codigo 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 codigo original en cada archivo de RMarkdown, puedes crear una copia de cada tutorial y modificar las copias mientras completas cada tutorial. + +

Limpiar tu ambiente global

+ +Tu ambiente global es tu espacio virtual en R, y puede contener paquetes y objectos diferentes que facilitaran tus objetivos de programacion y/o analizar datos. Puedes ver los paquetes y objetos que existen en tu ambiente con hacer clic en la pestana de ambiente o "Environment" en el mismo panel que incluye las pestanas de "History" y "Connections". + +Si iniciaste una sesion nueva de RStudio puede que tu ambiente global este vacio. Pero si estas trabajando en una sesion 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 costumbre para asegurar que cada vez que empiezas una sesion de escribir codigo. Si no limpias tu ambiente global, incluso cuando usas el mismo codigo entre sesiones, corres el riesgo de usar versiones viejas de objetos que no reflejan los cambios mas recientes en tu codigo. + +Puedes limpiar tu ambiente global directamente de la interfaz de RStudio con hacer clic en el icono de la escoba debajo de la pestana de "Environment" (haz clic en "Yes" con "hidden objects" para tambien limpiar objectos escondidos). + +Tambien puedes limpiar tu ambiente global con ejecutar el codigo como se detalla abajo. Puedes ejecutar el codigo adentro de este trozo de formas diferentes: + +* Hacer clic en el icono de la flecha verde en la parte superior derecha del trozo de codigo para ejecutar solamente el codigo en este trozo + +* Puedes ubicar tu cursor (y hacer clic) en cualquiera posicion adentro de la linea de codigo, y luego navegar al icono de "Run" en la parte superior derecha del panel de fuente que tiene un cuadro blanco y una flecha verde. En el menu desplegable, selecciona "Run Selected Lines" or "Run Current Chunk" (o usar los atajos de cada comando, ver los siguientes dos vinetas). "Run Selected Lines" ejecutara la linea de codigo en donde esta tu cursor, y "Run Current Chunk" ejecutara todo el codigo adentro del trozo (independientemente de la posicion de tu cursor) + +* Puedes ubicar tu cursor (y hacer clic) en cualquiera posicion adentro de la linea de codigo, y usar el atajo de "Ctrl" + "Enter" para ejecutar la linea de codigo corriente donde esta tu cursor + +* Para ejecutar todo el codigo en el trozo, puedes usar el atajo "Ctrl" + "Shift" + "Enter" + +El primer atajo arriba, para ejecutar una linea de codigo a la vez, es muy util para poder ver los resultados de cada linea de codigo y revisarlos por errores. +```{r} + +rm(list = ls()) + +``` + +El codigo arriba para limpiar tu ambente global es una expresion anidada con 2 funciones: `rm()` and `ls()`. La notacion de `()` se usa para funciones en R. Las funciones son operaciones que puedes aplicar en tu propio codigo con usar el nombre de una funcion especifica. R tiene una coleccion de funciones base que puedes acceder sin necesitar un paquete especifico, incluyendo las dos funciones arriba (`rm()` and `ls()`). + +

Acceder documentacion de funciones de R

+ +Puedes acceder la documentacion para las funciones que usaste arriba con hacer clic en la pestana de "Help" en el panel que incluye "Files" y "Plots", o con escribir el nombre de la funcion en la barra de busqueda. Tambien puedes acceder la documentacion de funciones con ejecutar este codigo: +```{r} + +?rm + +``` + +```{r} + +?ls + +``` + +La documentacion de cada funcion contiene secciones especificas que pueden ser utiles para entender el uso y proposito de la function, sobre todo los argumentos de la funcion. Estos argumentos son los valores que la funcion require de la usaria para poder guiar o modificar la operacion. Muchas funciones tienen valores por defecto para algunos de sus argumentos que se usaran cuando no provees valores especificos a los argumentos. + +Por ejemplo, la funcion `rm()` tiene un argumento `list()` (seguido por un `=` o signo igual, que se usa para proveer un valor especifico a un argumento). Necesitas proveer informacion despues 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 funcion `ls()`, que imprime los nombres de todos los objetos en tu ambiente global. + +

Instalar y cargar paquetes

+ +Despues de limpiar tu ambiente global, todavia necesitas configurar tu espacio virtual de trabajo para preparar para tu sesion de escribir codigo para analizar datos. Un paso important es asegurar que puedes acceder funciones que necesitas para analizar datos pero que no son disponibles a traves de la coleccion de funciones bases de R. Por ejemplo, [el `tidyverse`](https://www.tidyverse.org/) es una coleccion de paquetes de R que provee funciones y expresiones utiles para analizar datos. + +Si no has instalado el `tidyverse` en tu computadora local, necesitas instalar esta coleccion de paquetes para acceder funciones utiles para analizar datos. El codigo abajo instala el `tidyverse` de [CRAN](https://cran.r-project.org/), el "Comprehensive R Archive Network" en linea que contiene miles de paquetes de R. +```{r} + +# Instalar el tidyverse de CRAN +install.packages("tidyverse") + +``` + +En el trozo arriba, anadi un comentario arriba del codigo usando el simbolo de `#`, un signo numeral o hashtag. Cualquier texto que escribes despues de un hashtag sera ignorado cuando ejecutas tu codigo. Es buena costumbre comentar tu codigo, sobre todo cuando estes aprendiendo como escribir codigo en R. Para biologas, comentar tu codigo incluso cuando eres experto tambien puede ser muy buena costumbre. Comentar tu codigo es una forma de documentar to trabajo, y sirve para hacer el codigo que publicas con manuscritos o herramientas mas acesible 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 sesion de RStudio es cargar el paquete en tu ambiente global para poder acceder las funciones contenidos adentro de la coleccion de paquetes del `tidyverse`. +```{r} + +library(tidyverse) + +``` + +

Atajos para escribir codigo

+ +RStudio tiene varios atajo utiles para escribir codigo. Puedes encontrar estos atajos con ir a "Tools" en el menu de RStudio, luego seleccionar "Keyboard Shortcuts Help", que deberia de abrir una ventana nueva con todos los atajos por defecto en RStudio. Arriba aprendiste sobre unos atajos para ejecutar codigo adentro de un trozo de RStudio. Algunos atajos utiles son "Shift + Ctrl + C", que puedes usar para comentar o silenciar de una a multiple lineas de codigo a la vez (o sea, convertir codigo a comentarios), y "Ctrl + Alt + I", que automaticamente crea un trozo nuevo de RMarkdown. + +RStudio tambien contiene un atajo de autocompletar con el tabulador. Por ejemplo, en el trozo abajo, despues de escribir `libr` y hacer clic en el tabulador, deberias de poder ver una ventana pequena que demuestra todas las funciones, paquetes, u objetos disponibles que empiezan en el patron "libr". Puedes usar las teclas con flechas para seleccionar la opcion que quieres y hacer clic en "Enter" para completar la linea (por ejemplo, para escribir `library()` para cargar un paquete). +```{r eval = FALSE} + +libr + +``` + +TKTK continue translation + +

Troubleshooting incomplete statements

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

Create your working directory

+ +The next important step for setting up your virtual workspace is to decide on the working directory that you will use for the coding session. A directory is a location (a folder) on your computer where R will search for files to read in. When you write out files from R, those files can also be written to your working directory. + +You can use the function `getwd()` to check your current working directory. +```{r eval = TRUE} + +getwd() + +``` + +My working directory is set by default to the folder on my computer where I saved this RMarkdown file. To work in a separate directory that holds only data generated from these vignettes, you can create a new directory or folder on your computer: +```{r} + +?dir.create + +dir.create("/home/gsvidaurre/Desktop/ABISSMAL_vignettes") + +``` + +In the code above, you'll need to replace the path (or the list of folder names) to reflect where you want to save the new folder called `ABISSMAL_vignettes` (or another name of your choice) on your own computer. For those using Windows, if you copy and paste a path into R directly from your computer then you may need to change the direction of the slashes. + +In introductory R coding courses, it's common to formally set your working directory before a coding session by using the function `setwd()`. It's good practice to avoid using `setwd()` in code that that you want to share with collaborators or share more widely in the spirit of open science (e.g. accompanying a publication or a new tool). When you use `setwd()` and share the code with others, you're assuming that someone else has the same working directory on their computer, which generally will not be true. There are other ways in which you can specify your working directory throughout your code without using `setwd()`. + +For instance, let's say that we want to make a copy of the first vignette and then save that copy inside of the directory that we created above. We can use a base R functions to copy and save the file to the correct working directory. You will need to update the paths below to reflect the folder where the vignettes are saved on your computer, as well as your own working directory: +```{r} + +file.copy( + from = "/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/Vignette_01_Introduction.Rmd", + to = "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Vignette_01_Introduction_copy.Rmd" + ) + +``` + +In the code above, we're specifying 2 arguments to the function `file.copy()`. The first argument specifies the location of the file that we want to copy (/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/) and the name of the file (Vignette_01_Introduction.Rmd). The second argument specifies the location where we want to save this copy (/home/gsvidaurre/Desktop/ABISSMAL_vignettes/), and a new file name for the copied file (Vignette_01_Introduction_copy.Rmd). In each argument, the file paths are surrounded by 2 double quotes, in order to indicate that the text inside each pair of quotes should be considered character information or a "character string" (a formal term for text included in R code). + +When you run the line of code above, the output in the console should read "[1] TRUE" if the file was successfully copied and saved. You can check that the function worked correctly by opening a file window on your computer in this new directory. You can also use the base R function `list.files()` to check whether the copied file exists in the working directory. The output of `list.files()` is a list of all of the files contained within the new directory, and that list should contain a single file: "Vignette_01_Introduction_copy.Rmd". +```{r} + +list.files(path = "/home/gsvidaurre/Desktop/ABISSMAL_vignettes") + +``` + +The code above is an example of how you can specify your working directory in the code that you write without needing to use `setwd()`. In the following vignettes, you will see other examples of how to specify your working directory without `setwd()`. Before moving on to the next vignette, you can remove the file that you copied to your working directory: + +```{r} + +file.remove("/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Vignette_01_Introduction_copy.Rmd") + +``` + +In the next vignette, you will start creating and manipulating objects in R that can be used in the ABISSMAL data processing and analysis pipeline. \ No newline at end of file From 4d377e4a0839587da55d236504e7cdedc7e949cd Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Thu, 28 Mar 2024 11:16:44 -0400 Subject: [PATCH 38/69] Rough translation second vignette --- ...ccion.Rmd => Tutorial_01_Introduccion.Rmd} | 0 ...cion.Rmd => Tutorial_02_Configuracion.Rmd} | 43 +++++++++---------- 2 files changed, 20 insertions(+), 23 deletions(-) rename R/vignettes/{Vignette_01_Introduccion.Rmd => Tutorial_01_Introduccion.Rmd} (100%) rename R/vignettes/{Vignette_02_Configuracion.Rmd => Tutorial_02_Configuracion.Rmd} (63%) diff --git a/R/vignettes/Vignette_01_Introduccion.Rmd b/R/vignettes/Tutorial_01_Introduccion.Rmd similarity index 100% rename from R/vignettes/Vignette_01_Introduccion.Rmd rename to R/vignettes/Tutorial_01_Introduccion.Rmd diff --git a/R/vignettes/Vignette_02_Configuracion.Rmd b/R/vignettes/Tutorial_02_Configuracion.Rmd similarity index 63% rename from R/vignettes/Vignette_02_Configuracion.Rmd rename to R/vignettes/Tutorial_02_Configuracion.Rmd index ddffc78..53c257a 100644 --- a/R/vignettes/Vignette_02_Configuracion.Rmd +++ b/R/vignettes/Tutorial_02_Configuracion.Rmd @@ -1,5 +1,5 @@ --- -title: "Vignette 02: Configuración" +title: "Tutorial 02: Configuración" author: "Grace Smith-Vidaurre" date: "2023-12-27" output: @@ -125,68 +125,65 @@ libr ``` -TKTK continue translation +

Resolver frases incompletas en la consola

-

Troubleshooting incomplete statements

- -As you write and run code in RStudio, it's important to keep an eye on the console. When you see the symbol `>` in the console, this means that the console finished running code and is ready for another operation. However, when you see the symbol `+` in the console, this means that the statement you just ran was incomplete. Incomplete lines of code can occur when you have a typo so that a statement isn't fully closed, like when you miss an opening or closing parenthesis in a function. Below is an example of an incomplete statement: +Es importante vigilar el panel de la consola mientras escribes y ejecutas codigo en RStudio. El simbolo de `>` (o "mayor que") en la consola significa que la consola termino de ejecutar codigo y esta lista para otra operacion. Cuando ves el simbolo de `+` (o "mas") en la consola, este simbolo indica que la frase de codigo que acabas de ejecutar no esta completa. Frases incompletas de codigo surgen de errores de tipeo, como cuando te falto o abrir o cerrar los parentesis en una function. Abajo hay un ejemplo de una frase incompleta de codigo: ```{r eval = FALSE} library(tidyverse ``` -When you run the code above, you should see a `+` symbol appear in the console. This is because you're missing a parenthesis to close the function `library()`. There are two ways to solve this problem. First, if you know what is missing from the statement, you can type the missing bit into the console and hit "Enter". Or, you can click in the console, and then press "Esc", which will remove the incomplete statement and restart the console for additional code. It's good practice to keep an eye on the console to check that the code you're running is producing output. If you run a lot of code and don't see the output that you expect, it may be because you included an incomplete statement, and it would be better to clean the console and check your code before running it again. +Deberias de ver el simbolo de `+` aparecer en la consola cuando ejecutas el codigo arriba, porque te falto un parentesis para cerrar la funcion `library()`. Tienes dos opciones para resolver este problema. Primero, si sabes que simbolo te falta para completar la frase, puedes escribir este simbolo directamente en la consola y terminar de ejecutar el codigo con seleccionar "Enter". La segunda opcion que tienes es hacer clic en la consola y luego seleccionar "Esc", que va a borrar la frase incompleta de la consola y reinciar la consola para que puedas ejecutar mas codigo (despues de corregir tu error en el codigo en el panel de la fuente). Es buena costumbre vigilar la consola para revisar que el codigo que ejectuas produce resultados. Si ejecutas muchas frases de codigo a la vez y no observas los resultados que esperas en la consola, puede que tienes una frase incompleta por alli, y seria mejor limpiar la consola y revisar bien tu codigo antes de ejecutarlo otra vez. -

Create your working directory

+

Crear tu directorio de trabajo

-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. +El siguiente paso en el proceso de configurar tu espacio virtual de trabajo es decidir donde sera tu directorio de trabajo para tu sesion de escribir codigo. Un directorio es una ubicacion fisica en tu computadora (o una carpeta) donde R va a buscar archivos para leer o cargar datos. Cuando escribes datas de R como archivos fisicos, estos archivos fisicos se crearan en tu directorio de trabajo. -You can use the function `getwd()` to check your current working directory. +Puedes usar la funcion `getwd()` para revisar tu directorio corriente de trabajo. ```{r eval = TRUE} getwd() ``` -My working directory is set by default to the folder on my computer where I saved this RMarkdown file. To work in a separate directory that holds only data generated from these vignettes, you can create a new directory or folder on your computer: +Mi directorio de trabajo por defecto es la carpeta en mi computadora donde guarde este archivo de RMarkdown. Para trabajar en un directorio aparte que contiene solo los datos generados en estos tutoriales, puedes mejor crear un nuevo directorio o carpeta en tu computadora: ```{r} ?dir.create -dir.create("/home/gsvidaurre/Desktop/ABISSMAL_vignettes") +dir.create("/home/gsvidaurre/Desktop/ABISSMAL_tutoriales") ``` -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. +En el codigo arriba, deberias de reemplazar el `path`, o la combinacion de directorios arriba (en este ejemplo, el `path` es "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales") para que represente la ubicacion en tu computadora donde quieres guardar la carpeta nueva que se llamara `ABISSMAL_tutoriales` (u otro nombre que prefieres). Si usas el sistema operativo de Windows, tienes que cambiar la direccion de los simbolos 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`. -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()`. +Es comun configurar tu directorio de trabajo con la funcion `setwd()` en cursos preliminares de programacion en R. Es buena costumbre evitar usar `setwd()` en codigo que quieres compartir con colaboradores o codigo que quieres compartir con la comunidad en general siguiendo la filosofia de ciencia abierta, por ejemplo cuando publicas un articulo o compartes una nueva herramienta. Usar `setwd()` cuando compartes tu codigo es suponer que todos los que van a usar tu codigo tienen el mismo directorio de trabajo en su computadora. Hay otras formas en que puedes especificar tu directorio de trabajo a traves de tu codigo sin depender de `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: +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 coleccion 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 tambien tu directorio de trabajo: ```{r} file.copy( - from = "/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/Vignette_01_Introduction.Rmd", - to = "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Vignette_01_Introduction_copy.Rmd" + from = "/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/Tutorial_01_Introduccion.Rmd", + to = "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales/Tutorial_01_Introduccion_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). +En el codigo arriba especificamos dos argumentos a la funcion `file.copy()`. El primer argumento especifica la ubicacion 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 ubicacion 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 estan entrecomillas en cada argumento, para indicar que la informacion entrecomillas se deberia de tratar como el tipo de datos `character` en R, que es un termino formal para informacion en formato de texto. -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". +El resultado en la consola deberia de ser "[1] TRUE" si el archivo se copio y se guardo correctamente despues de que ejecutas el codigo. Puedes revisar que la funcion ejecuto bien con navegar a la carpeta donde se debio de haber guardado el archivo copiado y ver si esta copia existe. Tambien puedes usar la funcion base de R `list.files()` para revisar desde RStudio si el archivo que copiaste existe en tu directorio de trabajo. El resultados de `list.files()` es una lista de todos los archivos adentro de tu directorio de trabajo y esta lista deberia de contener un solo archivo: "Tutorial_01_Introduccion_copy.Rmd". ```{r} -list.files(path = "/home/gsvidaurre/Desktop/ABISSMAL_vignettes") +list.files(path = "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales") ``` -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: - +El codigo arriba es un ejemplo sobre como puedes especificar tu directorio de trabajo en el codigo que escribes sin tener que depender de `setwd()`. En los siguientes tutoriales vas a ver otros ejemplos sobre como puedes leer archivos de o escribir archivos a tu directorio de trabajo sin la funcion `setwd()`. Antes de empezar el siguiente tutorial, puedes borrar el archivo que copiaste a tu directorio de trabajo: ```{r} -file.remove("/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Vignette_01_Introduction_copy.Rmd") +file.remove("/home/gsvidaurre/Desktop/ABISSMAL_tutoriales/Tutorial_01_Introduction_copy.Rmd") ``` -In the next vignette, you will start creating and manipulating objects in R that can be used in the ABISSMAL data processing and analysis pipeline. \ No newline at end of file +En el siguiente tutorial vas a crear y manipular objetos en R que puedes usar para procesar y analizar datos con ABISSMAL. \ No newline at end of file From 63866ae22fdbb31d5bf97163ad24540aec412dca Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Thu, 28 Mar 2024 12:49:34 -0400 Subject: [PATCH 39/69] Started rough translation of third vignette --- R/vignettes/Tutorial_01_Introduccion.Rmd | 2 +- R/vignettes/Tutorial_03_SimularDatos.Rmd | 363 +++++++++++++++++++++++ 2 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 R/vignettes/Tutorial_03_SimularDatos.Rmd diff --git a/R/vignettes/Tutorial_01_Introduccion.Rmd b/R/vignettes/Tutorial_01_Introduccion.Rmd index 084908a..8698ba8 100644 --- a/R/vignettes/Tutorial_01_Introduccion.Rmd +++ b/R/vignettes/Tutorial_01_Introduccion.Rmd @@ -95,7 +95,7 @@ ABISSMAL provee 5 funciones diferentes de R para procesar y analizar datos, y de 1. `combine_raw_data` automáticamente combina hojas de cálculo de 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 infrarrojos 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"), rayos infrarrojo, la cámara, o el sensor de temperatura de ABISSMAL. Las siguientes funciones no aceptan datos de temperatura -2. `detect_perching_events` toma como entrada los resultados de `combine_raw_data` y detecta grupos de detecciones que ocurrieron cerca en el tiempo como eventos de posar. Estos eventos de posa representan periodos de tiempo cuando un individuo estuvo posado en el 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 +2. `detect_perching_events` toma como entrada los resultados de `combine_raw_data` y detecta grupos de detecciones que ocurrieron cerca en el tiempo como eventos de posar. Estos eventos de 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 3. `preprocess_detections` usa como entrada los resultados de `combine_raw_data` y remueve detecciones que ocurrieron muy cerca en el tiempo. Esta función devuelve una hoja de cálculo de datos pre-procesados por tipo de sensor, y en cada hoja de cálculo, las detecciones consecutivas deberían de estar separadas por un umbral temporal predeterminado o más. Por ejemplo, cuando usas un umbral de 1 segundo, solo una detección puede ocurrir por segundo en los datos pre-procesados diff --git a/R/vignettes/Tutorial_03_SimularDatos.Rmd b/R/vignettes/Tutorial_03_SimularDatos.Rmd new file mode 100644 index 0000000..e722d09 --- /dev/null +++ b/R/vignettes/Tutorial_03_SimularDatos.Rmd @@ -0,0 +1,363 @@ +--- +title: "Tutorial 03: Simular Datos" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = TRUE) + +``` + +

Información sobre esta traducción

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

Resumen del tutorial y objetivos de aprendizaje

+ +En este tercer tutorial, vamos a crear datos simulados de moviemientos de animals que fueron detectados por diferentes sensores. El proceso de simular estos datos reemplaza el proceso de coleccion de datos que provee ABISSMAL para grabar datos de animales en vivo. Generar estos datos simulados te va a proveer mas oportunidades para practicar habilidades basicas de escribir codigo y tener control sobre la creacion de estos datos te ayudara entender los pasos diferentes de analisis de datos que siguen. Si quieres ver datos recolectados de pajaros con el software de ABISSMAL, y el codigo que fue usado para analizar esos datos, puedes revisar el preprint del [manuscrito de metodos](https://ecoevorxiv.org/repository/view/6268/) que tiene enlaces a los datos y el codigo que son publicamente acesibles. + +A traves del proceso de simular datos en este tutorial, vas a continuar usando habilidades de programacion que aprendiste en el segundo tutorial, y vas a aprender habilidades adicionales que incluyen: + +1. Crear objetos como vectores y `dataframes` +2. Tipos de datos en R +3. Indexar y manipular objetos +4. Usar frases condicionales +5. Expresiones de `pipe` en el `tidyverse` + +

Cargar paquetes

+ +En el tutorial anterior instalaste el `tidyverse`, una coleccion de paquetes para la ciencia de datos. Tambien aprendiste sobre directorios de trabajo y creaste un directorio nuevo en tu computadora para guardar archivos de datos o imagenes 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 codigo que viste en el segundo tutorial, pero ahora todo el codigo esta combinado en un solo trozo. +```{r message = FALSE, warning = FALSE} + +rm(list = ls()) # Limpia tu ambiente global + +library(tidyverse) # Carga la coleccion de paquetes en el tidyverse + +``` + +

Crear un objeto de `path`

+ +El siguiente paso sera especificar tu directorio de trabajo. En el segundo tutorial, usaste un `string`, o una secuencia de caracteres entrecomillas para indicar texto adentro de codigo 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 mas 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 simbolos para crear un objeto en R (`<-`), y luego la informacion que quieres asignar a este objeto. En este case, la informacion que quieres guardar adentro de este objeto es tu directorio de trabajo en el formato de un `string`, y esta informacion necesita estar entrecomillas. +```{r eval = TRUE} + +path <- "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales" + +``` + +En el codigo arriba, creaste un objeto que se llama `path`. Puedes ver la informacion que contiene este objeto con escribir el nombre del objeto y ejecutar ese codigo en la consola: +```{r eval = TRUE} + +path + +``` + +Tambien puedes ver el contenido de `path` con hacer clic en la pestana de "Environment" y revisar la columna al lado derecho del nombre del objeto. Ahora puedes confirmar que `path` es un objeto nuevo que contiene informacion sobre tu directorio de trabajo y esta disponible en tu ambiente global para mas operaciones. + +Puedes practicar eliminar solamente el objeto `path` de tu ambiente global con escribir y ejecutar el codigo `rm(list = "path")`. Despues de ejecutar este codigo, deberias de inicializar `path` otra vez usando el codigo arriba. + +

Crear marcas de tiempo para detecciones simuladas de movimientos

+ +Los datos primarios que colecta ABISSMAL son marcas de tiempo que indican el momento en el tiempo cuando un sensor se activo y grabo movimiento. Muchas (pero no todas) de estas detecciones se pueden asignar a uno o mas animales que se movieron cerca de un sensor, por ejemplo, cuando un pajaro entra a un contenedor de nido a traves de una antena circular de RFID ("radio frequency identification") montado en la entrada del contenedor. En los siguientes trozos de codigo, vamos a generar datos simulados que representan datos de detecciones grabados por el sensor de RFID y tambien sensores de infrarrojo. + +Digamos que estamos recolectando datos para 2 pajaros adultos a traves de sensores de ABISSMAL montados en un contenedor de nido. La antena de RFID esta montada en la entrada del contenedor, y un par de sensores de infrarrojo, el par "externo", esta 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", esta montado detras 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 pajaro entra al contenedor de nido, luego la antena de RFID, y luego el par interno de sensores infrarrojos. Cuando un pajaro sale del contenedor, el par interno de sensores infrarrojos deberia de activar primero, luego la antena de RFID, y luego el par externo de sensores infrarrojos. + +TKTK continue translation + +We will start by creating an object that holds simulated timestamps for the RFID antenna. This object will be called `rfid_ts`, and it will hold 4 timestamps in hours:minutes:seconds format. Each timestamp will be surrounded by double quotes to denote that we are using text or character string information here. + +These timestamps are combined inside of the same object by using the function `c()`. This function `c()` concatenates values separated by commas into a vector or list object. Above you created an object called `path` without using `c()`, but that object had a single value. Using `c()` allows you to combine multiple values into a vector that will have more than 1 element. +```{r} + +# Create a vector of 4 RFID timestamps or 4 elements in HH:MM:SS format +rfid_ts <- c("10:00:00", "10:05:00", "11:00:00", "11:05:00") + +``` + +You can check out the different properties of the `rfid_ts` object after it's made: +```{r} + +rfid_ts # Run the object name to see its contents + +is.vector(rfid_ts) # A binary value indicating that rfid_ts is a vector + +class(rfid_ts) # A vector with data of type "character" + +length(rfid_ts) # This vector has 4 elements + +``` + +Let's continue by simulating 2 entrance and 2 exit movement events. We can choose timestamps for the infrared beam breakers that precede the RFID timestamps to simulate an entrance event, and infrared beam breaker timestamps that follow the RFID timestamps to simulate an exit event. We will offset detections from each sensor within the entrance and exit movement events by 1 second. +```{r} + +# Simulate outer and inner beam breaker timestamps for an entrance, an exit, and then another entrance and exit +o_irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") +i_irbb_ts <- c("10:00:01", "10:04:59", "11:00:01", "11:04:59") + +``` + +Birds will sometimes perch in the nest container entrance, and the sensors should pick up this behavior. You can add some perching events to the simulated dataset. Here we will simulate perching events for the RFID data only. +```{r} + +rfid_ts <- c(rfid_ts, "08:00:00", "08:00:01", "08:00:02", "08:00:03", "11:30:00", "11:30:01", "11:30:02", "11:30:03", "11:30:04", "11:30:05") + +``` + +In the code above, we modified the object `rfid_ts` by using the function `c()` to add 10 more timestamps to this vector, for a total of 14 elements. Check out the structure of the modified `rfid_ts` object using the function `glimpse()`: +```{r} + +# See the object structure in a format that includes the data type ("chr"), the number of elements ([1:14]), and a preview of the first few elements +glimpse(rfid_ts) + +``` + +Another important type of information to simulate is noise in the sensor detections. For instance, the RFID antenna can fail to detect an individual's passive integrated transponder (PIT) tag, and the infrared beam breakers can activate when birds leave nesting material hanging in the entrance of the container. In both of these cases, the infrared beam breakers should activate when the RFID antenna does not. +```{r} + +# Simulate some RFID detection failures across both beam breaker pairs +# These RFID detection failures occurred over 2 additional simulated entrances and 2 additional simulated exits +o_irbb_ts <- c(o_irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") +i_irbb_ts <- c(i_irbb_ts, "06:05:04", "06:35:09", "07:15:41", "11:10:24") + +glimpse(o_irbb_ts) +glimpse(i_irbb_ts) + +# Simulate some stray detections for the outer beam breaker +o_irbb_ts <- c(o_irbb_ts, "09:45:01", "09:45:02", "09:45:03", "09:45:04", "09:45:05", "09:45:06", "09:45:07", "09:45:08", "09:45:09", "09:45:10", "09:45:11") + +glimpse(o_irbb_ts) + +``` + +You've created simulated, noisy datasets of detections of animal movements, but currently, these datasets are encoded across separate vector objects and do not have important metadata. For instance, some useful metadata includes information about the experimental replicate, the date, and for the RFID data, the unique alphanumeric code of each PIT tag detected by the RFID antenna. Some of this metadata is critically important for downstream analyses (e.g. the date and PIT tag codes). + +Vectors are useful data structures in R, but one limitation of vectors is that you cannot combine different data types into the same object. You can try this out with the code below: +```{r} + +# Create a vector with character, numeric, and binary data types +# Since you're not saving the output into an object, the result will print directly to the console +# You should see that all elements are forced to be character strings in double quotes +c("1", 1, TRUE, FALSE) + +# Create a vector with numeric and binary data types +# You should see that all elements have been forced to numeric. The TRUE and FALSE binary values are converted to the underlying numeric values used to store binary information (TRUE is stored as 1, and FALSE is stored as 0) +c(1, 1, TRUE, FALSE) + +``` + +When you try to combine character, numeric, and binary data types in the same vector, all vector elements will be forced to the character data type. A similar thing happens when you try to combine numeric and binary data types, but the vector is forced to numeric. Note that the binary values TRUE and FALSE are equivalent to the numeric values 1 and 0, respectively. + +

Create metadata vectors

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

Create data frames with primary data and metadata

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

Create data frames using the tidyverse

+ +In the chunk above, you used base R code to add a new column and to update the column name. It took more than a few lines of code to carry out these operations. You could reduce the amount of code needed for these steps by removing the lines included to check your work. But another way that you can reduce the amount of code you're writing for these operations is to use tidyverse notation and functions: +```{r} + +# Make the data frame again with only 2 columns +sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) + +# Use the tidyverse to add the year as the 3rd column +sim_dats <- sim_dats %>% + dplyr::mutate( + year = rep(2023, length(rfid_ts)) + ) + +glimpse(sim_dats) + +``` + +You added a column for the year with the correct column name in fewer lines of code. In this tidyverse syntax, the symbol `%>%` represents a piping operation, in which you're using one object as the input for a subsequent operation. Here, you're using the object `sim_dats` at the input for the function `mutate()`, which you used to create the column `year`. + +The notation `dplyr::` before `mutate()` indicates that the function `mutate()` should be sourced from the package called `dplyr()`. Including the package name with 2 colons before a function name is important when there are multiple functions loaded in your global environment that have the same name. For instance, if you use other packages that also have functions called `mutate()`, and you don't specify which package you want to use, then you could end up with immediate errors (the code fails to run) or worse, you could run the wrong `mutate()` operation for a given analysis (which can lead to errors down the line that are harder to trace). + +Piping operations can simplify the code that you write, so that you don't have to create as many intermediate objects. On the other hand, for the same reason it can take practice to troubleshoot piping operations, especially when they become very long. A useful way to check intermediate results within long piping operations is to include the function `glimpse()` between different piping steps: +```{r} + +# Make the data frame again with only 2 columns +sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) + +# Use the tidyverse to add the year as the 3rd column +sim_dats %>% + glimpse() %>% # See the structure of the first version sim_dats + dplyr::mutate( + # The nrow(.) expression means "get the number of rows for the current object", which in this case is sim_dats + year = rep(2023, nrow(.)) + ) %>% + glimpse() # See the structure of the latest sim_dats with the new column "year" + +``` + +In the code above, you also learned a new way to repeat a value within a piping operation by using the notation '.' inside of a function, which means that you will perform the given operation (getting the number of rows) on the current object (`sim_dats`) piped into the expression (the `nrow()` function). + +You can use a similar rule of thumb to add two more columns for the month and day to the data frame: +```{r} + +# Use the tidyverse to add the year as the 3rd column +sim_dats %>% + glimpse() %>% # See the structure of the original sim_dats + dplyr::mutate( + year = 2023 + ) %>% + glimpse() %>% # See the structure of the intermediate sim_dats with the new column year + # Also add columns for the month and day + dplyr::mutate( + month = 08, + day = 01 + ) %>% + glimpse() # See the structure of the final sim_dats with the additional new columns month and day + +``` + +In the code above, you added 2 more numeric columns to the data frame and you did this without needing to use the `rep()` function. Since you piped an existing data frame into each `dplyr::mutate()` expression, the single value that you specified for each of the year, month, and day columns was automatically repeated to fill the total rows in the data frame. Specifying a single value for a new column can help reduce the amount of code that you write, but only when you truly want the same value repeated across all rows of a data frame. + +You did not save these modifications that you made to `sim_dats` in an object, so the output was printed to the console only. In the next vignette, you will write out this data frame of simulated RFID and IRBB data to spreadsheets as physical files on your computer. \ No newline at end of file From b853af9ef4e5d6f742cdfecc718327eaf47a9e77 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Thu, 28 Mar 2024 12:56:34 -0400 Subject: [PATCH 40/69] [PCT-472]: two more paragraphs of rough translation --- R/vignettes/Tutorial_03_SimularDatos.Rmd | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/R/vignettes/Tutorial_03_SimularDatos.Rmd b/R/vignettes/Tutorial_03_SimularDatos.Rmd index e722d09..2604ec4 100644 --- a/R/vignettes/Tutorial_03_SimularDatos.Rmd +++ b/R/vignettes/Tutorial_03_SimularDatos.Rmd @@ -73,18 +73,18 @@ Los datos primarios que colecta ABISSMAL son marcas de tiempo que indican el mom Digamos que estamos recolectando datos para 2 pajaros adultos a traves de sensores de ABISSMAL montados en un contenedor de nido. La antena de RFID esta montada en la entrada del contenedor, y un par de sensores de infrarrojo, el par "externo", esta 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", esta montado detras 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 pajaro entra al contenedor de nido, luego la antena de RFID, y luego el par interno de sensores infrarrojos. Cuando un pajaro sale del contenedor, el par interno de sensores infrarrojos deberia de activar primero, luego la antena de RFID, y luego el par externo de sensores infrarrojos. -TKTK continue translation - -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. +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 estara entrecomillas para indicar que estamos usando informacion de text o secuencia de caracteres en el formato `string` de R. -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. +Vas a combinar estas marcas de tiempo adentro de un solo objeto usando la funcion `c()`. Esta function 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 tenia un solo valor o elemento. Usar `c()` facilita combinar multiple valores en un objeto como un vector que puede tener multiple elementos. ```{r} -# Create a vector of 4 RFID timestamps or 4 elements in HH:MM:SS format +# Crea un vector de cuatro marcas de tiempo de RFID o cuatro elementos in formato HH:MM:SS rfid_ts <- c("10:00:00", "10:05:00", "11:00:00", "11:05:00") ``` +TKTK continue translation + You can check out the different properties of the `rfid_ts` object after it's made: ```{r} From 494283d8939b4e5c8d39abc036f58282d2092dfe Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Thu, 28 Mar 2024 16:09:56 -0400 Subject: [PATCH 41/69] [PCT-472] Continuing rough translation of third vignette --- R/vignettes/Tutorial_03_SimularDatos.Rmd | 102 ++++++++++++----------- R/vignettes/Vignette_03_SimulateData.Rmd | 6 +- 2 files changed, 58 insertions(+), 50 deletions(-) diff --git a/R/vignettes/Tutorial_03_SimularDatos.Rmd b/R/vignettes/Tutorial_03_SimularDatos.Rmd index 2604ec4..3a09b7f 100644 --- a/R/vignettes/Tutorial_03_SimularDatos.Rmd +++ b/R/vignettes/Tutorial_03_SimularDatos.Rmd @@ -83,157 +83,161 @@ rfid_ts <- c("10:00:00", "10:05:00", "11:00:00", "11:05:00") ``` -TKTK continue translation - -You can check out the different properties of the `rfid_ts` object after it's made: +Puedes ver las propiedades diferentes del objeto `rfid_ts`: ```{r} -rfid_ts # Run the object name to see its contents +rfid_ts # Ejecuta el nombre del objeto para ver sus contenidos -is.vector(rfid_ts) # A binary value indicating that rfid_ts is a vector +is.vector(rfid_ts) # Un valor binario indica si rfid_ts es un vector (TRUE) o no (FALSE) -class(rfid_ts) # A vector with data of type "character" +class(rfid_ts) # Un vector de tipo de dato `character` en R, o tipo `string` -length(rfid_ts) # This vector has 4 elements +length(rfid_ts) # Este vector tiene cuatro elementos ``` -Let's continue by simulating 2 entrance and 2 exit movement events. We can choose timestamps for the infrared beam breakers that precede the RFID timestamps to simulate an entrance event, and infrared beam breaker timestamps that follow the RFID timestamps to simulate an exit event. We will offset detections from each sensor within the entrance and exit movement events by 1 second. +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. ```{r} -# Simulate outer and inner beam breaker timestamps for an entrance, an exit, and then another entrance and exit -o_irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") -i_irbb_ts <- c("10:00:01", "10:04:59", "11:00:01", "11:04:59") +# 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 ``` -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. +Los pajaros a veces posan en la entrada del contenedor usando la antena de RFID como percha, y los sensores deberian de colectar datos sobre este comportamiento. Puedes anadir eventos de posar a los datos simulados, y aqui vas a simular eventos de posar solamente con los datos de RFID. ```{r} rfid_ts <- c(rfid_ts, "08:00:00", "08:00:01", "08:00:02", "08:00:03", "11:30:00", "11:30:01", "11:30:02", "11:30:03", "11:30:04", "11:30:05") ``` -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()`: +En el codigo arriba, modificaste el objeto `rfid_ts` con la funcion `c()` para anadir diez mas marcas de tiempo a este vector para tener un total de 14 elementos. Revisa la estructura del objeto modificado de `rfid_ts` usando la funcion `glimpse()`: ```{r} -# See the object structure in a format that includes the data type ("chr"), the number of elements ([1:14]), and a preview of the first few elements +# Aqui 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 numero de elementos ([1:14]), y los valores de los primeros elementos del vector glimpse(rfid_ts) ``` -Another important type of information to simulate is noise in the sensor detections. For instance, the RFID antenna can fail to detect an individual's passive integrated transponder (PIT) tag, and the infrared beam breakers can activate when birds leave nesting material hanging in the entrance of the container. In both of these cases, the infrared beam breakers should activate when the RFID antenna does not. +Otro tipo de informacion 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 pajaros dejan materal de nido colgando en la entrada del contenedor. En ambos casos, los sensores infrarrojos deberian de activar pero no la antena de RFID. ```{r} -# Simulate some RFID detection failures across both beam breaker pairs -# These RFID detection failures occurred over 2 additional simulated entrances and 2 additional simulated exits +# Simula unas fallas de deteccion de la antena de RFID a traves de ambos pares de sensore infrarrojos +# Estas fallas en deteccion del sensor de RFID surgio en cuatro eventos simulados adicionales (dos entradas y dos salidas) o_irbb_ts <- c(o_irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") i_irbb_ts <- c(i_irbb_ts, "06:05:04", "06:35:09", "07:15:41", "11:10:24") glimpse(o_irbb_ts) glimpse(i_irbb_ts) -# Simulate some stray detections for the outer beam breaker +# 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) ``` -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). +Acabas de crear datos simulados de movimientos de animales con unas detecciones que representan errores de deteccion, pero por el momento estos datos se encuentran en diferentes vectores separados y les faltan metadatos muy importantes. Por ejemplo, metadatos utiles que nos faltan incluyen informacion sobre la replica experimental, la fecha, y para los datos de RFID, el codigo alfanumerico unico de cada etiqueta PIT que fue detectada por la antena de RFID. Algunos de estos metadatos son criticos para los analisis mas adelante, como la fecha y los codigos de las etiquetas PIT. -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: +Los vectores son estructras utiles en R, pero una limitacion de los vectore es que no puedes combinar diferentes tipos de datos en un solo objeto. Puedes intentar combinar diferentes tipos de datos en un vector: ```{r} -# Create a vector with character, numeric, and binary data types -# Since you're not saving the output into an object, the result will print directly to the console -# You should see that all elements are forced to be character strings in double quotes +# Crea un vector con los tipos `character`, `numeric`, y `binary` (o sea, datos de texto, valores numericos, y valores binarios) +# El resultado se deberia de imprimir directamente a la consola porque no estas guardando el resultado en un objeto +# Deberias de ver que todos los elementos se forzan a tipo `character` o `string` entrecomillas c("1", 1, TRUE, FALSE) -# Create a vector with numeric and binary data types -# You should see that all elements have been forced to numeric. The TRUE and FALSE binary values are converted to the underlying numeric values used to store binary information (TRUE is stored as 1, and FALSE is stored as 0) +# Ahora crea un vector con datos `numeric` y `binary` +# Deberias de poder ver que todos los elementos se forzan al tipo `numeric`. Los valores de TRUE y FALSE se conviertieron a los valores numericos que R usa por defecto para guardar informacion binaria (TRUE se convierte a 1, y FALSE se convierte a 0) c(1, 1, TRUE, FALSE) ``` -When you try to combine character, numeric, and binary data types in the same vector, all vector elements will be forced to the character data type. A similar thing happens when you try to combine numeric and binary data types, but the vector is forced to numeric. Note that the binary values TRUE and FALSE are equivalent to the numeric values 1 and 0, respectively. +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 numerico. En este ejemplo, tambien aprendiste que los valores binarios TRUE y FALSE en R son equivalentes a los valores numericos 1 y 0, respectivamente. -

Create metadata vectors

+

Crear vectores de metadatos

-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. +Para los analsis 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 informacion sobre la replica experimental, la fecha, y la identidad de la etiqueta PIT para cada deteccion. -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. +Para crear un vector de metadatos sobre la replica experimental, vas a usar la funcion `rep()` para repetir la informacion sobre la replica experimental automaticamente, en vez de copiar y pegar las misma informacion varias veces. Para configurar el numero de veces que la informacion sobre la replica experimental se vaya a repetir, tambien vas a usar la funcion `length()` para calcular lo largo del vector `rfid_ts` automaticamente, y comunicarle este resultado a `rep()`. Crear un vector de metadatos que tiene el mismo largo que el vector de `rfid_ts` sera util para combinar estos vectores en un solo objeto mas adelante. ```{r} -# The documentation tells us that rep() expects two arguments, `x` and `times` +# La documentacion nos dice que rep() espera dos argumentos, `x` y `time` ?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 +# Crea un vector con informacion sobre la replica experimental +# El argumento `x` contiene la informacion de metadatos que se va a repetir +# El argumento `times` especifica la cantidad de veces que esta informacion se va a repetir exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) glimpse(exp_rep) -# You can also run code without writing out the argument names, as long as the arguments are written in the same order that the function expects: +# Tambien puedes ejecutar codigo sin escribir los nombres de los argumentos, siempre y cuando los argumentos se escriben en el mismo orden que la funcion espera: exp_rep <- rep("Pair_01", length(rfid_ts)) glimpse(exp_rep) ``` -Using `times = length(rfid_ts)` is better practice than manually entering the length of `rfid_ts` (e.g. `times = 14`). When you manually enter a value for `times`, you're assuming that the `rfid_ts` has not changed within and between coding sessions, which can be a dangerous assumption. By using `times = length(rfid_ts)`, you are ensuring that the code above will create a vector of metadata the same length as `rfid_ts` regardless of whether you made additional modifications to `rfid_ts` above. +Usar `times = length(rfid_ts)` es mejor costumbre que configur lo largo de `rfid_ts` mmanualmente (por ejemplo, `times = 14`). Configurar el valor de `times` manualmente es suponer que el objeto `rfid_ts` no ha cambiado dentro de una sesion de escribir codigo, o entre sesiones diferentes, y esto puede ser una suposicion peligrosa. Cuando usas `times = length(rfid_ts)` te aseguras que el codigo arriba va a crear un vector de metadatos del mismo largo que `rfid_ts` sin importar que tantas modificaciones le hayas hecho a `rfid_ts` en el codigo arriba. -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. +Puedes tambien 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 utiles para revisar suposiciones en tu codigo, o para construir nuevos datos y funciones. ```{r} -# If this condition is met, then the result in the console should be "[1] TRUE" +# Si esta condicion se cumple, el resultado en la consola deberia de ser "[1] TRUE" length(rfid_ts) == length(exp_rep) ``` -In the conditional statement above, you're using the symbols `==` to ask if the two vectors `rfid_ts` and `exp_rep` are the same length. +En la frase condicional arriba, estas usando los simbolos `==` para preguntar si los dos vectores `rfid_ts` y `exp_rep` tienen la misma cantidad de elementos (si tienen el mismo largo). -You can also use the symbols `!=` to ask if the two vectors are *not* the same length: +Tambien puedes usar los simbolos `!=` para preguntar si los dos vectores `rfid_ts` y `exp_rep` *no* tienen la misma cantidad de elementos (si *no* tienen el mismo largo): ```{r} -# This statement should yield FALSE, because these vectors are the same length +# El resultado de esta frase deberia de ser FALSE, porque estos vectores tienen el mismo largo length(rfid_ts) != length(exp_rep) ``` -As an example, you can modify `rfid_ts` so that it is a different length than `exp_rep`. Below you'll see some different ways that you can filter out 4 elements of the vector `rfid_ts` so that this vector is 10 elements long. +Como un ejemplo, puedes modificar `rfid_ts` para que tenga un numero de elementos diferente a `exp_rep`. Abajo puedes ver algunas formas diferentes de filtrar o eliminra cuatro elementos del vector `rfid_ts` para que tenga diez elementos total. ```{r} -# You can use : to create a sequence of numbers from indices 5 to the length of rfid_ts +## Crear indices numericos para filtrar un objeto + +# Puedes usar el simbolo `:` para crear una secuencia de numeros de los indices 5 a lo largo de rfid_ts 5:length(rfid_ts) -# You can also use the function `seq()` to create the same sequence of numeric indices +# Tambien puedes usar la funcion `seq()` para crear la misma secuencia de indices numericos que ves arriba 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()` +# Si quieres filtrar elementos no consecutivos, puedes crear un vector de indices con la funcion `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 +## Filtrar un vector por indices numericos + +# Cuando insertas cualquiera de las expresiones arriba adentro de los corchetes que vienen despues del nombre del vector, puedes seleccionar los elementos de indice cinco a lo largo de rfid_ts, y eliminar los primeros cuatro elementos rfid_ts[5:length(rfid_ts)] rfid_ts[seq(from = 5, to = length(rfid_ts), by = 1)] rfid_ts[c(1, 3, 5, 6, 8, 10, 11, 12, 13, 14)] -# 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 +# Puedes usar cualquier de los metodos arriba para crear una secuencia de indices que quieres eliminar, y luego usar el simbolo `-` adentro de los corchetes para eliminar los elementos en esos indices particulares. Por ejemplo: +rfid_ts[-c(1:4)] # los numeros deberian de estar adentro de la funcion `c()` para que este tipo de filtrar de forma invertida funcione ``` -Next, you can check whether the modified version of `rfid_ts` is the same length as `exp_rep`: +Luego puedes revisar si esta version modificada de `rfid_ts` es el mismo largo que `exp_rep`: ```{r} -# This statement should yield TRUE, because these vectors are not the same length +# Esta frase deberia de resultar en TRUE, porque estos vectores ya no tienen el mismo largo length(rfid_ts[-c(1:4)]) != length(exp_rep) ``` +TKTK continue translation +

Create data frames with primary data and metadata

For later analyses, it is really important to have the metadata accompany your primary data in the very same object. To combine the primary data and metadata for these simulated datasets, you can rely on an object called a "data frame". Data frames are similar to spreadsheets. They have 2 dimensions (rows and columns), and you can store multiple different types of data in the same data frame. You can also write out data frames to spreadsheets that exist as physical files on your computer. diff --git a/R/vignettes/Vignette_03_SimulateData.Rmd b/R/vignettes/Vignette_03_SimulateData.Rmd index 5d4aaa1..f9fbbd1 100644 --- a/R/vignettes/Vignette_03_SimulateData.Rmd +++ b/R/vignettes/Vignette_03_SimulateData.Rmd @@ -92,7 +92,7 @@ length(rfid_ts) # This vector has 4 elements ``` -Let's continue by simulating 2 entrance and 2 exit movement events. We can choose timestamps for the infrared beam breakers that precede the RFID timestamps to simulate an entrance event, and infrared beam breaker timestamps that follow the RFID timestamps to simulate an exit event. We will offset detections from each sensor within the entrance and exit movement events by 1 second. +Let's continue by simulating 2 entrance and 2 exit movement events. We can choose timestamps for the outer pair of infrared beam breakers that precede the RFID timestamps and timestamps for the inner beam breaker pai that follow the RFID antenna to simulate an entrance event. We can simulate detections by these sensors in the opposite order (inner beam breakers, then RFID, followed by the outer beam breakers) to simulate an exit event. We will offset detections from each sensor within the entrance and exit movement events by 1 second. ```{r} # Simulate outer and inner beam breaker timestamps for an entrance, an exit, and then another entrance and exit @@ -199,6 +199,8 @@ length(rfid_ts) != length(exp_rep) As an example, you can modify `rfid_ts` so that it is a different length than `exp_rep`. Below you'll see some different ways that you can filter out 4 elements of the vector `rfid_ts` so that this vector is 10 elements long. ```{r} +## Create numeric indices for filtering an object + # You can use : to create a sequence of numbers from indices 5 to the length of rfid_ts 5:length(rfid_ts) @@ -208,6 +210,8 @@ seq(from = 5, to = length(rfid_ts), by = 1) # If you want to filter out non-consecutive elements, you can create a vector of indices with the function `c()` c(1, 3, 5, 6, 8, 10, 11, 12, 13, 14) +## Filter an object using numeric indices + # When you put any of the expressions above inside of square brackets after the object name you will pull out elements 5 to the length of rfid_ts and drop the first 4 elements rfid_ts[5:length(rfid_ts)] From 286acb3535e3d276be76ee03ba830a5a48fab0cf Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Fri, 29 Mar 2024 12:00:34 -0400 Subject: [PATCH 42/69] [PCT-472]: Pushing to finish rough translation of second vignette --- R/vignettes/Tutorial_03_SimularDatos.Rmd | 42 ++++++++++++------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/R/vignettes/Tutorial_03_SimularDatos.Rmd b/R/vignettes/Tutorial_03_SimularDatos.Rmd index 3a09b7f..24f1853 100644 --- a/R/vignettes/Tutorial_03_SimularDatos.Rmd +++ b/R/vignettes/Tutorial_03_SimularDatos.Rmd @@ -236,13 +236,11 @@ length(rfid_ts[-c(1:4)]) != length(exp_rep) ``` -TKTK continue translation +

Crear `dataframes` con datos primarios y metadatos

-

Create data frames with primary data and metadata

+Es importante combinar los metadatos con los datos primarios para analisis futuros, y puedes combinarlos usando un tipo de objeto que se llama un `dataframe`. Los `dataframes` son parecidos a las hojas de calculo porque tienen dos dimensiones (filas y columnas), y puedes guardar multiples tipos de datos diferentes en el mismo `dataframe`. Tambien puedes guardar `dataframes` en hojas de calculo o archivos fisicos en tu computadora. -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: +Para combinar dos vectores en un solo `dataframe`, los vectores tienen que tener el mismo largo. Cuando intentas combinar dos vectores de largo diferentes, deberias de recibir un mensaje de error en la consola especificando que los dos argumentos no tienen el mismo numero de filas: ```{r eval = FALSE} sim_dats <- data.frame(exp_rep, rfid_ts[-c(1:4)]) @@ -252,7 +250,7 @@ sim_dats <- data.frame(exp_rep, rfid_ts[-c(1:4)]) ``` -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: +Ahora puedes usar los vectores enteros de `exp_rep` y `rfid_ts` para crear el `dataframe`, y los vectores se van a convertir en las columnas del `dataframe`: ```{r} sim_dats <- data.frame(exp_rep, rfid_ts) @@ -261,9 +259,9 @@ glimpse(sim_dats) ``` -When you check out the structure of the new data frame `sim_dats`, you can see that it has 14 rows and 2 columns. For each column (after the "$" symbol), you can see the column name (here `exp_rep` and `rfid_ts`), the type of data in each column (both columns are type "character"), and then a preview of the first values in each column. +Cuando revisas la estructura del objeto `sim_dats`, el `dataframe` nuevo, puedes ver que tiene 14 filas y 2 columnas. Para cada columna (despues del simbolo de "$"), puedes ver que el nombre de la columna (aqui `exp_rep` y `rfid_ts`), el tipo de dato en cada columna (en este momento cada column es del tipo `character`), y luego los valores de cada columna en las primeras filas del `dataframe`. -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`` +Puedes cambiar los nombres de cada columna con anadir un nombre nuevo y el simbolo `=` antes de cada vector. Abajo el vector `exp_rep` se convierte en la column `replicate` con la replica experimental y el vector `rfid_ts` se convierte en la columna `timestamps` con las marcas de tiempo. ```{r} sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) @@ -272,7 +270,7 @@ glimpse(sim_dats) ``` -We can add additional metadata to this data frame now that it's been created. For instance, it would be useful to have information about the date when these timestamps were collected. You can start by adding a column for the year, which will be data type "dbl" (which stands for "double" or "numeric"): +Podemos anadir metadatos adicionales a este `dataframe`, como informacion sobre la fecha de coleccion de datos. Puedes primero añadir una columna para el año usando el tipo de datos `double` en R que es un tipo de dato `numeric`. ```{r} sim_dats <- cbind(sim_dats, rep(2023, length(rfid_ts))) @@ -281,36 +279,36 @@ glimpse(sim_dats) ``` -The year column has a strange name when we add the new column using `cbind()`. The column name is the expression that you wrote inside of `cbind()`. You can use the function `names()` and square bracket indexing to change the strange column name to "year": +La columna del año tiene un nombre extraño cuando añadimos una nueva columna usando `cbind()`, y si revisas el codigo de arriba, puedes ver que el nombre de esta columna se parece mucho al codigo que escribiste adentro de `cbind()`. Puedes usar la funcion `names()` e indexar con corchetes para cambiar el nombre extraño a un nombre mejor, como "year": ```{r} -# This function returns a vector of the column names of the data frame +# Esta funcion devuelve un vector de los nombres de las columnas del `dataframe` names(sim_dats) -# Use square bracket indexing and the function `ncol()` to find the last column name -ncol(sim_dats) # There are 3 columns in this data frame +# Usa indexar con corchetes y la funcion `ncol()` para encontrar el ultimo nombre entre los nombres de todas las columnas, porque esta ultima columna contiene la informacion sobre el año +ncol(sim_dats) # Hay 3 columnas en este `dataframe` -# This expression gets you the name of the last column +# Esta expresion devuelve el nombre de la ultima columna names(sim_dats)[ncol(sim_dats)] -# Then you can overwrite the last column name with a new name +# Puedes sobreescribir el nombre de la ultima columna con un nombre nuevo names(sim_dats)[ncol(sim_dats)] <- "year" -# Confirm that the name was changed correctly +# Confirma que el nombre de la columna de año se actualizo de la forma que esperas names(sim_dats) glimpse(sim_dats) ``` -

Create data frames using the tidyverse

+

Crear `dataframes` usando el `tidyverse`

-In the chunk above, you used base R code to add a new column and to update the column name. It took more than a few lines of code to carry out these operations. You could reduce the amount of code needed for these steps by removing the lines included to check your work. But another way that you can reduce the amount of code you're writing for these operations is to use tidyverse notation and functions: +En el codigo arriba, usaste codigo de R base para anadir una columna nueva y actualizar el nombre de esa columna. Te tomo varias lineas de codigo para completar estas operaciones. Puedes reducir la cantidad de codigo que necesitas para estos pasos si eliminas las lineas de codigo que usaste para revisar las operaciones. Pero otra forma para reducir la cantidad de codigo que escribes para esta serie de operaciones es usar la notacion y coleccion de funciones del `tidyverse`: ```{r} -# Make the data frame again with only 2 columns +# Crear el `dataframe` de nuevo con dos columnas sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) -# Use the tidyverse to add the year as the 3rd column +# Usa el `tidyverse` para anadir el año como la tercera columna sim_dats <- sim_dats %>% dplyr::mutate( year = rep(2023, length(rfid_ts)) @@ -320,7 +318,9 @@ glimpse(sim_dats) ``` -You added a column for the year with the correct column name in fewer lines of code. In this tidyverse syntax, the symbol `%>%` represents a piping operation, in which you're using one object as the input for a subsequent operation. Here, you're using the object `sim_dats` at the input for the function `mutate()`, which you used to create the column `year`. +Acabas de anadir una columna para el año con el nombre correcto en menos lineas de codigo. En la notacion del `tidyverse`, el simbolo de `%>%` significa una operacion de `pipe`, en que usas el objeto antes del simbolo de `%>%` como entrada para la operacion o funcion que sigue el simbolo de `%>%`. Arriba usaste el objeto de `sim_dats` como entrada para la funcion `mutate()`, que usaste para crear la columna `year`. + +La notacion `dplyr::` antes de `mutate()` indica que la funcion `mutate()` se deberia de acceder desde el paquete que se llama `dplyr()`. Incluir el nombre del paquete con dos puntos repetidos dos veces es una notacion importante para usar cuando hay multiples funciones accesibles en tu ambiente global con el mismo nombre. 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). From 4a69a1e44e61750e06833df44214dbdecfe366fc Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Fri, 29 Mar 2024 12:29:57 -0400 Subject: [PATCH 43/69] [PCT-472]: Finished rough translation of third vignette --- R/vignettes/Tutorial_03_SimularDatos.Rmd | 38 +++++++++++------------- R/vignettes/Vignette_03_SimulateData.Rmd | 2 +- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/R/vignettes/Tutorial_03_SimularDatos.Rmd b/R/vignettes/Tutorial_03_SimularDatos.Rmd index 24f1853..caa8ef9 100644 --- a/R/vignettes/Tutorial_03_SimularDatos.Rmd +++ b/R/vignettes/Tutorial_03_SimularDatos.Rmd @@ -308,7 +308,7 @@ En el codigo arriba, usaste codigo de R base para anadir una columna nueva y act # Crear el `dataframe` de nuevo con dos columnas sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) -# Usa el `tidyverse` para anadir el año como la tercera columna +# Usa el tidyverse para anadir el año como la tercera columna sim_dats <- sim_dats %>% dplyr::mutate( year = rep(2023, length(rfid_ts)) @@ -320,48 +320,46 @@ glimpse(sim_dats) Acabas de anadir una columna para el año con el nombre correcto en menos lineas de codigo. En la notacion del `tidyverse`, el simbolo de `%>%` significa una operacion de `pipe`, en que usas el objeto antes del simbolo de `%>%` como entrada para la operacion o funcion que sigue el simbolo de `%>%`. Arriba usaste el objeto de `sim_dats` como entrada para la funcion `mutate()`, que usaste para crear la columna `year`. -La notacion `dplyr::` antes de `mutate()` indica que la funcion `mutate()` se deberia de acceder desde el paquete que se llama `dplyr()`. Incluir el nombre del paquete con dos puntos repetidos dos veces es una notacion importante para usar cuando hay multiples funciones accesibles en tu ambiente global con el mismo nombre. +La notacion `dplyr::` antes de `mutate()` indica que la funcion `mutate()` se deberia de acceder desde el paquete que se llama `dplyr`. Incluir el nombre del paquete con dos puntos repetidos dos veces es una notacion importante para usar cuando hay multiples funciones accesibles en tu ambiente global con el mismo nombre. Por ejemplo, si usas otros paquetes aparte de `dplyr` que tambien tienen funciones que se llaman `mutate()`, y no especificas cual paquete quieres usar, puedes terminar con errores inmediatos (como cuando el codigo no se puede ejecutar). Incluso si el codigo ejecuta, usar la operacion equivocada de otra funcion de `mutate()` puede introducir errores a tus analisis mas adelante que son dificiles de identificar. -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: +Las oparaciones de `piping` con el simbolo `%>%` (o un `pipe`) pueden simplicar el codigo que escribes porque no creas tantos objetos intermedios como cuando usas R base. Por otro lado, por esta misma razon puede tomar practica solucionar errors con operaciones de `piping` cuando estas operaciones son largas y anidadas. Una forma util para revisar resultados intermedios adentro de operaciones largas de `piping` es incluir la funcion `glimpse()` entre diferentes pasos de la operacion: ```{r} -# Make the data frame again with only 2 columns +# Crear el data frame otra vez con solo dos columnas sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) -# Use the tidyverse to add the year as the 3rd column +# Usa el tidyverse para añadir el año como la tercera columna sim_dats %>% - glimpse() %>% # See the structure of the first version sim_dats + glimpse() %>% # Ver la estructura de la primera version de sim_dats dplyr::mutate( - # The nrow(.) expression means "get the number of rows for the current object", which in this case is sim_dats + # La expresion nrow(.) significa "obtener el numero de filas para el objeto actual". El objeto en este caso es sim_dats year = rep(2023, nrow(.)) ) %>% - glimpse() # See the structure of the latest sim_dats with the new column "year" + glimpse() # Ver la estructura de la version mas reciente de sim_dats con la columna nueva de "year" ``` -In the code above, you also learned a new way to repeat a value within a piping operation by using the notation '.' inside of a function, which means that you will perform the given operation (getting the number of rows) on the current object (`sim_dats`) piped into the expression (the `nrow()` function). +En el codigo arriba, tambien aprendiste una forma nueva para repetir un valor adentro de una operacion de `piping` con la notacion `.` adentro de una funcion, que significa que la operacion ejecutara con el objeto actual. En el ejemplo arriba, `.` se refiere al objeto `sim_dats` que se uso como entrada para la operacion entera de `piping`. Como el simbolo `.` esta adentro de la funcion `nrow()`, la funcion deberia de devolver el numero de filas de `sim_dat`. -You can use a similar rule of thumb to add two more columns for the month and day to the data frame: +Puedes usar una operacion parecida para anadir dos columnas al `dataframe` que contienen informacion del mes y dia: ```{r} - -# Use the tidyverse to add the year as the 3rd column + +# Usar el tidyverse para anadir el año como la tercera columna sim_dats %>% - glimpse() %>% # See the structure of the original sim_dats + glimpse() %>% # Ver la estructura de la version original de 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 + glimpse() %>% # Ver la estructura de la version intermedia de sim_dats con la nueva columna del año + # Tambien puedes anadir columnas para el mes ("month") y dia ("day") dplyr::mutate( month = 08, day = 01 ) %>% - glimpse() # See the structure of the final sim_dats with the additional new columns month and day + glimpse() # Ver la estructura de la version final de sim_dats con las columnas adicionales con el mes y el dia ``` -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. +En el codigo arriba, anadiste dos columnas numericas mas al `dataframe` y lo hiciste sin necesitar usar la funcion `rep()` para repetir valores. Esto fue posible porque usaste un `dataframe` que ya existia como la entrada a las operaciones de `dplyr::mutate()`, y el unico valor que especificaste para cada columna nueva del año, mes, y dia se repitio automaticamente para llenar todas las filas en el data frame para cada columna. Especificar un solo valor para una nueva columna puede ayudar reducir la cantidad de codigo que escribes, pero solo cuando de verdad quieres que el mismo valor se repite por todas las filas del `dataframe`. -You did not save these modifications that you made to `sim_dats` in an object, so the output was printed to the console only. In the next vignette, you will write out this data frame of simulated RFID and IRBB data to spreadsheets as physical files on your computer. \ No newline at end of file +Como no guardaste estas modificaciones a `sim_dats` en un objeto el resultado del codigo 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 calculo como archivos fisicos en tu computadora. \ No newline at end of file diff --git a/R/vignettes/Vignette_03_SimulateData.Rmd b/R/vignettes/Vignette_03_SimulateData.Rmd index f9fbbd1..4cfe4da 100644 --- a/R/vignettes/Vignette_03_SimulateData.Rmd +++ b/R/vignettes/Vignette_03_SimulateData.Rmd @@ -316,7 +316,7 @@ glimpse(sim_dats) You added a column for the year with the correct column name in fewer lines of code. In this tidyverse syntax, the symbol `%>%` represents a piping operation, in which you're using one object as the input for a subsequent operation. Here, you're using the object `sim_dats` at the input for the function `mutate()`, which you used to create the column `year`. -The notation `dplyr::` before `mutate()` indicates that the function `mutate()` should be sourced from the package called `dplyr()`. Including the package name with 2 colons before a function name is important when there are multiple functions loaded in your global environment that have the same name. For instance, if you use other packages that also have functions called `mutate()`, and you don't specify which package you want to use, then you could end up with immediate errors (the code fails to run) or worse, you could run the wrong `mutate()` operation for a given analysis (which can lead to errors down the line that are harder to trace). +The notation `dplyr::` before `mutate()` indicates that the function `mutate()` should be sourced from the package called `dplyr`. Including the package name with 2 colons before a function name is important when there are multiple functions loaded in your global environment that have the same name. For instance, if you use other packages that also have functions called `mutate()`, and you don't specify which package you want to use, then you could end up with immediate errors (the code fails to run) or worse, you could run the wrong `mutate()` operation for a given analysis (which can lead to errors down the line that are harder to trace). Piping operations can simplify the code that you write, so that you don't have to create as many intermediate objects. On the other hand, for the same reason it can take practice to troubleshoot piping operations, especially when they become very long. A useful way to check intermediate results within long piping operations is to include the function `glimpse()` between different piping steps: ```{r} From 568b8bcc1ae42de0a0a779b24825fc6798426f85 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Fri, 29 Mar 2024 16:36:59 -0400 Subject: [PATCH 44/69] [PCT-472]: Halfway through fourth vignette (rough translation) --- R/vignettes/Tutorial_04_GuardarDatos.Rmd | 704 +++++++++++++++++++++++ R/vignettes/Vignette_04_SaveData.Rmd | 18 +- 2 files changed, 713 insertions(+), 9 deletions(-) create mode 100644 R/vignettes/Tutorial_04_GuardarDatos.Rmd diff --git a/R/vignettes/Tutorial_04_GuardarDatos.Rmd b/R/vignettes/Tutorial_04_GuardarDatos.Rmd new file mode 100644 index 0000000..8ecef78 --- /dev/null +++ b/R/vignettes/Tutorial_04_GuardarDatos.Rmd @@ -0,0 +1,704 @@ +--- +title: "Tutorial 04: Guardar Datos" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = TRUE) + +``` + +

Información sobre esta traducción

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

Resumen del tutorial y objetivos de aprendizaje

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

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

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

Crear los datos simulados

+ +En el codigo abajo, vas a recrear los datos simulados de RFID y sensores infrarrojos del tutorial anterior. Aqui estamos combinando el codigo en menos trozos comparado con el tercer tutorial: +```{r} + +# Crea un vector de cuatro marcas de tiempo de RFID en formato HH:MM:SS +rfid_ts <- c("10:00:00", "10:05:00", "11:00:00", "11:05:00") + +# Anade 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) + +``` + +```{r} + +# Simula marcas de tiempo para los pares externos ("o_") e internos ("i_") de sensores infrarrojos 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 deteccion por la antena de RFID en las marcas de tiempo de cada par de sensores infrarrojos +# Estos errores de deteccion surgieron en cuatro movimientos adicionales: dos entradas y dos salidas +o_irbb_ts <- c(o_irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") +i_irbb_ts <- c(i_irbb_ts, "06:05:04", "06:35:09", "07:15:41", "11:10:24") + +# Simula unas detecciones de ruido en las marcas de tiempo del par externo de sensores infrarrojo +o_irbb_ts <- c(o_irbb_ts, "09:45:01", "09:45:02", "09:45:03", "09:45:04", "09:45:05", "09:45:06", "09:45:07", "09:45:08", "09:45:09", "09:45:10", "09:45:11") + +glimpse(o_irbb_ts) +glimpse(i_irbb_ts) + +``` + +

Simula tres dias de coleccion de datos de RFID

+ +En el codigo abajo, vas a combinr 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 dia, y tambien una columna con los valores de dos etiquetas PIT (una etiqueta por individuo simulado), y una columna con informacion sobre el tipo de sensor. Deberias de reconocer partes de este codigo desde el tutorial anterior: +```{r} + +# Crea un vector para la replica 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 (4 detecciones) al primer individuo, y el segundo evento de posar (6 detecciones) al segundo individuo +# Estas tres expresiones de rep() estan combinadas en un solo vector usando la funcion c() +PIT_tag <- c(rep("1357aabbcc", 4), rep("1357aabbcc", 4), rep("2468zzyyxx", 6)) + +# Crea el dataframe con los metadatos de replica experimental y las marcas de tiempo +sim_dats_rfid <- data.frame(chamber_id = exp_rep, timestamps = rfid_ts) + +# Sobrescribe el datafram con la version modificada que tiene columnas para el año, el mes, y el dia +sim_dats_rfid <- sim_dats_rfid %>% + dplyr::mutate( + year = 2023 + ) %>% + dplyr::mutate( + month = 08, + day = 01 + ) %>% + # Anade los metadatos de las etiquetas PIT en una columna nueva + dplyr::mutate( + PIT_tag = PIT_tag + ) %>% + dplyr::mutate( + sensor_id = "RFID" + ) + +glimpse(sim_dats_rfid) + +``` + +Ahora puedes usar este `dataframe` para simular el proceso de coleccion de datos a traves de dos dias mas. Para crear mas observaciones (filas) para dos dias adicionales, puedes adjuntar filas de una copia modificada de `sim_dats_rfid` al objeto original de `sim_dats_rfid`. + +En el codigo abaho, estas usando una operacion `pipe` para usar `sim_dats_rfid` como entrada en `bind_rows()`, y con esta notacion estas especificando que `sim_dats_rfid` es el objeto original al cual quieres adjuntar mas filas. Luego el codigo adentro de `bind_rows()` especifica el `dataframe`, o las filas nuevas, que quieres adjuntar a `sim_dats_rfid`. En este case, el codigo adentro de `bind_rows()` provee `sim_dats_rfid` a `dplyr::mutate()` para modificar la columna de `day` para representar un dia adicional de coleccion de datos. Luego repites este proceso para anadir un tercer dias de recolectar datos: +```{r} + +sim_dats_rfid <- sim_dats_rfid %>% + bind_rows( + sim_dats_rfid %>% + dplyr::mutate( + day = 02 + ) + ) %>% + bind_rows( + sim_dats_rfid %>% + dplyr::mutate( + day = 03 + ) + ) + +glimpse(sim_dats_rfid) # Tres veces el numero original de filas, se ve bien + +``` + +

Revisar columnas de `dataframe` con R base

+ +Acabas de revisar la estructura del `dataframe` para confirmar que los datos simulados tiene datos recolectados a traves de tres dias. Tambien puedes revisar los valores unicos que estan presentes en la columna de `day`. Abajo puedes ver una forma de revisar los valores unicos adentro de una columna de un `dataframe`, usando dos ejemplos diferentes de notacion de R base para acceder columnas adentro de un `dataframe`: +```{r} + +# Escribir una expresion con el nombre de un objeto dataframe, un simbolo $, y el nombre de una columna te ayuda sacar o acceder una columan a la vez de un dataframe. Una columna de un dataframe es un vector, por ende cuando ejecutas este codigo deberias de ver un vector de valores impreso en la consola +sim_dats_rfid$day + +# Puedes tambien acceder una columna en un dataframe con indexar si escribes dos pares corchetes (ambos pares de abrir y cerrar) despues del nombre del dataframe, y colocas el nombre de la columna entrecomillas adentro de del par interno de corchetes +sim_dats_rfid[["day"]] + +# Puedes usar la funcion unique() para ver los valores unicos adentro de un vector, incluyendo una columna en un dataframe +unique(sim_dats_rfid$day) # Tres dias, se ve bien + +unique(sim_dats_rfid[["day"]]) # Tres dias, se ve bien + +``` + +

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

+ +Tambien puedes revisar valores unicos en una columna usando funciones del `tidyverse`. En la expresion abajo, vas a usar una expresion de `pipe` para proveer el `dataframe` `sim_dats_rfid` a la funcion `pull()`, que va a facilitar acceder la columna `day` del dataframe como un vector. Luedo este vector de la columna `day` se va a usar como entrada a la funcion `unique()` para revisar los valores unicos del vector mismo. La funcion `unique()` no require un argumento adentro de los parentesis porque ya recibio el valor de entrada que necesita a traves de la operacion de `piping`. +```{r} + +# Tres dias, se ve bien +sim_dats_rfid %>% + pull(day) %>% + unique() + +``` + +

Simula tres dias de recolectar datos de los sensores infrarrojo

+ +Ahora puedes repetir este proceso de crear un `dataframe` con metadatos para los datos de los sensores de infrarrojo. Dado que los sensores de infrarrojo no colectan informacion sobre la identidad unica de individuos, vas a anadir columnas para el año, el mes, el dia, y el tipo de sensor. Tambien vas a simular la coleccion de datos para estos sensores a traves de los mismos tres dias que los datos simulados de RFID. +```{r} + +# Sobreescribe 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)) + +# Anade 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, + # Anade un identificador unico para cada par de sensores + # Cada etiqueta unica se repitira 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) + +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 numero de filas, se ve bien + +# Tres dias, se ve bien +sim_dats_irbb %>% + pull(day) %>% + unique() + +``` + +

Guarda un `dataframe` como un archivo fisico

+ +Los `dataframes` que creas y manipulas en R se pueden guardar como archivos fisicos 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 funcion `write.csv()` para guardar `dataframes` a hojas de calculo `.csv` en tu computadora: +```{r eval = FALSE} + +?write.csv + +``` + +Para escribir un archivo fisico 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 informacion a `write.csv()` con combinar tu directorio de trabajo y el nombre del archivo usando la funcion `file.path()`. Para este ejemplo, vas a crear un archivo de preuba mientras practicas como usar `write.csv()`: +```{r} + +# Combina el path para tu directorio de trabajo con el nombre del archivo que quieres escribir +# La funcion file.path() combinara ambas piezas de informacion en un solo path para este archivo +rfid_file <- file.path(path, "test_file.csv") + +# Este objeto contiene la ubicacion donde vas a guardar el archivo, y luego el nombre del archivo +rfid_file + +``` + +Luego puedes proveer el `dataframe` a `write.csv()` usando un `pipe` y puedes especificar informacion adicional para crear el archivo `.csv`, como si quieres anadir una columna adicional de identidades numericas de las filas: +```{r eval = FALSE} + +sim_dats_rfid %>% + # Escribe el dataframe como una hoja de calculo en formato .csv. No incluyes los nombres de las filas (row.names = FALSE) + # El simbolo "." abajo significa que la funcion write.csv() va a operar sobre el objeto que proveyo 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 documentacion para la funcion `write.csv()`, esta funcion va a incluir las nombres de las columnas en la hoja de calculo por defecto. La funcion tambien no va a adjuntar esta informacion en el `dataframe` al archivo de `.csv` si esta hoja de calculo ya existe, o sea, si ya creaste el archivo de `.csv` y vuelves a correr el codigo arriba, el archivo se va a sobrescribir por defecto. + +Puedes revisar que `write.csv()` funcion como esperabas con usar `list.files()` para ver los archivos en tu directorio de trabajo. +```{r eval = FALSE} + +# Ve una lista de todos los archivos en este path +list.files(path) + +``` + +Tambien puedes usar `list.files()` para customizar una busqueda con el argumento `pattern`. Usar el argumento `pattern` es parecido a buscar una palabra especifica adentro de un documento de texto. El simbolo de "$" despues de ".csv" significa que la funcion deberia de buscar todos los archivos que *terminan* en el patron ".csv". +```{r eval = FALSE} + +# Devuelve solo archivos que terminan en el patron ".csv" en este path particular +list.files(path, pattern = ".csv$") + +``` + +

Leer una hoja de calculo

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

Guardar datos simuladoes para analisis con ABISSMAL

+ +En el codigo 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 calculo diferente por el tipo de sensor y el dia de coleccion de datos. Estas hojas de calculo 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 empiricos de animales. + +

Filtrar un `dataframe`

+ +Vas a practicar como usar la funcion `dplyr::filter()` para filtrar filas de un `dataframe` por dia y luego guardar un `dataframe` filtrado con `write.csv()`. Para filtrar un `dataframe`, puedes usar una frase condicional adentro de la funcion `dplyr::filter()`: +```{r} + +# Provee el dataframe a la funcion filter() con un "pipe" +sim_dats_rfid %>% + # Filtra el dataframe con seleccionar todas las filas en que la columna de dia era igual a uno (el primer dia de coleccion de datos) + dplyr::filter(day == 1) %>% + glimpse() + +# Revisa que este paso de filtrar se hizo correctamente. El unico valor adentro de la columna del dia deberia de ser uno, y se ve bien +sim_dats_rfid %>% + dplyr::filter(day == 1) %>% + pull(day) %>% + unique() + +``` + +Puedes obtener resultados similares cuando inviertes la frase condicional adentro de `dplyr::filter()` para eliminar los dias que **no** fueron ni el segundo dia ni el tercer dia de coleccion de datos. Abajo combinaste dos frases condicionales usando el simbolo de "&". +```{r} + +sim_dats_rfid %>% + # Filtra el dataframe con seleccionar todas las filas en que los valores en la columna de dia no son iguales a 2 o 3 + dplyr::filter(day != 2 & day != 3) %>% + glimpse() + +# Usa la funcion par ver los valores uncios 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() + +``` + +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 dia: +```{r eval = FALSE} + +# Crea un directorio nuevo adentro de tu directorio de trabajo para guardar datos +file.path(path, "Data") # Revisa el path nuevo +dir.create(file.path(path, "Data")) # Crea el path nuevo + +# Crea un directorio nuevo adentro del directorio de Data para los datos originales de RFID +file.path(path, "RFID") # Revisa el path nuevo +dir.create(file.path(path, "Data", "RFID")) # Crea el path nuevo + +# Inicializa el nombre del archivo nuevo con el path de tu directorio de trabajo +# Asegurate de especificar que el archivo se va a guardar adentro de la carpeta neuva "RFID" +rfid_file <- file.path(path, "Data/RFID", "test.csv") +rfid_file + +# Filtra los datos simulados de RFID para sacar el primer dia de coleccion de datos +sim_dats_rfid %>% + dplyr::filter(day == 1) %>% + # Escribe el dataframe filtrado como una hoja de calculo en formato .csv. No incluyes nombres para las filas + # Recuerda que el simbolo "." significa que la funcion ca a usar el objeto que proveyo la operacion de "pipe", que aqui es el dataframe filtrado para seleccionar solo el primer dia de coleccion de datos + write.csv(x = ., file = rfid_file, row.names = FALSE) + +``` + +Revisa que el archivo de prueba se creo adentro de la carpeta nueva de RFID, y luego puedes eliminar este archivo. +```{r eval = FALSE} + +list.files(file.path(path, "Data/RFID"), pattern = ".csv$") + +rfid_file <- file.path(path, "Data/RFID", "test.csv") +rfid_file + +file.remove(rfid_file) + +``` + +

Practicar escribir un bucle

+ +Podrias repetir el codigo arriba seis veces (tres veces por sensor) para escribir un `dataframe` por cada dia de coleccion de datos por sensor. Pero es mejor evitar repetir el mismo codigo varias veces, porque cuando escribes codigo de esta forma es mas dificil mantener archivos organizados de codigo y tambien es mas facil introducir errors mientras procesas y analizas datos. Cuando necesitas ejecutar el mismo codigo 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 como escribir un bucle con la funcion `lapply()`. +```{r} + +?lapply + +# Crea un vector de los archivos que quieres guardar +files <- c(file.path(path, "Data/RFID", "test1.csv"), file.path(path, "Data/RFID", "test2.csv")) + +files +length(files) + +``` + +Ahora puedes empezar a escribir la estructura de un bucle. En el codigo abajo, el argumento `X` es el numero de veces que se va a ejecutar el bucle. En este caso, `X` es un vector numerico de uno al largo del vector `files` y contiene los numeros uno y dos. Por end, el bucle va a ejecutar dos veces, y cada valor consecutivo en `X` se va a usar en cada iteracion correspondiente del bucle para escribir un archivo a la vez. +El argumento `FUN` es una funcion customizada que fue escrita usando la notacion `function(x){}`. Todo el codigo adentro de las llaves curvas (abre y cierre) se ejecutara en cada iteracion del bucle. El argumento `x` adentro de `funcion()` es el variable de iteracion, o el variable que va a tomar un valor diferente del vector `X` en cada iteracion. +```{r} + +# En este bucle el variable de iteracion x va a tomar cada valor del vector en el argumento X. Por ejemplo, en la primera iteracion del bucle, x va a tomar el valor numerico de 1. En la segunda iteracion del bucle, x va a tomra el valor numerico de 2. Para probar esta logica puedes ejecutar el bucle abajo, y ver el valor de x que va a imprimir en cada iteracion en la consola +lapply(X = 1:length(files), FUN = function(x){ + + x + +}) + +``` + +TKTK continue translation + +As you can see, the output of this loop is a list with 2 elements. Each list element is shown in double square brackets ([[1]] and [[2]]), and each list element contains a vector of length 1 that holds the value of the iterating variable in each iteration (1 and 2, respectively). + +The iterating variable of the loop, or `x`, does not exist as an object outside of the function. So if you print `x` outside of the loop, no object will be found. If you created a object called `x` outside of the loop above, then you will see the contents of that object instead. This means that writing a loop that uses `x` as an iterating variable will not affect other lines of code that use an object called `x` outside of the loop, and vice versa. + +The iterating variable of a function does not always need to be `x`, but can be another letter (`i`, `j`, `y`, `z`) or any combination of multiple letters and numbers, underscores, and periods (as long as the variable name starts with a letter). + +A useful property of the iterating variable is that you can use it to index vectors, data frames, or other objects that you created outside of the loop. For instance, you can use `x` and square bracket indexing to print the name of each file that you want to save: +```{r} + +lapply(X = 1:length(files), FUN = function(x){ + + files[x] + +}) + +``` + +You can also modify the code inside of the custom function to save each of these files, by placing the expression `files[x]` inside of the `write.csv()` function and piping a data frame to `write.csv()`. +```{r eval = FALSE} + +lapply(X = 1:length(files), FUN = function(x){ + + # In each iteration of the loop, you will save the data frame `sim_dats_rfid` as a separate spreadsheet with the file name specified in the given iteration + sim_dats_rfid %>% + write.csv(file = files[x], row.names = FALSE) + +}) + +``` + +You should see 2 "NULL" outputs printed to the console if the loop runs correctly. When you check the contents of the nested RFID folder, you should see that both of the testing .csv files were written out: +```{r} + +list.files(file.path(path, "Data/RFID")) + +``` + +You just wrote out 2 spreadsheets using 1 loop, but you wrote out the same data frame to each spreadsheet. In order to write out a different data frame to each spreadsheet, you can add the data frame filtering that you learned above to the loop. In the code below, you'll also create another vector object called `days`, and then use the iterating variable to filter `sim_dats_rfid` and write out a spreadsheet by each day in `days`. +```{r eval = FALSE} + +days <- c(1, 2, 3) + +lapply(X = 1:length(files), FUN = function(x){ + + sim_dats_rfid %>% + # Filter the data frame by one day at a time + dplyr::filter(day == days[x]) %>% + # Write out data frame filtered by the given data to a separate spreadsheet + write.csv(file = files[x], row.names = FALSE) + +}) + +``` + +You can delete these files that you created for testing. In the code below, you're seeing another pattern searching example while checking whether the function ran correctly. The string that you pass to the argument `pattern` starts with the symbol `^`, which is a symbol that indicates you're searching for all files that *start* with the pattern "test". You're also telling the function to return the full location of each file along with the file name, so that `file.remove()` knows exactly where to look for each file. +```{r eval = FALSE} + +rem_files <- list.files(file.path(path, "Data/RFID"), pattern = "^test", full.names = TRUE) +rem_files + +file.remove(rem_files) + +``` + +

Use a loop to save RFID spreadsheets

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

Write a nested loop to save spreadsheets

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

Simulate 3 days of RFID data collection

-In the code below, you'll use 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: +In the code below, you'll combine the vector of RFID timestamps that you made above with metadata in a data frame. This metadata will include the year, month, and day, as well as a column with 2 unique PIT tag identifiers (1 for each simulated individual), and a column with the sensor type. You should recognize some of this code from the previous vignette: ```{r} # Make a vector for the experimental replicate @@ -234,7 +234,7 @@ Next, you can pipe the data frame to `write.csv()` and specify additional inform 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 the object that is piped in, which here is the data frame `sim_dats_rfid` + #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) ``` @@ -300,17 +300,17 @@ sim_dats_rfid %>% ``` -You can obtain similar results by inverting the conditional statement inside of `dplyr::filter()` to remove days that are **not** the second day of data collection. +You can obtain similar results by inverting the conditional statement inside of `dplyr::filter()` to remove days that are **not** the second or third days of data collection. Below you added two conditional statements together using the symbol "&". ```{r} sim_dats_rfid %>% # Filter the data frame by pulling out all rows in which the day column was not equal to 2 - dplyr::filter(day != 2) %>% + dplyr::filter(day != 2 & day != 3) %>% glimpse() # Check that the filtering was done correctly by getting the unique values in the day column. Looks good sim_dats_rfid %>% - dplyr::filter(day != 2) %>% + dplyr::filter(day != 2 & day != 3) %>% pull(day) %>% unique() @@ -355,7 +355,7 @@ file.remove(rfid_file)

Practicing writing a loop

-In order to write out a data frame for each day of data collection per sensor, you could repeat the code above 4 times (twice 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. +In order to write out a data frame for each day of data collection per sensor, you could repeat the code above 6 times (three times per sensor). But it's good to avoid repeating the same code over and over, since this can lead to messy scripts as well as a greater risk for errors in data processing and analysis. Whenever you need to run the same code many times, you can write a loop. Loops are an important coding skill and we'll work through building a loop step by step. To start, you can practice building a loop with the function `lapply()`. ```{r} @@ -370,12 +370,12 @@ length(files) ``` -Next, you can start writing out a looping structure. In the code below, the argument `X` specifies the number of times that the loop will run. In this case, `X` is a numeric vector from 1 to the length of the files vector and contains the numbers 1 and 2. Therefore the loop will run twice, and each value in `X` will be used in each loop iteration to write out one file at a time +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 in each iteration of the loop. +The argument `FUN` is a custom function, written using the `function(x){}` notation. All of the code inside of the curly brackets `{}` will be run for each iteration of the loop. The argument `x` inside `function()` is the iterating variable, or the variable that will take on a different value of the vector supplied to `X` in each iteration of the loop. ```{r} -# In this loop, the iterating variable `x` will take on each value in the vector supplied to the `X` argument. This means that in the first loop iteration, `x` will take on the numeric value of 1. In the second loop iteration, `x` will hold the numeric value 2. To test that this is true, you can run the loop below, which will print the value of x per iteration to the console +# 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 From 7f3c70de36ecb2ff07008c81fd34a4b86eb79398 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Mon, 1 Apr 2024 10:56:25 -0400 Subject: [PATCH 45/69] [PCT-472]: Finished rough translation of fourth vignette --- R/vignettes/Tutorial_04_GuardarDatos.Rmd | 154 +++++++++--------- R/vignettes/Vignette_02_Configuracion.Rmd | 190 ++++++++++++++++++++++ R/vignettes/Vignette_04_SaveData.Rmd | 27 +-- 3 files changed, 280 insertions(+), 91 deletions(-) create mode 100644 R/vignettes/Vignette_02_Configuracion.Rmd diff --git a/R/vignettes/Tutorial_04_GuardarDatos.Rmd b/R/vignettes/Tutorial_04_GuardarDatos.Rmd index 8ecef78..045fa59 100644 --- a/R/vignettes/Tutorial_04_GuardarDatos.Rmd +++ b/R/vignettes/Tutorial_04_GuardarDatos.Rmd @@ -387,15 +387,13 @@ lapply(X = 1:length(files), FUN = function(x){ ``` -TKTK continue translation +Como puedes ver, el resultado de este bucle es una lista con dos elementos. Cada elemento de la lista esta rodeado de dos pares de corchetes ([[1]] and [[2]]) y contiene un vector con un largo de uno que contiene el valor del variable de iteracion (uno y dos, respectivamente). -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). +El variable de iteracion, o `x`, no existe como un objetp afuera de la funcion del bucle. Si imprimes `x` afuera del bucle, no se va a encontrar ese objeto. Si creaste un objeto `x` afuera del bucle arria, veras los contenidos de este objeto cuando imprimes `x`. O sea, escribir un bucle que usa `x` como el variable de iteracion no afectara otras lineas de codigo que usan un objeto que se llama `x` afuera de la funcion, y viceversa. -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. +El variable de iteracion de una funcion puede ser otras letras del alfabeto como `i`, `j`, `y`, `z`, o una combinacion de multiples letras, numeros, guiones bajo, o periodos, siempre y cuando el nombre de la variable empieza con una letra. -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: +Una propiedad util del variable de iteracion 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: ```{r} lapply(X = 1:length(files), FUN = function(x){ @@ -406,12 +404,12 @@ lapply(X = 1:length(files), FUN = function(x){ ``` -You can also modify the code inside of the custom function to save each of these files, by placing the expression `files[x]` inside of the `write.csv()` function and piping a data frame to `write.csv()`. +Tambien puedes modificar el codigo adentro de la funcion custoimizada para guardar cada archivo si metes la expresion `files[x]` adentro de la funcion `write.csv()` y usas una operacion de `piping` para usar un `dataframe` como entrada para `write.csv()`. ```{r eval = FALSE} lapply(X = 1:length(files), FUN = function(x){ - # In each iteration of the loop, you will save the data frame `sim_dats_rfid` as a separate spreadsheet with the file name specified in the given iteration + # En cada iteracion del bucle, vas a guardar el dataframe `files[x]` en una hoja de calculo con el nombre de archivo de la iteracion actual sim_dats_rfid %>% write.csv(file = files[x], row.names = FALSE) @@ -419,14 +417,14 @@ lapply(X = 1:length(files), FUN = function(x){ ``` -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: +Deberias 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 ubicacion: ```{r} list.files(file.path(path, "Data/RFID")) ``` -You just wrote out 2 spreadsheets using 1 loop, but you wrote out the same data frame to each spreadsheet. In order to write out a different data frame to each spreadsheet, you can add the data frame filtering that you learned above to the loop. In the code below, you'll also create another vector object called `days`, and then use the iterating variable to filter `sim_dats_rfid` and write out a spreadsheet by each day in `days`. +Acabas de escribir dos hojas de calculo usando un bucle, pero escribistes el mismo `dataframe` a cada hoja de calculo. Para poder escribir un `dataframe` diferente a cada hoja de calculo, puedes anadir el paso de filtrar el `dataframe`que aprendiste arriba. En el codigo abajo, tambien vas a crear otro objecto de vector que se llama `days` (dias) y luego usaras el variable de iteracion para filtrar `sim_dats_rfid` y escribir una hoja de calculo por cada dia en `days`. ```{r eval = FALSE} days <- c(1, 2, 3) @@ -434,16 +432,16 @@ 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 + # Filtra el dataframe una dia a la vez dplyr::filter(day == days[x]) %>% - # Write out data frame filtered by the given data to a separate spreadsheet + # Escribe el dataframe filtrado por el dia actual a una hoja de calculo para ese dia 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. +Puedes borrar estos archivos que creaste de prueba. En el codigo abajo, vas a ver otro ejemplo de buscar un patron 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 simbolo de `^`, el cual significa que quieres buscar todos los archivos que *empiezan* con el patron "test". Tambien esta especificandp que quieres devolver la ubicacion (`path`) completa de cada archivo usando el argumento `full.names = TRUE`, para que la funcion `file.remove()` tenga toda la informacion que necesita para borrar estos archivos de prueba. ```{r eval = FALSE} rem_files <- list.files(file.path(path, "Data/RFID"), pattern = "^test", full.names = TRUE) @@ -453,54 +451,53 @@ file.remove(rem_files) ``` -

Use a loop to save RFID spreadsheets

+

Usa un bucle para guardar la hoja de calculo de RFID

-Now you can put all of these pieces together and use the loop to write out a spreadsheet per day for the RFID sensor. +Ahora puedes juntar todo lo que aprendiste arriba y usar el bucle para escribir una hoja de calculo por dia para el sensor de RFID. ```{r eval = FALSE} -# Make a vector of the custom file names to write out +# 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" ) -# Add the file path for the correct directory +# Anade el path para la ubicacion o carpeta al nombre de cada archivo files <- file.path(path, "Data/RFID", files) files -# Make a vector of the days to write out (1 day per iteration of the loop) +# Inicializa un vector de los dias para poder escribir una hoja de calculo por dia en cada iteracion del bucle 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 +# Puedes eliminar los nombres de los argumentos de lapply() porque estas especificando los valores de los argumentos en el orden que la funcion espera por defecto invisible(lapply(1:length(files), function(x){ sim_dats_rfid %>% - # Filter the data frame by one day at a time + # Filtra el dataframe por el dia actual en esta iteracion dplyr::filter(day == days[x]) %>% - # Write out the filtered data frame to the correct spreadsheet for the given day + # Escribe el dataframe filtrado a la hoja de calculo correcta para el dia actual 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. - +En el codio arriba, deberias de poder ver un cambio adicional que hicimos al bucle con rodearlo con la funcion `invisible()`. Esta funcion silencia al resultado que se imprima a la consola (que viste cuando ejecutaste trozos de codigo arriba), en que el resultado de cada iteracion de `lapply()` esta rodeado de dos pares de corchetes, y luego un solo par de corchetes. `lapply()` es una funcion que devuelve una lista, y cuando la funcion se ejecuta correctamenta pero no hay resultados para imprimir (como cuando creas un archivo fisico), la funcion deberia de devolver valores de `NULL` que significan resultados vacios. Este comportamiento se espera con nuestro uso de `lapply()` porque usamos la funcion para escribir archivos fisicos y no para devolver resultados importantes a la consola. Ya que puedes usar `list.files()` para revisar que `lapply()` se ejecuto bien, usar `invisible()` te ayudara minimizar la cantidad de texto que tienes que revisar en tu consola. ```{r eval = FALSE} -# The new .csv files for each day of RFID data are present, looks good +# Los archivos nuevos de .csv para cada dia de datos de RFID estan presentes en el directorio esperado, se ve bien 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. +Ahora puedes eliminar los archivos que acabas de escribir, porque vas a trabajar en escribir un bucle anidado que va a automaticamente escribir los datos de cada dia para cada tipo de sensor. ```{r eval = FALSE} files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv") -# Add the file path for the correct directory +# Anade el path para el directorio correcto a los nombres de los archivos que quieres borrar files <- file.path(path, "Data/RFID", files) files @@ -508,21 +505,21 @@ 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. +Si quieres mas pratcica escribiendo bucles, puedes escribir un bucle para guardar una hoja de calculo para cada dia de coleccion de datos para los sensores infrarrojo. -

Write a nested loop to save spreadsheets

+

Escribe un bucle anidado para crear hojas de calculo

-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: +Si quieres minimizar la cantidad de codigo que escribes para guardar los datos por tipo de sensor y por dia, puedes guardar archvos de ambos tipos de sensores (RFID y sensores infrarrojos) en el mismo bucle. Para continuar, deberias de crear otro directorio para los datos de los sensores infrarrojos: ```{r eval = FALSE} dir.create(file.path(path, "Data", "IRBB")) ``` -To carry out the file filtering and writing for both sensor types across days, you'll use a type of object called a `list`. You will then use these lists inside of a nested loop structure: +Para lograr filtrar y escribir datos para ambos tipos de sensores a traves de los dias de coleccion de datos, vas a usar un tipo de objeto en R que se llama un `list`. Vas a usar estos lists adentro del bucle anidado: ```{r} -# Make a list of the custom file names to write out for each sensor type and day +# Crea una lista de los nombres de archivos customizados para guardar datos para cada tipo de sensor y dia 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") @@ -532,25 +529,25 @@ glimpse(files) ``` -Lists are useful objects because they're very flexible. Unlike vectors, a single list can hold many different types of data. And then unlike a data frame, the elements of a list do not need to have the same dimensions. The elements of a list can also be different data structures or object types themselves. For instance, lists can contain vectors, data frames, and other lists all inside of the same larger list object. For instance, 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()`. +Los `lists` son objetos utiles porque son muy flexibles. A diferencia de vectores, una sola lista puede contener varios diferentes tipos de datos. A diferecia de un `dataframe`, los elementos de una lista no necesitan tener las mismas dimensiones. Los elementors de una lista tambien 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 funcion `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: +Puedes indexar un `list` de una forma parecida a indexar vectores y `dataframes`, pero usar un par o dos pares de corchetes devuelve resultados diferentes: ```{r} -# Using a single square bracket to filter a list returns the first list element in list format +# Usar un par de corchetes para filtrar un list devuelve el elemento actual en formato de list files[1] glimpse(files[1]) -# Using double square brackets returns the first list element only, so it removes the list structure and shows the original data structure of that element (here a vector) +# Usar dos pares de corchetes devuelvo solo el elemento actual, o sea elimina el estructura de list para demostrar el estructura original de ese elemento (aqui este elemento es un vector) files[[1]] glimpse(files[[1]]) ``` -Lists can also have named elements, which makes it possible to access elements by name. +Un `list` tambien puede tener nombres para sus elementos, y los elementos se pueden acceder por nombre: ```{r} -# Make a named list of the custom file names to write out +# 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") @@ -560,28 +557,28 @@ glimpse(files) ``` -Access the list elements by name: +Accede los elementos de este `list` por nombre: ```{r} -# Using a single bracket returns the first element as a list, called "RFID" +# Usar un par de corchetes devuelve el elemento "RFID" como una lista files["RFID"] -# Using the dollar sign or double square brackets returns the first list element in its original format +# Usar el simbolo de dolar "$" o dos pares de corchetes devuelve el elemento "RFID" en su formato original files$RFID files[["RFID"]] ``` -Lists are very useful data structures for setting up nested loop operations. For instance, if you want to write out a spreadsheet per day per sensor type, you need a loop to 1) iterate over sensor types, and then a loop to 2) 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): +Los `lists` con tipos de objetos muy utiles para operaciones con bucles anidados. Por ejemplo, si quieres escribir una hoja de calculo por tipo de sensor y por dia, vas a necesitar 1) un bucle para iterar a traves de los tipos de sensors y 2) un bucle para iterar a traves de dias de coleccion de datos para cada sensor. Puedes usar listas para crear estructuras anidadas de datos que puedes proveer a un bucle anidado, para aseguar 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 estan ordernados primero por el tipo de sensor (cada elemento del `list`) y luego por dia de coleccion de datos (cada elemento del vector adentro de cada elemento del `list`): ```{r} -# Make a vector of sensor labels +# Crea un vector de los nombres de los sensores sensors <- c("RFID", "IRBB") sensors -# Make a named list of the custom file names to write out +# 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") @@ -589,7 +586,7 @@ files <- list( files -# Make a list of file paths per sensor that will be used inside of the loop +# Crea un list de los paths para los archivos de cada sensor. Usaras estos paths adentro de los bucles file_dirs <- list( `RFID` = file.path(path, "Data/RFID"), `IRBB` = file.path(path, "Data/IRBB") @@ -597,8 +594,8 @@ file_dirs <- list( file_dirs -# Make a list of the days to write out for each sensor -# This could be a single vector rather than a list since you want to write out the same days per sensor, but a list is useful in case you wanted to change the days (and number of days) to write out per sensor +# Crea un list de los dias de coleccion de datos para cada tipo de sensor +# Esto puede ser un solo vector en vez de una lista porque quieres guardar el mismo numero de dias por sensor, pero una lista es util por si quieres cambiar los dias mismos o el numero de dias que quieres guardar por sensor days <- list( `RFID` = c(1, 2, 3), `IRBB` = c(1, 2, 3) @@ -606,7 +603,7 @@ days <- list( days -# Finally, you need to make a list of the data frames that you want to write out. Since you already know how to filter data frames by day, that code can go inside of the loop. Here you can specify the data frame to use in the filtering operation per sensor type +# Ahora necesitas crear un list de los dataframes que quieres usar para crear los archivos. Como ya sabes como filtrar dataframes por dia, ese codigo puede ir adentro de los bucles para minimizar la cantidad de codigo que escribes. Aqui vas a especificar el dataframe que usaras en las operaciones de filtrar para cada tipo de sensor dats <- list( `RFID` = sim_dats_rfid, `IRBB` = sim_dats_irbb @@ -616,74 +613,75 @@ glimpse(dats) ``` -Once you've set up the data structures that you want to loop over, you can write out the nested loop itself. Since this is a complex loop structure, it can be helpful to test the loop with set values of each iterating variable (below these will be `x` and `y`). +Cuando hayas establecido las estructuras de datos para informar la operacion del bucle, puedes escribir el bucle anidado mismo. Este bucle anidado es una estructra compleja, y por ende es util probar el bucle con valores determinados de cada variable de iteracion (`x` y `y` abajo). -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). +Despues de escribir este bucle pero antes de ejecutar la estructura completa del bucle, deberias de probar el codigo adentro de cada capa del bucle. Para lograr esto, puedes inicializar los valores de los variables de iteracion y luego correr el codigo 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 codigo para una sola iteracion (abajo vas a ver la primera iteracion para cada capa del bucle cuando ambos `x` y `y` tiene el valor numerico de uno). -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. +Para lograr este tipo de chequeo, deberias de ejecutar el codigo para "congelar" los variables de iteracion en la primera iteracion de cada bucle ( o sea, inicializar `x` y `y` con el valor de uno). Luego deberias de ejecutar el codigo adentro de cada bucle, empezando con la creacion de `days_tmp`, luego las operaciones de indexar y filtrar el `dataframe`, y luego filtrar los nombres de los archivos. No deberias de ejecutar las lineas con `lapply()` porque quieres evitar ejecutar los bucles completos hasta que estes segura que el codigo adentro de cada bucle funciona de la forma que esperas. -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: +*Nota importante*: Arriba aprendiste que los variables de iteracion no existen afuera de un bucle. Esta forma de probar el codigo adentro del bucle, en que no estas ejecutando los bucles mismos, es equivalente a probar el codigo afuera del bucle y por ende, los valores de los variables de iteracion que inicializes afuera del bucle se van a respetar. + +Mientras revisas el codigo adentro de cada bucle, deberias de ver que entre el bucle exterior y el bucle interior, vas a usar el nombre del sensor para la primera iteracion del bucle exterior ("RFID") para determinar los dias 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 dias que quieres por sensor. Luego vas a indexar el nombre del archivo para el tipo de sensor actual y el dia actual con una combinacion de indexar la lista de nombres de los archivos con uno o dos pares de corchetes. Abajo, las lineas que abren y cierrn los bucles mismos estan comentados para guiar tu chequeo (o sea para guiar cuales lineas de codigo deberias de ejecutar): ```{r eval = FALSE} -# Testing +# Congela las variables de iteracion para el chequeo x <- 1 y <- 1 -# Start by iterating over sensors -invisible(lapply(1:length(sensors), function(x){ +# El bucle exterior: empieza con iterar a traves de los sensores +# 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 + # Para obtener los dias correctos para el tipo de sensor actual, deberias de indexar el list nombrado de dias + # Este paso de indexar es importante para que el bucle interior ejecute correctamente + sensors[x] # Una secuencia de caracteres con el nombre del sensor - 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 + # Coloca la secuencia de caracteres con el nombre del sensor adentro de dos pares de corchetes para extraer el vector de dias para el tipo de sensor actual days_tmp <- days[[sensors[x]]] days_tmp - # For each sensor, iterate over days - lapply(1:length(days_tmp), function(y){ + # El bucle interior: itera a traves de dias para cada tipo de sensor + # 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 + # Para obtener el dataframe para el tipo de sensor actual, puedes usar x adentro de dos pares de corchetes para extraer el data frame del 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) + # Para filtrar el data frame por el dia actual puedes usar y para indexar el vector temporal de dias (para extraer un solo elemento de este 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 + # Usa dos pares de corchetes para acceder el vector de los nombres de los archivos para el tipo de sensor actual que esta adentro de la lista. Luego usa el variable y para indexar este vector con un par de corchetes para acceder el nombre de archivo correcto para esta iteracion files[[x]] files[[x]][y] - # You'll also combine the file name with the right path: + # Tambien vas a combinar el nombre del archivo con el path correcto: 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: +Ahora deberias de tener una mejor idea sobre como cada bucle opera a traves de diferentes estructuras de datos para realizar la tarea que quieres (en este caso, escribir una hola de calculo por tipo de sensor y dia). Luego puedes modificar la estructura entera de los bucles para reemplazar las lineas que escribiste para eln chequeo con las operaciones finales que quieres realizar: ```{r eval = FALSE} -# Start by iterating over sensors +# El bucle exterior: empieza con iterar a traves de los sensores 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 + # Para obtener los dias correctos para el tipo de sensor actual, deberias de indexar el list nombrado de dias + # Este paso de indexar es importante para que el bucle interior ejecute correctamente days_tmp <- days[[sensors[x]]] - # For the given sensor, iterate over days of data collection to write out a spreadsheet per sensor and day + # El bucle interior: itera a traves de dias para cada tipo de sensor para escribir una hoja de calculo por tipo de sensor y dia lapply(1:length(days_tmp), function(y){ - # Get the data frame per sensor type using x and then filter by day using y + # Usa el variable x para acceder el dataframe para el tipo de sensor actual y luego usar el variable y para filtrar este dataframe por el dia actual dats[[x]] %>% - # Filter the data frame by one day at a time + # Filtra el dataframe por el dia actual 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 + # Usa una operacion 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 dia actual write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) }) @@ -692,7 +690,7 @@ invisible(lapply(1:length(sensors), function(x){ ``` -This loop should have created 1 file per sensor and day in the correct directory per sensor. Check that these 4 files now exist inside each directory per sensor within your working directory. +Esta estructura de bucle deberia de haber creado un archivo por tipo de sensor y dia en el directorio correcto por tipo de sensor. Puedes revisar que estos seis archivos existen adentro del directorio para cada tipo de sensor en tu directorio de trabajo: ```{r eval = FALSE} list.files(file.path(path, "Data/RFID")) @@ -701,4 +699,4 @@ list.files(file.path(path, "Data/IRBB")) ``` -You learned more about filtering data frames, writing them out to spreadsheets, and writing single-layer and nested loops in this vignette. In the next vignette, you'll use the spreadsheets of simulated RFID and IRBB data to start the ABISSMAL data processing and analysis workflow. \ No newline at end of file +En este tutorial aprendiste mas sobre como filtrar `dataframes` y guardar estos objetos como hojas de calculo, y como usar bucles de una capa y estructuras de bucles anidados. En el siguiente tutorial vas a usar las hojas de calculo que datos simulados de RFID y sensores infrarrojos para empezar a procesar y analizar datos con las funciones de ABISSMAL. \ No newline at end of file diff --git a/R/vignettes/Vignette_02_Configuracion.Rmd b/R/vignettes/Vignette_02_Configuracion.Rmd new file mode 100644 index 0000000..8e8b0e6 --- /dev/null +++ b/R/vignettes/Vignette_02_Configuracion.Rmd @@ -0,0 +1,190 @@ +--- +title: "Vignette 02: Configuracion" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = FALSE) + +``` + +

Resumen del tutorial y objetivos de aprendizaje

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

Using RMarkdown files

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

Clean your global environment

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

Access R function documentation

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

Install and load packages

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

Coding shortcuts

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

Troubleshooting incomplete statements

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

Create your working directory

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

Información sobre esta traducción

+ +Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las [convenciones de traduccion de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el genero femenino por defecto. Si encuentran errores de ortografía que impiden su habilidad de completar estos tutoriales, por favor reporten los errores de ortografía a GitHub usando los pasos arriba para reportar un "Issue" con los tutoriales. \ No newline at end of file diff --git a/R/vignettes/Vignette_04_SaveData.Rmd b/R/vignettes/Vignette_04_SaveData.Rmd index 98e541d..cbf4c6d 100644 --- a/R/vignettes/Vignette_04_SaveData.Rmd +++ b/R/vignettes/Vignette_04_SaveData.Rmd @@ -431,7 +431,7 @@ 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 data to a separate spreadsheet + # Write out data frame filtered by the given day to a separate spreadsheet write.csv(file = files[x], row.names = FALSE) }) @@ -527,7 +527,7 @@ glimpse(files) ``` -Lists are useful objects because they're very flexible. Unlike vectors, a single list can hold many different types of data. And then unlike a data frame, the elements of a list do not need to have the same dimensions. The elements of a list can also be different data structures or object types themselves. For instance, lists can contain vectors, data frames, and other lists all inside of the same larger list object. For instance, 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()`. +Lists are useful objects because they're very flexible. Unlike vectors, a single list can hold many different types of data. And then unlike a data frame, the elements of a list do not need to have the same dimensions. The elements of a list can also be different data structures or object types themselves. For instance, lists can contain vectors, data frames, and other lists all inside of the same larger list object. The list that you created above has 2 elements, and each element is a vector of 3 character strings containing the file names that you supplied using the function `c()`. You can index lists in a manner that is similar to indexing vectors and data frames, but indexing lists can be done with single or double square brackets for different outcomes: ```{r} @@ -568,7 +568,7 @@ files[["RFID"]] ``` -Lists are very useful data structures for setting up nested loop operations. For instance, if you want to write out a spreadsheet per day per sensor type, you need a loop to 1) iterate over sensor types, and then a loop to 2) 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): +Lists are very useful data structures for setting up nested loop operations. For instance, if you want to write out a spreadsheet per day per sensor type, you need 1) a loop to iterate over sensor types, and then 2) a loop to iterate over days per sensor. You can use lists to create nested data structures to supply to a nested loop, which will help you make sure that each layer of the loop runs in the way that you expect. For instance, the list called `files` reflects the nested loop that you need because files are listed first by sensor type (each element of the list) and then by date (each element of the vector inside of each list element): ```{r} # Make a vector of sensor labels @@ -617,19 +617,20 @@ After writing out this loop but before running the full loop structure, you shou 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. -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: +*Important note*: Above you learned that the iterating variables in a loop do not exist outside of the loop. In this testing that you will cary out, you're not fully running either loop, and so the code that you're testing inside of each loop will "see" the values of iterating variables that you initialized outside of the loops. + +As you test the code inside of each loop, you should see that between the outer and inner loops, you're using the name of the sensor for the given outer loop iteration ("RFID" for the first iteration) to set up the days over which the inner loop will iterate (in `days_tmp`). Inside of the inner loop, you're filtering the list of data frames by sensor, and then by the days that you want per sensor. Finally, you're indexing the file name for the right sensor type and day with a combination of double and single bracket filtering on the list of file names. Below, the lines of code that open and close each loop are commented out in order to guide you through which lines of code you should run in this testing step: ```{r eval = FALSE} -# Testing +# Freezer the iterating variables for testing x <- 1 y <- 1 -# Start by iterating over sensors -invisible(lapply(1:length(sensors), function(x){ +# 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 @@ -637,8 +638,8 @@ invisible(lapply(1:length(sensors), function(x){ days_tmp - # For each sensor, iterate over days - lapply(1:length(days_tmp), function(y){ + # 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]] %>% @@ -654,9 +655,9 @@ invisible(lapply(1:length(sensors), function(x){ # You'll also combine the file name with the right path: file.path(file_dirs[[x]], files[[x]][y]) - }) + # }) -})) +# })) ``` @@ -687,7 +688,7 @@ invisible(lapply(1:length(sensors), function(x){ ``` -This loop should have created 1 file per sensor and day in the correct directory per sensor. Check that these 4 files now exist inside each directory per sensor within your working directory. +This loop should have created 1 file per sensor and day in the correct directory per sensor. Check that these 6 files now exist inside each directory per sensor within your working directory. ```{r eval = FALSE} list.files(file.path(path, "Data/RFID")) From e02fe33a337d3be353ffe1708295f58fb840dc6c Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Mon, 1 Apr 2024 10:58:13 -0400 Subject: [PATCH 46/69] [PCT-472]: deleted file copy --- R/vignettes/Vignette_02_Configuracion.Rmd | 190 ---------------------- 1 file changed, 190 deletions(-) delete mode 100644 R/vignettes/Vignette_02_Configuracion.Rmd diff --git a/R/vignettes/Vignette_02_Configuracion.Rmd b/R/vignettes/Vignette_02_Configuracion.Rmd deleted file mode 100644 index 8e8b0e6..0000000 --- a/R/vignettes/Vignette_02_Configuracion.Rmd +++ /dev/null @@ -1,190 +0,0 @@ ---- -title: "Vignette 02: Configuracion" -author: "Grace Smith-Vidaurre" -date: "2023-12-27" -output: - html_document: - css: "styles.css" - toc: true - toc_float: true - toc_depth: 4 ---- - -```{r setup, include = FALSE} - -knitr::opts_chunk$set(echo = TRUE, eval = FALSE) - -``` - -

Resumen del tutorial y objetivos de aprendizaje

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

Using RMarkdown files

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

Clean your global environment

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

Access R function documentation

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

Install and load packages

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

Coding shortcuts

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

Troubleshooting incomplete statements

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

Create your working directory

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

Información sobre esta traducción

- -Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las [convenciones de traduccion de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el genero femenino por defecto. Si encuentran errores de ortografía que impiden su habilidad de completar estos tutoriales, por favor reporten los errores de ortografía a GitHub usando los pasos arriba para reportar un "Issue" con los tutoriales. \ No newline at end of file From 9d54e039d1982df9088ad7b485dfc7e2117d2158 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Mon, 1 Apr 2024 17:39:36 -0400 Subject: [PATCH 47/69] [PCT-472]: Started rough translation of fifth vignette --- R/vignettes/Tutorial_04_GuardarDatos.Rmd | 5 +- ...utorial_05_ProcesarDatos_CrearGraficas.Rmd | 503 ++++++++++++++++++ .../Vignette_05_ProcessData_BuildPlots.Rmd | 8 +- 3 files changed, 510 insertions(+), 6 deletions(-) create mode 100644 R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd diff --git a/R/vignettes/Tutorial_04_GuardarDatos.Rmd b/R/vignettes/Tutorial_04_GuardarDatos.Rmd index 045fa59..8815d5a 100644 --- a/R/vignettes/Tutorial_04_GuardarDatos.Rmd +++ b/R/vignettes/Tutorial_04_GuardarDatos.Rmd @@ -34,9 +34,9 @@ En este cuarto tutorial, vas a guardar hojas de calculo de las detecciones simul ```{r message = FALSE, warning = FALSE} -rm(list = ls()) # Limpiar tu ambiente global +rm(list = ls()) # Limpia tu ambiente global -library(tidyverse) # Cargar la coleccion de paquetes del tidyverse +library(tidyverse) # Carga la coleccion de paquetes del tidyverse path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Inicializar un objeto con el path de tu directorio de trabajo @@ -57,6 +57,7 @@ glimpse(rfid_ts) ``` +Aqui "IRBB" significa "infrared beam breakers" o sensores de infrarrojo. ```{r} # Simula marcas de tiempo para los pares externos ("o_") e internos ("i_") de sensores infrarrojos para una entrada y una salida, y luego otra entrada y salida diff --git a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd new file mode 100644 index 0000000..dd603bc --- /dev/null +++ b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd @@ -0,0 +1,503 @@ +--- +title: "Tutorial 04 05: Procesar Datos y Crear Graficas" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = TRUE) + +``` + +

Información sobre esta traducción

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

Resumen del tutorial y objetivos de aprendizaje

+ +En este quinto tutorial, vas a empezar a usar las detecciones simuladas de movimientos de animales en el flujo de analisis de datos de ABISSMAL, incluyendo combinar los datos originales a traves de dias y procesar o limpiar los datos originales. Tambien vas a crear graficas de los datos procesados. Vas a continuar a usar habilidades que aprendiste en los tutoriales anteriores, y vas a aprender nuevas habilidades que incluyen: + +1. Acceder funciones customizadas +2. Usar funciones customizadas +3. Crear graficas con `ggplot` + +

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

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

Cargar las funciones de ABISSMAL

+ +Las funciones customizadas de R en ABISSMAL estan guardados en archivos fisicos (extension .R) adentro de la version 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 fisicos de R para que las funciones esten disponibles en tu ambiente global. En el codigo abajo, vas a usar la funcion `source()` para cargar tres de las cinco funciones primarias de ABISSMAL, y tambien un archivo que contiene funciones de apoyo: +```{r} + +# Carga la funcion que combina los datos originales +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/combine_raw_data.R") + +# Carga la funcion que detecta eventos de posa en los datos originales +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_perching_events.R") + +# Carga la funcion que procesa los datos originales +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/preprocess_detections.R") + +# Carga un archvio con funciones de apoyo que cada funcion arriba require +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") + +``` + +

Accede informacion sobre las funciones de ABISSMAL

+ +Despues de ejecutar las lineas de codigo arriba, deberias de ver que una coleccion entera de funciones estan disponibles en tu ambiente global (revisa la pestana de `Environment`). Las funciones que empiezan con `check_` son funciones de apoyo. Si haces `scroll` para abajo, puedes ver que tres de las funciones primarias de ABISSMAL (`combine_raw_data`, `detect_perching_events`, `preprocess_detections`) tambien estan disponsibles en tu ambiente global. En la columna al lado derecho de los nombres de las funciones tambien podras ver algo de informacion sobre los argumentos de cada funcion. + +Para obtener mas informacion sobre cada una de las tres funciones primarias, puedes hacer clic en el icono blanco cuadrado a la mera derecha de cada funcion en la pestana de `Environment`, o ejecutar el codigo `View(nombre_de_la_funcion)`. Este comando deberia de abrir el archivo de la funcion actual en una pestana nueva adentro de tu panel de fuente. En el archico de cada funcion, vas ver lineas de documentacion que empiezan con los simbolos "`#@", luego el nombre de la funcion y una descripcion, y luego una descripcion de cada argumento (paramtero) para la funcion. Si haces scroll para abajo, podras ver una seccion con detalles sobre la funcion misma, incluyendo la informacion que devuelve. Esta documentacion esta escrita en ingles por el momento. Despues de las lineas de documentacion veras el codigo de la funcion misma. + +

Combinar los datos originales

+ +Cuando hayas cargado las funciones de ABISSMAL, podras usar la primera funcion, `combine_raw_data()`, para combinar los datos colectados a traves de dias y tipos de sensors en una sola hoja de calculo por sensor. Vas a empezar con combinar los datos originales para el sensor de RFID que fueron colectados a traves de dias diferentes en una hoja de calculo para este sensor. + +Vas a proveerle informacion a la funcion `combine_raw_data()` a traves 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 calculo de los datos originales combinados. La funcion creara 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 seccion de "Time zones" en la documentacion para `DateTimeClasses` en R para mas informacion (`?DateTimeClasses`) + +* `POSIXct_format` es una secuencia de caracteres que contiene la informacion del formato `POSIXct` para combinar fechas y marcas de tiempo en una sola columna. Por defecto la funcion devolvera el año como un numero con cuatro digitos y el mes y el dia como numeros con dos digitos, separados por guiones. La fecha y el tiempo estaran separados por un espacio, y la hora, el minuto, y el segundo (en decimales), todos con dos digitos, estaran separados por dos puntos +```{r} + +# Combina los datos originales para los sensores de RFID e infrarrojo en procesos separados +combine_raw_data(sensors = "RFID", path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +``` + +Puedes revisar que `combine_raw_data()` guardo una hoja de calculo con los datos originales combinados de RFID al directorio nuevo `raw_combined`: +```{r} + +list.files(file.path(path, "Data/raw_combined"), pattern = ".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 calculo: +```{r} + +rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) + +glimpse(rfid_data) + +``` + +Leer estos datos a R creo un objeto `dataframe`. Deberias de poder ver que hay unas columnas nuevas creadas por la funcion, como la columna `data_type`. Para esta hoja de calculo, las columnas `sensor_id` y `data_type` contienen la misma informacion, pero es util tener columnas separadas para poder estar al tanto de la identidad unica del sensor y el tipo de sensor cuando usas multiples sensores del mismo tipo (por ejemplo, dos pares de sensores infrarrojos tendran numeros de identidad unicos en la columna de `senso_id`). + +La funcion `combine_raw_data()` tambien creo 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 funcion anadio 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, veras que las hojas de calculo originales por dia se preservaron y no fueron ni eliminados ni sobrescritos. + +Tambien puedes ejecutar `combine_raw_data()` con los datos originales de multiples sensores a la vez con proveer un vector con las etiquetas de estos sensores al argumento `sensores`. Los datos para cada tipo de sensor todavia se guardaran en hojas de calculo separados, y evitas tener que escribir el mismo codigo varias veces para ejecutar `combine_raw_data()` para multiples sensores: +```{r} + +combine_raw_data(sensors = c("RFID", "IRBB"), path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +``` + +Los datos de RFID se sobrescribiran, y deberias de ver una hoja de calculo adicional con los datos originales de los sensores infrarrojos: +```{r} + +list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") + +``` + +

Detectar eventos de posar

+ +Puedes usar los datos originales combinados de sensores diferentes en las siguientes funciones de ABISSMAL para empezar a hacer inferencias de comportamiento de los datos de deteccion de movimiento. Por ejemplo, puedes detectar eventos de posar en los datos originales de RFID con la funcion `detect_perching_events()`. Puedes leer mas sobre cada argumento en el archivo de R que contiene esta funcion. +```{r} + +detect_perching_events(file_nm = "combined_raw_data_RFID.csv", threshold = 2, run_length = 2, sensor_id_col_nm = "sensor_id", timestamps_col_nm = "timestamp_ms", PIT_tag_col_nm = "PIT_tag", rfid_label = "RFID", general_metadata_cols = c("chamber_id", "sensor_id"), path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", out_file_prefix = "perching_events", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +``` + +`detect_perching_events()` puede operar en solo un archivo y tipo de sensor a la vez. La funcion automaticamente crea una carpeta que se llama "prcoessed" (para datos procesados) y guardara un archivo de .csv adentro de esa carpeta si pudo detectar eventos de posar usando el umbral temporal actual (`threshold`, en segundos), y la duracion de secuencias de deteccion actual (`run_length`, en numero de detecciones). + +Cuando creamos datos simulados en los ultimos tutoriales, simulaste eventos de posar en los datos de RFID. Pudiste recuperar estos eventos de posar usando `detect_perching_events()`? +```{r} + +perching <- read.csv(file.path(path, "Data", "processed", "perching_events_RFID.csv")) + +glimpse(perching) + +``` + +`detect_perching_events()` identifico un total de seis eventos de posar, que es el mismo numero que simulaste en el tutorial anterior (dos eventos de posar por dia a traves de tres dias). Puedes revisar los valores adentro del `dataframe` para ver mas informacion sobre estos eventos de posar: +```{r} + +# Las marcas de tiempo cuando cada evento de posar empezo +perching$perching_start + +# Las marcas de tiempo cuando cada evento de posar termino +perching$perching_end + +# La etiqueta unica de PIT que contiene informacion sobre la identidad del individuo que estuvo posando en la antena de RFID +perching$PIT_tag + +``` + +Tambien puedes visualizar el `dataframe` entero en un panel separado: +```{r eval = FALSE} + +View(perching) + +``` + +La informacion arriba te dice que hubo dos eventos de posar a las 8:00 cada dia, y dos eventos de posar a las 11:30 cada dia (como esperamos). La etiqueta de PIT para cada individuo se detecto una vez por dia, por ende, cada individuo realizo un evento de posar cada dia. + +Detectar eventos de posar no es un requisito en el flujo de analisis de ABISSMAl, pero puede ser un paso util para obtener la mayor cantidad de informacion que puedes de los datos originales antes de filtrar detecciones en el siguiente paso de procesar o limpiar los datos originales. + +TKTK continue translation + +

Pre-process raw data

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

Plot RFID data in a barcode plot

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

Load packages and your working directory path

@@ -40,7 +40,7 @@ path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes"

Load ABISSMAL functions

-The custom R functions available through the ABISSMAL GitHub repository 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: +The custom R functions available through ABISSMAL are stored in physical files (extension .R) inside the local repository on your computer (which you should have downloaded in vignette 01). In order to start using the ABISSMAL functions, you need to load the physical .R files so that the functions are available in your global environment. In the code below, you will use the function `source()` to load 3 of the 5 main ABISSMAL functions, plus a script that holds a set of utility functions: ```{r} # Load the function that combines raw data @@ -103,11 +103,11 @@ glimpse(rfid_data) ``` -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). +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`: +You can also run `combine_raw_data()` with raw data from multiple sensors by supplying a vector with the sensor labels to the argument `sensors`. The function will still combine the raw data per sensor into separate spreadsheets per sensor, and in the process, you can avoid copy-pasting the code multiple times to run this process for multiple sensors: ```{r} combine_raw_data(sensors = c("RFID", "IRBB"), path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") From 3dd6dc331e53fab4e1e52d7da91684e64e4341b2 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Tue, 2 Apr 2024 11:29:29 -0400 Subject: [PATCH 48/69] [PCT-472]: Finished rough translation of fifth vignette --- ...utorial_05_ProcesarDatos_CrearGraficas.Rmd | 128 +++++++++--------- .../Vignette_05_ProcessData_BuildPlots.Rmd | 29 ++-- 2 files changed, 79 insertions(+), 78 deletions(-) diff --git a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd index dd603bc..02bd7ba 100644 --- a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd +++ b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd @@ -169,27 +169,25 @@ La informacion arriba te dice que hubo dos eventos de posar a las 8:00 cada dia, Detectar eventos de posar no es un requisito en el flujo de analisis de ABISSMAl, pero puede ser un paso util para obtener la mayor cantidad de informacion que puedes de los datos originales antes de filtrar detecciones en el siguiente paso de procesar o limpiar los datos originales. -TKTK continue translation +

Procesa los datos originales

-

Pre-process raw data

+Cuando hayas detectado los eventos de posar en los datos originales, puedes seguir con procesar o limpiar los datos originales con la funcion `preprocess_detections()`. Los datos originales a veces contienen multiple detecciones separadas por poco tiempo (como las detecciones de RFID cuando un individuo esta posando en la antena), y estos multiples detecciones pueden causar ruido cuando tratas de hacer inferencias de comportamiento con datos colectados por multiples sensores. Cuando le provees "thin" al argumento `mode`, `preprocess_detections()` elimina detecciones separaas 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 todavia representan eventos discretos de movimiento. -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, 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()` opera en un solo tipo de sensor a la vez: ```{r} preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") ``` -You should now see an additional .csv file called "pre_processed_data_RFID.csv" in the "processed" folder: +Ahora deberias de ver un archvio de `.csv` adicional que se llama "pre_processed_data_RFID.csv" en la carpeta "processed": ```{r} list.files(file.path(path, "Data/processed")) ``` -You can read this file into R to check out the data structure. You should see that there are fewer rows here compared to the spreadsheet of raw data: +Puedes leer este archivo a R para ver su estructura. Deberias de ver menos filas en este `dataframe` comparado con la hoja de calculo de los datos originales: ```{r} rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) @@ -198,7 +196,7 @@ glimpse(rfid_pp) ``` -Next, you can pre-process the raw beam breaker data and check out the resulting .csv file: +Luego puedes procesar los datos originales de los sensores infrarrojos y revisar el archivo de `.csv`: ```{r} preprocess_detections(sensor = "IRBB", timestamps_col_nm = "timestamp_ms", group_col_nm = "sensor_id", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") @@ -211,28 +209,29 @@ glimpse(irbb_pp) ``` -

Plot RFID data in a barcode plot

+

Visualizar datos de RFID en una grafica de codigo de barras

-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. +Ahora que combinaste y procesaste los datos originales por sensor, es hora de visualizar estos conjuntos de datos diferentes. Hacer graficas mientras escribes codigo es importante para generar figures de alta calidad para publicaciones y presentaciones y tambien para revisar tu proceso de analizar datos. -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. +En el codigo abajo, vas a aprender como usar funciones del paquete `ggplot2` para hacer una grafica del estilo de codigo de barras con los datos originales y procesados de RFID. -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. +Puedes empezar con leer los datos originales y procesados de RFID, tambien los eventos de posar de RFID, y convertir las marcas de tiempo al formato `POSIX`. ```{r} rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) %>% - # The timestamps must be converted to POSIX format every time that the data is read back into R for plotting + # Tienes que convertir las marcas de tiempo al formato POSIX cada vez que los datos se leen a R para hacer graficas dplyr::mutate( timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) ) %>% - # Arrange the timestamps from oldest to most recent + # Ordena las marcas de tiempo + # La expresion "-desc()" adentro de la funcion arrange() indica que las marcas de tiempo se ordenaran de menos a mas recientes 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 +# Deberias de ver que la columna timestamp_ms con las marcas de tiempo esta en el formato "dttm", significando que la conversion a formato POSIX sea realizo bien glimpse(rfid_raw) rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) %>% - # The timestamps must be converted to POSIX format every time that the data is read back into R for plotting + # Tienes que convertir las marcas de tiempo al formato POSIX cada vez que los datos se leen a R para hacer graficas dplyr::mutate( timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) ) %>% @@ -241,7 +240,7 @@ rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv" glimpse(rfid_pp) rfid_perch <- read.csv(file.path(path, "Data/processed/perching_events_RFID.csv")) %>% - # The start and end timestamps must be converted to POSIX format every time that the data is read back into R for plotting + # Tienes que convertir las marcas de tiempo de incio y final de cada evento de posar al formato POSIX cada vez que los datos se leen a R para hacer graficas 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")) @@ -252,7 +251,7 @@ glimpse(rfid_perch) ``` -Next, you can combine these datasets into a single data frame to facilitate combining them in the same plot. You will add a new column in order to identify the two different datasets. +Luego puedes combinar los datos originales y procesados en un solo `dataframe` para facilitar visualizar todos estos datos en la misma grafica. Vas a anadir una columna nueva (`dataset`) con etiquetas para identificar los dos conjuntos de datos diferentes. ```{r} rfid_combined <- rfid_raw %>% @@ -266,40 +265,39 @@ rfid_combined <- rfid_raw %>% dplyr::mutate( dataset = "pre-processed" ) - ) %>% - # Order the data frame by timestamps. The -desc() inside of arrange() means that the timestamps will be arranged in increasing order + ) %>% dplyr::arrange(-desc(timestamp_ms)) glimpse(rfid_combined) ``` -You will start plotting the data using functions from `ggplot2`. This package is part of the `tidyverse`, but can also be installed and used separately. You can check out this [link](https://ggplot2.tidyverse.org/) for more resources to learn how to use ggplot 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. +Vas a construir la grafica con estos datos usando funciones de `ggplot2`, un paquete que es parte del `tidyverse` pero que tambien se puede instalar y usar afuera del `tidyverse`. Puedes revisar este [enlace](https://ggplot2.tidyverse.org/) que tiene mas recursos (en ingles) para aprender como usar la notacion de `ggplot` para hacer diferentes tipos de graficas. Estos recursos incluyen secciones de tres libros diferentes con ejercicios para practicar hacer graficas a diferentes niveles de experiencia, y tambien un curso en linea y un seminario en linea. Puedes encontrar otros recursos en espanol en linea, como esta [guia para `ggplot2`](https://raw.githubusercontent.com/rstudio/cheatsheets/main/translations/spanish/data-visualization_es.pdf). -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. +El paquete de `ggplot2` tiene una notacion unica para construir graficas, en que empiezas haciendo la grafica con llamar la funcion `ggplot()` y luego anades caracteristicas con anadir capas de otras funciones de `ggplot2` con el simbolo de `+`. -If you call `ggplot()`, you'll see that the function immediately draws a blank plot in your Plots pane in RStudio. +Si llamas `ggplot()`, veras que la funcion inmediatamente dijuba una grafica vacia en tu panel de `Plots` (graficas) en RStudio. ```{r} ggplot() ``` -The plot will still remain blank even when you supply information about your data in order to set up plot aesthetics. +La grafica seguira vacia includo cuando le provees informacion sobre tus datos para poder configurar la estetica en los siguientes pasos. ```{r} ggplot(data = rfid_combined) ``` -You need to add other aesthetics functions to this base layer of the plot in order to see your data. The functions that you use to layer aesthetics over the empty plot will depend on the type of plot that you want to make. For this example, you will make a barcode style plot, in which each timestamp is shown as a thin vertical line. Barcode plots can be useful visualizations when you're working with timestamps, since the most important information is contained in one dimension (time on the x-axis). If you were to summarize the number of timestamps recorded on each day, then you could create a line plot instead. +Necesitaras anadir otras funciones esteticas a esta capa fundamental de la grafica para poder ver tus datos. Las funciones que usaras para anadir detalles esteticos a la grafica vacia dependeran del tipo de grafica que quieres crear. En este ejemplo, vas a generar una grafica de codigo de barras, en que cada marca de tiempo esta representado por una linea vertical delgada. Las grafica de codigo de barra pueden ser graficas utiles cuando trabajas con marcas de tiempo, porque la informacion mas importante se contiene en una dimension (el tiempo en el eje x). Si fueras a resumir el numero de marcas de tiempo grabado cada dia, seria mejor hacer una grafica de lineas. -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 x-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). +Puedes anadir la funcion `geom_segment()` como la siguiente capa encima de la capa fundamental de la grafica. `geom_segment()` facilita anadir linea a una grafica, y las lineas pueden comunicar informacion en una o dos dimensiones (por su ancho en el eje x y su altura en el eje y). ```{r} ggplot(data = rfid_combined) + - # Add a vertical line for each timestamp + # Anade una linea vertical para cada marca de tiempo geom_segment( aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 @@ -307,14 +305,14 @@ ggplot(data = rfid_combined) + ``` -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 in order for this color assignment by dataset to work correctly. +En el codigo arriba, `geom_segment()` anada una linea vertical a la grafica para cada deteccion 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 funcion que las lineas deberian de tener colores que corresponden al conjunto particular de datos. El argumento `color` tiene que estar adentro de la funcion `aes()` (que controla la estetica de esta capa de informacion) para que esta asignacion de colores se realice por el conjunto de datos. -The line segment colors are automatically assigned by `ggplot` using default colors, but you can change these colors using the function `scale_color_manual()`: +Los colores de las lineas se asignaran automaticamente por `ggplot` usando los colores por defecto del paquete, pero puedes cambiar estso colores usando la funcion `scale_color_manual()`: ```{r} ggplot(data = rfid_combined) + - # Add a vertical line for each timestamp + # Anade una linea vertical para cada marca de tiempo geom_segment( aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 @@ -324,32 +322,32 @@ ggplot(data = rfid_combined) + ``` -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. +Las lineas 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 grafica. -`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: +Las funciones de `ggplot` usan un tipo de datos que se llaman `factors` para automaticamente determinar la estetica de la grafica, como el metodo 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 unicos de cada columna como numeros enteros y luego guarda los valores unicos de las secuencias de caracteres en una propiedad que se llama `levels` ("niveles" o "categorias"). Puedes cambiar el orden en que los valores unicos de una columna en formato `factor` se anaden a la grafica cuando cambias el orden de los `levels` de la columna: ```{r} -# Change the column dataset to data type "factor" -# By specifying "raw" first in the argument levels, you are reordering the factor levels so that "raw" comes first +# Cambia la columna dataset al tipo de datos "factor" +# Cuando especificas que el valor de "raw" ("original") venga primero en el argumento de levels, estas 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")) ) -# The dataset column is now type "fct", or "factor" +# La columna de dataset ahora es tipo "fct" o "factor" glimpse(rfid_combined) -# The levels of the factor column are ordered with "raw" first, rather than in alphabetical order +# Los niveles de la columna ahora estan ordenados para que el valor "raw" venga primero, en vez de estar en orden alfabetico levels(rfid_combined$dataset) ``` -After converting the `dataset` column to type "factor" amnd reordering the levels of the unique character values in this column, the unique values should appear in the correct order in the plot legend (not in alphabetical order). You should also see that the colors assigned to each dataset changed. +Despues de convertir la columna de `dataset` al tipo `factor` y reorganizar los `levels` de los valores unicos en esta columna, las categorias de esta columna deberian de aparecer en el orden correcto en la leyenda de la grafica y no en orden alfabetico. Tambien deberias de ver que los colores asignados a cada conjunto de datos acaba de cambiar. ```{r} ggplot(data = rfid_combined) + - # Add a vertical line for each timestamp + # Anade una linea vertical para cada marca de tiempo geom_segment( aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 @@ -359,12 +357,12 @@ ggplot(data = rfid_combined) + ``` -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: +En la grafica que acabas de hacer, es muy dificil de discriminar entre las lineas para cada conjunto de datos. Puedes usar la funcion `facet_wrap()` para dividir los conjuntos de datos en paneles diferentes: ```{r} ggplot(data = rfid_combined) + - # Add a vertical line for each timestamp + # Anade una linea vertical para cada marca de tiempo geom_segment( aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 @@ -372,23 +370,25 @@ ggplot(data = rfid_combined) + 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 + # El simbolo de ~ significa "por", asi que estas creando un panel por cada valor unico (o categoria) en la columna 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. +Acabas de crear paneles diferentes adentro de esta grafica y cada panel contiene datos un solo conjunto de datos. Con este cambio estructural tambien alineaste los panels en el mismo eje x para que sea mas facil comparar patrones temporales. -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: +Desde este punto de vista es dificil ver como los dos conjuntos de datos (originales y procesados) son diferentes. Puedes filtrar el `dataframe` con functiones del `tidyverse` para visualizar solo las primeras dos detecciones para cada conjunto de datos: ```{r} ggplot(data = rfid_combined %>% + # Crea grupos por las categorias o levels en la columna dataset group_by(dataset) %>% + # Selecciona las primas dos filas para cada grupo slice(1:2) %>% ungroup() ) + - # Add a vertical line for each timestamp + # Anade una linea vertical para cada marca de tiempo geom_segment( aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 @@ -396,19 +396,19 @@ ggplot(data = rfid_combined %>% 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 + # El simbolo de ~ significa "por", asi que estas creando un panel por cada valor unico (o categoria) en la columna 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. +Ahora deberias de ver que la segunda marca de tiempo en los datos originales se elimino del conjunto de datos procesados (fue filtrado usando el umbral temporal en `preprocess_detections`). -Next, you can add the perching data to the plot. 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. +Luego puedes anadir 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 informacion sobre el inicio y el fin de cada evento de posar. Puedes anadir este conjunto de datos a la grafica con otra llamada de la funcion `geom_segment()`. Usaras esta capa de `geom_segment()` para anadir lineas que contienen informacion temporal sobre cuando los eventos de posar empezaron y terminaron. ```{r} ggplot(data = rfid_combined) + - # Add a vertical line for each timestamp + # Anade una linea vertical para cada marca de tiempo geom_segment( aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 @@ -416,10 +416,10 @@ ggplot(data = rfid_combined) + 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 + # El simbolo de ~ significa "por", asi que estas creando un panel por cada valor unico (o categoria) en la columna dataset facet_wrap(~ dataset, nrow = 2, strip.position = "left") + - # Add the perching events to the plot + # Anade los eventos de posar geom_segment( data = rfid_perch, aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5), @@ -429,14 +429,14 @@ ggplot(data = rfid_combined) + ``` -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. +En el codigo arriba para `geom_segment()`, especificaste que querias anadir otro conjunto de datos a la grafica cuando usaste el argumento `data`. Los argumentos `x` y `y` determinan donde va a empezar cada linea en ambos ejes de la grafica, respectivamente. Tambien vas a tener que especificar donde quieres que cada linea termina en cada eje. En el eje x, indicaste que quieres que la linea 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 numeros que usaste para los argumentos `y` y `yend` determinan donde las lineas para los eventos de posar se dibujaran, que en este caso es justamente arriba de las lineas para los otros conjuntos de datos. Las lineas para los eventos de posar se dibujaron como otra capa de informacion encima de cada panel de la grafica por defecto. -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. +Puedes hacer unos cambios a la grafica para que sea mas facil de interpretar. Puedes cambiar la posicion de la leyenda usando el argumento `legend.position` adentro de la funcion general de `theme()`. Abajo puedes guardar la grafica adentro de un objeto, para que no tengas que escribir todo el codigo de la grafica de nuevo cuando quieres anadirle mas informacion. ```{r} gg <- ggplot(data = rfid_combined) + - # Add a vertical line for each timestamp + # Anade una linea vertical para cada marca de tiempo geom_segment( aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 @@ -444,10 +444,10 @@ gg <- ggplot(data = rfid_combined) + 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 + # El simbolo de ~ significa "por", asi que estas creando un panel por cada valor unico (o categoria) en la columna dataset facet_wrap(~ dataset, nrow = 2, strip.position = "left") + - # Add the perching events to the plot + # Anade los eventos de posar geom_segment( data = rfid_perch, aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5), @@ -463,21 +463,21 @@ 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(since the y-axis does not contain information for interpretation (the height of each segment does not reflect data that you want to interpret). You can also change the position of the legend so that it sits on top of the plot. +Ahora puedes hacer unos ajustes menores para seguir mejorando a la grafica, incluyendo cambiar los titulos de los ejes para que sean mas informativos, cambiar el color de fondo a blanco, y eliminar el text en el eje y tnato como las rayas en el eje y, porque este eje no contiene informacion para interpretacion de los datos (o sea, la altura de cada linea no contiene informacion para interpretacion). ```{r} gg <- gg + - # Change the x and y axis labels + # Cambia los titulos de ambos ejes xlab("Date and time") + - # The y-axis does not contain information right now, so it can be blank + # El eje y no contiene informacion y por ende puedes eliminar este titulo ylab("") + - # Use this function to convert the plot background to black and white + # Usa esta funcion para cambiar el color de fondo a blanco y negro theme_bw() + - # Use aesthetics functions to remove the y-axis labels and ticks + # Usa funciones de estetica para eliminar el texto en el eje y y tambien los rayos en este eje theme( axis.text.y = element_blank(), axis.ticks.y = element_blank(), @@ -488,16 +488,16 @@ 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. +Puedes guardar las graficas que creas en R como archivos fisicos. Abajo usaras la funcion `ggsave()` para escribir la grafica que hiciste arriba como un archico en tu computadora. ```{r} gg -# Save the image file to your computer +# Guarda la grafica como un archivo en tu computadora 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. +Puedes continuar con modificaciones a este archivo para crear una figura de alta calidad para una publicacion. Por ejemplo, puedes cambiar el tamano final del archivo (`width` o "lo ancho", `height` o "la altura"), tanto como la resolucion en pixeles (`dpi`). Tambien puedes cambiar el tamano de texto en cada eje o del titulo de cada eje, o la posicion de la layenda mientras determinas el tamano final del imagen. -In the next vignette, you will continue the ABISSMAL data analysis pipeline and make a more complex and refined barcode style figure. \ No newline at end of file +En el siguiente tutorial vas a continuar analizando datos con ABISSMAL y vas a crear una grafica de codigo de barras mas compleja y refinada. \ No newline at end of file diff --git a/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd b/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd index ae67820..93f4cb9 100644 --- a/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd +++ b/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd @@ -168,7 +168,7 @@ Detecting perching events is not a requirement in the ABISSMAL workflow, but it

Pre-process raw data

-Once you've detected perching events in the raw data, you can move on to pre-processing the raw data itself with `preprocess_detections()`. The raw data can sometimes contain multiple detections separated by a short period of time (e.g. RFID detections when an individual is perching on the antenna), and these multiple detections can be a source of noise when you're trying to make behavioral inferences across data collected by multiple sensors. When the argument `mode` is set to "thin", `preprocess_detections()` removes detections separated by a very short period of time, and retains a reduced datasets of detections that still represents discrete movement events. +Once you've detected perching events in the raw data, you can move on to pre-processing the raw data itself with `preprocess_detections()`. The raw data can sometimes contain multiple detections separated by a short period of time (e.g. RFID detections when an individual is perching on the antenna), and these multiple detections can be a source of noise when you're trying to make behavioral inferences across data collected by multiple sensors. When the argument `mode` is set to "thin", `preprocess_detections()` removes detections separated by a very short period of time (determined by the temporal threshold in seconds passed to the argument `thin_threshold`), and retains a reduced datasets of detections that still represents discrete movement events. `preprocess_detections()` also operates on a single sensor type at a time: ```{r} @@ -220,7 +220,7 @@ rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFI dplyr::mutate( timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) ) %>% - # Arrange the timestamps from oldest to most recent + # 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 @@ -247,7 +247,7 @@ glimpse(rfid_perch) ``` -Next, you can combine these datasets into a single data frame to facilitate combining them in the same plot. You will add a new column in order to identify the two different datasets. +Next, you can combine the original and pre-processed datasets into a single data frame to facilitate combining them in the same plot. You will add a new column (`dataset`) with labels in order to identify the two different datasets. ```{r} rfid_combined <- rfid_raw %>% @@ -262,14 +262,13 @@ rfid_combined <- rfid_raw %>% dataset = "pre-processed" ) ) %>% - # Order the data frame by timestamps. The -desc() inside of arrange() means that the timestamps will be arranged in increasing order dplyr::arrange(-desc(timestamp_ms)) glimpse(rfid_combined) ``` -You will start plotting the data using functions from `ggplot2`. This package is part of the `tidyverse`, but can also be installed and used separately. You can check out this [link](https://ggplot2.tidyverse.org/) for more resources to learn how to use ggplot 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. +You will start plotting the data using functions from `ggplot2`. This package is part of the `tidyverse`, but can also be installed and used separately. You can check out this [link](https://ggplot2.tidyverse.org/) for more resources to learn how to use ggplot notation to make different types of plots. These resources include sections of three different books with hands-on exercises at different levels, as well as an online course and a webinar. The `ggplot2` package has a unique syntax for building plots, in which you start making a plot by calling the function `ggplot()`, and then add features by layering on other `ggplot2` functions with the `+` symbol. @@ -289,7 +288,7 @@ 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 x-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). +You can layer the function `geom_segment()` over the base plot layer. `geom_segment()` allows you to add lines to a plot, and the lines can communicate information in one or two dimensions (width on the x-axis and height on the y-axis). In the plot you'll make below, you'll use `geom_segment()` to add lines to the plot that contain temporal information in one dimension (timestamps that provide information on the x-axis only). ```{r} ggplot(data = rfid_combined) + @@ -302,7 +301,7 @@ ggplot(data = rfid_combined) + ``` -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 in order for this color assignment by dataset to work correctly. +In the code above, `geom_segment()` adds a vertical line segment to the plot for each detection in the full dataset. Using the argument `color` inside of `geom_segment()`, you told the function that these line segments should be colored by dataset by supplying the column name that holds the dataset labels. The `color` argument must be inside of the `aes()` function (that controls the aesthetics for this layer of information) in order for this color assignment by dataset to work correctly. The line segment colors are automatically assigned by `ggplot` using default colors, but you can change these colors using the function `scale_color_manual()`: ```{r} @@ -378,7 +377,9 @@ From this point of view though, it's hard to see how the raw and pre-processed d ```{r} ggplot(data = rfid_combined %>% - group_by(dataset) %>% + # 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() ) + @@ -396,9 +397,9 @@ ggplot(data = rfid_combined %>% ``` -You should be able to see that the second timestamp in the raw data was dropped from the pre-processed dataset. +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 to the plot. 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. +Next, you can add the perching data in the data frame `rfid_perch` that you didn't combine with the other two datasets. This dataset doesn't have a single timestamps column, but rather has two columns that indicate the start and the end of each perching event, respectively. You can add this dataset to the full plot by using another `geom_segment()` layer. You'll use `geom_segment()` to add lines to the plot that contain temporal information about when perching events started and ended. ```{r} ggplot(data = rfid_combined) + @@ -458,21 +459,21 @@ 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(since the y-axis does not contain information for interpretation (the height of each segment does not reflect data that you want to interpret). You can also change the position of the legend so that it sits on top of the plot. +You can make some minor adjustments to the plot to make it easier on the eyes, including changing the axis titles to be more informative, changing the background to be white, and removing the y-axis text and axis ticks. The y-axis does not contain information for interpretation because the height of each segment does not reflect data that you want to interpret. ```{r} gg <- gg + - # Change the x and y axis labels + # Change the x and y axis title xlab("Date and time") + - # The y-axis does not contain information right now, so it can be blank + # 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 labels and ticks + # Use aesthetics functions to remove the y-axis text and ticks theme( axis.text.y = element_blank(), axis.ticks.y = element_blank(), From 76a190679f06102b58584f6ce63e937253692854 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Tue, 2 Apr 2024 14:01:38 -0400 Subject: [PATCH 49/69] [PCT-472]: Nearly done with rough translation of fifth vignette --- ...utorial_05_ProcesarDatos_CrearGraficas.Rmd | 2 +- R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd | 568 ++++++++++++++++++ .../Vignette_06_FinishAnalysisPipeline.Rmd | 45 +- 3 files changed, 591 insertions(+), 24 deletions(-) create mode 100644 R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd diff --git a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd index 02bd7ba..bc740f4 100644 --- a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd +++ b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd @@ -55,7 +55,7 @@ source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_perching_events. # Carga la funcion que procesa los datos originales source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/preprocess_detections.R") -# Carga un archvio con funciones de apoyo que cada funcion arriba require +# Carga un archivo con funciones de apoyo que cada funcion arriba require source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") ``` diff --git a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd new file mode 100644 index 0000000..d450fa1 --- /dev/null +++ b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd @@ -0,0 +1,568 @@ +--- +title: "Tutorial 06: Finalizar Los Analisis" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = TRUE) + +``` + +TKTK terms to replace in previous translations (flujo de analisis = pipeline, cumulo = cluster) + +

Información sobre esta traducción

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

Resumen del tutorial y objetivos de aprendizaje

+ +En este sexto y ultimo 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` ("cumulos") de detecciones que representan eventos de movimientos distintos y luego vas a anotar inferencias de comportamiento de estos eventos de movimiento. Tambien vas a crear graficas para visualizar las inferencias de comportamiento. Vas a continuar a usar habilidades que aprendistes en los tutoriales anteriores, y tambien vas a aprender como crear visualizaciones mas complejas con `ggplot()`. + +

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

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

Cargar funciones de ABISSMAL

+ +```{r} + +# Carga la funcion que detecta clusters en los datos procesados +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_clusters.R") + +# Carga la funcion 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 funcion arriba require +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") + +``` + +

Termina el `pipeline` de ABISSMAL

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

Revisa los resultados finales

+ +Puedes revisar los resultados finales ahora que terminaste de ejecutar el `pipeline` de ABISSMAl para detectar `clusters` y generar inferencias de comportamiento. +```{r} + +scored_clusters <- read.csv(file.path(path, "Data/processed", "scored_detectionClusters.csv")) %>% + # Tienes que convertir las marcas de tiempo al formato POSIX cada vez que los datos se leen a R para hacer graficas + 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) + +``` + +Cuantos eventos de entrada y salida se anotaron por dia? + +

Datos ausentos en R

+ +Para poder contra la cantidad de cada uno de estos eventos que ABISSMAL anoto por dia, necesitas saber como 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 especifico en R. Puedes determinar si un vector (o una columna en un `dataframe`) contiene datos ausentes si usas la funcion `is.na()`, que develovera `TRUE` cuando encuentra un valor de `NA` (un valor ausente) en el vector actual. +```{r} + +?is.na() + +x <- c(1, NA, 2, 3, NA) + +is.na(x) + +``` + +En el codigo arriba, creaste un vector que se llama `x` que tiene dos valores de `NA`. La funcion `is.na()` revisa si cada elemento de `x` es equivalente a `NA`, y devuelve `TRUE` cuando se cumple esa condicion (o sea, cuando encuentra un dato ausente). + +Como `is.na()` es una frase condicional, tambien puedes usar otros simbolos especiales que son relevantes a frases condicionales, como el simbolo de `!`, que sirve para invertir una frase condicional. Por ejemplo, en el codigo abajo, cuando anades `!` antes de `is.na()`, estas preguntando si cada elemento de `x` *no* es equivalente a `NA`: +```{r} + +!is.na(x) + +``` + +Como puedes ver, anadir 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 convirtio 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 utiles para encontrar y filtrar las filas de un `dataframe`. + +Por ejemplo, la funcion `dplyr::filter()` eliminara una fila cada vez que encuentra un valor de `FALSE` en la columna actual y en cambio no eliminara 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, anadirias `!is.na(name_of_column)` adentro de `dplyr::filter()`, que deberia de devolver `FALSE` cada vez que encuentra una fila con `NA`, y eliminara esa fila como parte de la operacion de filtrar. + +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. + +

Cuenta eventos por dia

+ +Ahora puedes escribir codigo para contar el numero de eventos de entrada y salida que ABISSMAL anoto por dia. Vas a necesitar 1) crear una columna nueva con la informacion sobre el dia para cada marca de tiempo, 2) eliminar filas con datos ausentes para la informacion anotada de la direccion del movimiento (la columna `direction_scored`, porque esta informacion no se puede anotar para algunos movimientos), 3) agrupar el `dataframe` por dia y por la direccion anotada, y luego 4) contar el numero de filas por grupo. +```{r} + +scored_clusters %>% + # Extrae el dia de cada marca de tiempo y crea una columna nueva con esta informacion + dplyr::mutate( + day = lubridate::day(start) + ) %>% + # Aqui estas usando la funcion is.na() que devolvera TRUE cuando encuentra un valo ausente (NA) en la columna actual. Con colocar el simbolo de ! antes de is.na(), estas invirtiendo la frase condicional y tambien el resultado, asi que todos los valores TRUE se convertiran a FALSE. Por ende, dplyr::filter eliminara todas las filas que devuelven el valor de FALSE en esta expresion (o sea, todas las filas con valores ausentes en la columna direction_scored con informacion sobre la direccion anotada de movimiento) + dplyr::filter(!is.na(direction_scored)) %>% + # Agrupa el dataframe por ambas columnas para las cuales quieres contrar filas (eventos). Aqui quieres saber el numero de entradas y salidas (categorias en la columna direction_scored) por dia (categorias en la columna day) + group_by(day, direction_scored) %>% + # Luego puedes resumir los datos: el numero de filas aqui es el numero de entradas o salidas anotadas por dia + dplyr::summarise( + n = n() + ) + +``` + +Cuatro entradas y salidas se anotaron por dia. Como se compara este resultado con el numero de entradas y salidas que esperabas por dia? Si regresas al codigo en los tutoriales anteriores (el tercer y el cuarto tutorial) donde creaste los datos originales simulados, deberias de poder ver que empezaste por simular dos entradas y dos salidas por dia en los datos para el sistema de RFID y los sensores de infrarrojo. Luego anadiste dos entradas y dos salidas mas por dia cuando simulaste fallas de deteccion del sistema de RFID (o sea, estos fueron movimientos capturados solamente por los sensores de infrarrojo). `score_clusters` detecto el numero correcto de entradas y salidas por dia. + +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 informacion sobre las etiquetas de PIT). Luego puedes seleccionar solo las columnas que contienen informacion que es util revisar, como las identidades de las etiquetas PIT, y tambien las marcas de tiempo para el inicio y fin de cada evento de posar. +```{r} + +scored_clusters %>% + # Usa una frase condicional con is.na() para retener solo las filas que tienen codigos de etiquetas PIT que fueron asociados con eventos de posar + dplyr::filter(!is.na(perching_PIT_tag)) %>% + # Luego selecciona colo las columnas que quieres revisar visualmente + dplyr::select(start, end, perching_PIT_tag) + +``` + +Como aprendiste en el tercer y el cuarto tutorial, el primer evento de posar por dia se realizo por el primer individuo (con la etiqueta de PIT "1357aabbcc"), y el segundo evento de posar por dia fue realizado por el segundo individuo (con la etiqueta de PIT "2468zzyyxx"). + +Cuantos eventos de movimento que no fueron eventos de posar fueron asignados a cada individuo? +```{r} + +scored_clusters %>% + # Extrae el dia de cada marca de tiempo y crea una columna nueva con esta informacion + dplyr::mutate( + day = lubridate::day(start) + ) %>% + # Usa una frase condicional con is.na() para retener solo las filas que tienen codigos de las etiquetas de PIT que no fueron asociados con eventos de posar + # Aqui estas combinando dos frases condicionales para poder buscar filas en la columna individual_initiated (o el individuo que inicio el movimiento) que tienen codigos de etiquetas PIT, pero que tambien no tienen una etiqueta de sensor el 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() + ) + +``` + +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 dia, que es exactamente lo que simulaste en el tercer y el cuarto tutorial. Mas movimientos que no fueron eventos de posar se detectaron a traves de estos dias tambien, 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 solo los sensores de infrarrojo que no pueden grabar informacion sobre la identidad de individuos. + +

Construye una grafica compleja de codigo de barras

+ +Ahora puedes visualizar los resultados finales. En el codigo abajo vas a aprender como crear una grafica de codigo de barras que es mas compleja de lo que viste en el tutorial anterior, pero que es mas facil que interpretar que esa grafica anterior. Para esta grafica seria util poder visualizar tres tipos de inferencias de comportamiento o tipos de informacion a traves del tiempo: la direccion de movimiento (cuando esta disponsible), la identidad del individuo (cuando esta disponible), y los eventos de posar. + +Puedes empezar con construir la grafica con anadir lineas verticles para los eventos que no fueron eventos de posar. El color de cada linea indicara la identidad del individuo, incluyendo cuando esta informacion no estaba disponible. El tipo de linea va a contener informacion sobre la direccion de movimiento, y tambien cuando esta informacion no se pudo anotar. + +Para poder asignar colores y tipos de lineas de la forma que quieres en la grafica, vas a necesitar modificar el `dataframe` de los resultados finales para convertir los `NAs` en las dos columnas asociadas con estos detalles de estetica para convertir los datos ausentes en informacion util. Por ejemplo, cuando no hay informacion sobre la identidad del individuo, seria muy util convertir los valores asociados de `NA` a un valor como "unassigned" ("no asignado"). En el codigo abajo vas a usar la funcion `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 informacion mas util parala grafica que vas a hacer mas adelante. + +Primero puedes practicar usar `is.na()` adentro de `ifelse()` para crear un vector nuevo. En el codigo abajo, vas a proveer la frase condicional que quieres probar (aqui vas a probar si la columna que contiene las etiquetas PIT del individuo que inicio el movimiento tiene valores de `NA`), el valor que quieres anadir al vetcor si la condicion se cumple (el valor "unassigned" cuando no hay informacion sobre la etiqueta PIT), y luego el valor que quieres anadirn si la condicion no se cumple (en este caso es devolver la etiqueta PIT en la columna individual_initiated si el valor actual no es `NA`). +```{r} + +# is.na() devuelve TRUE cuando encuentra valores de NA adentro de un vector (o columna) y FALSE cuando el valor actual no es NA +is.na(scored_clusters$individual_initiated) + +# Puedes ver que todos los valores de NA adentro de esta columna se convirtieron a "unassigned" pero todos los otros valores no cambiaron +ifelse(test = is.na(scored_clusters$individual_initiated), yes = "unassigned", no = scored_clusters$individual_initiated) + +``` + +Ahora puedes usar `is.na()` adentro de frases de `ifelse()` para modificar columnas en el `dataframe`. +```{r} + +scored_clusters_gg <- scored_clusters %>% + dplyr::mutate( + # Si esta columna tiene un valor de NA, cambia el valor actual a "unassigned" + # Pero si el valor actual no es NA, no cambies el valor actual de esta columna + individual_initiated = ifelse(is.na(individual_initiated), "unassigned", individual_initiated), + # Repita este proceso para la columna direction_scored pero con un valor diferente (aqui "not_scored" significa que la direccion no sen pudo anotar) + # En la frase condicional abajo anadiste is.na(perching_sensor) (despues del simbolo de &) para solo convertir los valores de NA en la columna de direction_scored si tambien 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 (categorias) para crear la grafica de la forma que queremos (por ejemplo, los valores de "unassigned" y "not scored" deberian de ser los ultimos 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 funcion distinct() para ver que todos los valores unicos de cada columna si 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) + +``` + +Este resultado se ve bien. Deberias de ver valores de `NA` en el `dataframe` pero estan en la columna "direction_scored" y asociados con los eventos de posar, que vas a anadir a la grafica en otra capa diferente de codigo mas adelante. + +Puedes usar este `dataframe` modificado para crear la grafica. En el codigo abajo, vas a anadir lineas con colores asignados por la identidad de individuos, y los tipos de linea van a representar la direccion de movimiento. Primero vas a especificar los detalles esteticos de la grafica: +```{r} + +# Los colores estan el el mismo order que los levels (categorias) de la columna individual_initiated, asi que el color naranja va a representar la etiqueta PIT "1357aabbcc" +levels(scored_clusters_gg$individual_initiated) +cols <- c("orange", "darkgreen", "black") + +# Los tipos de linea estan el el mismo order que los levels de la columna direction_scored, asi que el valor "dotted" ("puntos) va a representar "not scored" (cuando la direccion no se pudo anotar) +levels(scored_clusters_gg$direction_scored) +ltys <- c("solid", "longdash", "dotted") + +``` + +Luego puedes anadir lineas a la grafica por identidad de individuo. Aqui estas dividiendo las llamadas de `geom_segment()` por los levels (categorias o valores unicos) en la columna individual_initiated. Solo anadiste lineas para el primer individuo y todos los movimientos que no fueron eventos de posar que no fueron asignados a un individuo porque despues de revisar los resultados finales, sabes que ningun movimiento (que no fue evento de posar) fue asignado al segundo individuo. +```{r} + +ggplot() + + + # Anade una linea 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 + ) + + + # Anade una linea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos 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 + ) + + + # Anade los tipos de linea customizadas a la grafica + scale_linetype_manual(values = ltys) + + + # Elimina el titulo del eje y + ylab("") + + + # Usa esta funcion para convertir el fondo de la grafica a blanco y negro + theme_bw() + + + # Usa estas funciones de estetica para elimninar el text y los rayos del eje y + # Anade un argumento para cambiar la posicion de la leyenda adentro de la grafica + theme( + axis.text.y = element_blank(), + axis.ticks.y = element_blank(), + legend.position = "top" + ) + +``` + +Para hacer esta grafica organizaste las lineas verticales para separar los movimientos asignados al primer individuo y los movimientos que no fueron asignados. Esta separacion vertical entre estos dos conjuntos de datos facilita las comparaciones visuales de patrones de variacion en los movimientos a traves del tiempo. Creaste esta separacion vertical con cambiar los valores que usaste para `y` y `yend` en la segunda capa de `geom_segment()` para que esas lineas empezarian mas arriba que las lineas de la primera capa de `geom_segment()`. + +Puedes hacer mas modificaciones que ayudarian con interpretar esta grafica. En primerm lugar, las etiquetas de los paneles que estan al lado izquierdo se pueden cambiar para demostrar el dia general de coleccion de datos (como "Day 1" para el primer dia) en vez de la fecha. El texto en en eje x tambien se puede cambiar para demostrar solo el tiempo (o sea eliminar la informacion sobre el mes y el dia), y tambien puedes anadir mas etiquetas (por ejemplo, una etiqueta cada media hora). + +Puedes emmpezar con modificar el `dataframe` para anadir la informacion sobre el dia de coleccion de datos. Vas a crear esta columna nueva con frases condicionales a traves de la funcion `ifelse` porque solo hay tres dias de coleccion de datos con etiquetas que tienes que cambiar. +```{r} + +# Crea una columna nueva en los datos originales para la fecha de coleccion de datos +scored_clusters_gg2 <- scored_clusters_gg %>% + # Primero necesitas crear una columna con informacion sobre el dia + dplyr::mutate( + day = lubridate::day(start) + ) %>% + dplyr::mutate( + # Luego deberias de reemplazar la etiqueta para cada dia y guardar estos resultados en una columna nueva + day_label = ifelse(day == 1, "Day 1", day), # Aqui el ultimo argumento es la columna day porque la columna day_label no se ha creado todavia + day_label = ifelse(day == 2, "Day 2", day_label), + day_label = ifelse(day == 3, "Day 3", day_label) + ) + +# Se ve bien +glimpse(scored_clusters_gg2) + +scored_clusters_gg2 %>% + distinct(day_label) + +``` + +Ahora puedes actualizar el codigo para incluir las etiquetas nuevas de las fechas: +```{r} + +# Anade el dataframe como el conjunto de datos por defecto para la capa fundamental de la grafica para que la funcion facet_wrap() tenga datos +ggplot(data = scored_clusters_gg2) + + + # Anade una linea 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 + ) + + + # Anade una linea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos 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 + ) + + + # Anade los tipos de linea customizadas a la grafica + scale_linetype_manual(values = ltys) + + + # Elimina el titulo del eje y + ylab("") + + + # Usa esta funcion para convertir el fondo de la grafica a blanco y negro + theme_bw() + + + # Usa estas funciones de estetica para elimninar el text y los rayos del eje y + # Anade un argumento para cambiar la posicion de la leyenda adentro de la grafica + theme( + axis.text.y = element_blank(), + axis.ticks.y = element_blank(), + legend.position = "top" + ) + + + # Crea paneles en la grafica por dia, aqui usaras las etiquetas nuevas de dia + facet_wrap(~ day_label, nrow = 3, strip.position = "left") + +``` + +Ahora que moviste la informacion sobre el dia de coleccion de datos a las etiquetas de los paneles de la grafica, necesitas componer el texto en el eje x. La grafica sera mas facil de interpretar si puedes alinear las marcas de tiempo por hora y minuto para una comparacion directa a traves de dias diferentes. + +Para alinear las marcas de tiempo a traves de dias, necesitas actualizar el formato de las columnas que contienen las marcas de tiempo. El codigo para convertir las marcas de tiempo a un formato diferente es anidado y repetitivo pero la conversion se realizara correctamente. Cuando le comunicas a R que deberias de convertir las marcas de tiempo a un formato con horas, minutos, y segundos solamente, R va a anadir un año, un mes, y un dia por defecto antes de la marca de tiempo (lo mas comun es que usara la fecha actual). Este comportamiento es esperado y no es un error, mas bien facilita que las marcas de tiempo se alinean de la forma correcta a traves de dias (paneles) en la grafica (porque R considera todas las marcas de tiempo como si ocurrieron en un solo dia). +```{r} + +scored_clusters_gg3 <- scored_clusters_gg2 %>% + dplyr::mutate( + start_gg = as.POSIXct(strptime(format(as.POSIXct(start), "%H:%M:%S"), format = "%H:%M:%S")), + end_gg = as.POSIXct(strptime(format(as.POSIXct(end), "%H:%M:%S"), format = "%H:%M:%S")) + ) + +# Deberias de ver que un año, mes, y dia nuevo fueron adjuntados a las marcas de tiempo modificadas, pero este resultado es esperado (ve arriba) +glimpse(scored_clusters_gg3) + +``` + +Ahora puedes actualizar el codigo de crear la grafica para cambiar la estetica del eje x usando la funcion `scale_x_datetime()` para especificar que quieres etiquetas cada media hora en este eje. Tambien vas a anadir un titulo para el eje x y eliminar la cuadricula en el eje y adentro de cada panel: +```{r} + +gg <- ggplot(data = scored_clusters_gg3) + + + # Anade una linea 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 + ) + + + # Anade una linea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos 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 + ) + + + # Anade los tipos de linea customizadas a la grafica + scale_linetype_manual(values = ltys) + + + # Elimina el titulo del eje y + ylab("") + + + # Usa esta funcion para convertir el fondo de la grafica a blanco y negro + theme_bw() + + + # Usa estas funciones de estetica para elimninar el text y los rayos del eje y + # Anade un argumento para cambiar la posicion de la leyenda adentro de la grafica + theme( + axis.text.y = element_blank(), + axis.ticks.y = element_blank(), + legend.position = "top" + ) + + + # Crea paneles en la grafica por dia, aqui usaras las etiquetas nuevas de dia + facet_wrap(~ day_label, nrow = 3, strip.position = "left") + + + # Cambia la estetica de las etiquetas del eje x + scale_x_datetime( + date_breaks = "30 mins", + date_labels = "%H:%M" + ) + + + # Anade un titulo para el eje x + xlab("Time of day (HH:MM)") + + + # Puedes quitar la cuadricula en el eje (mayor y menor) adentro de cada panel + theme( + panel.grid.major.y = element_blank(), + panel.grid.minor.y = element_blank() + ) + +gg + +``` + +Todavia falta un tipo de informacion importante que necesitas anadir a esta grafica: los eventos de posar. Vas a anadir esta informacion con otra capa de `geom_segment()` pero ahora vas a crear lineas cortas y anchas para que aparezan mas como puntos en la grafica: +```{r} + +gg <- gg + + + # Anade los eventos de posar como lineas con orillas redondeadas, y puedes anadir colores que indican la identidad del individuo a traves 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" + ) + + + # Anade los colores customizados para estos eventos de posar. Los colores tambien aplican a las lineas de movimientos por individuo que no fueron eventos de posar que anadiste en capas anteriores de geom_segment() + scale_color_manual(values = cols) + +gg + +``` + +Ahora deberias de ver que una leyenda de color aparece arriba de la grafica con la adicion de esta capa adicional de `geom_segment()`. Ambas de las leyendas se pueden mejorar. Puedes modificar la grafica con actualizar el titulo de cada leyenda, aumentar el tamano de texto de cada leyenda, y reducir el espacio blanco ente las leyendas y la grafica. Para modificar los titulos de cada leyenda, vas a usar las funciones `guides()` y `guide_legend()`. Para aumentar el tamano de texto en la leyenda, vas a usar el argumento `legend.text` adentro de la funcion `theme()`, y para reducir el espacio blanco entre la grafica y las leyendas, vas a usar el argumento `legend.margin` adentro de la funcion `theme()`. +```{r} + +# Ve mas informacion sobre la funcion que controla los margenes (espacio blanco) alrededor de la leyenda +?margin + +gg <- gg + + + # Aumenta el tamano de texto de cada leyenda y reduce el espacio blanco entre la grafica y las leyendas + # 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") + ) + + + # Modifica los titulos de cada leyenda + guides( + linetype = guide_legend(title = "Direction"), + color = guide_legend(title = "Individual") + ) + +gg + +``` + +TKTK continue + +Finally, you can save this plot as an image file: +```{r} + +gg + +# Save the image file to your computer +ggsave(file.path(path, "behavioral_inferences.tiff"), width = 8, height = 6, units = "in", dpi = 300) + +``` + +You can continue to modify minor aesthetics to this image file to create a high-quality figure for a publication. For instance, you can change the final size of the image file (`width`, `height`), as well as the resolution (`dpi`). You can also change the size of the text on each axis and the axis titles, or the legend position, as you play around with the final image size. + +This is the all of the code used for the final plot, condensed and reorganized: +```{r eval = FALSE} + +ggplot(data = scored_clusters_gg3) + + + # Add a vertical line for each non-perching event assigned to the first individual + geom_segment( + data = scored_clusters_gg3 %>% + dplyr::filter(individual_initiated == "1357aabbcc"), + aes(x = start_gg, y = 0, xend = end_gg, yend = 0.5, linetype = direction_scored), + color = "orange", + linewidth = 0.5 + ) + + + # Add a vertical line for each non-perching event that was not assigned to either individual + geom_segment( + data = scored_clusters_gg3 %>% + dplyr::filter(individual_initiated == "unassigned"), + aes(x = start_gg, y = 0.6, xend = end_gg, yend = 1.1, linetype = direction_scored), + color = "black", + linewidth = 0.5 + ) + + + # Add the perching events as rounded segments, and now encode color through the column individual_initiated + geom_segment( + data = scored_clusters_gg3 %>% + dplyr::filter(!is.na(perching_sensor)), + aes(x = start_gg, y = 1.2, xend = end_gg, yend = 1.2, color = individual_initiated), + linewidth = 2, lineend = "round" + ) + + + # Add the custom linetype values to this plot + scale_linetype_manual(values = ltys) + + + # Add the custom colors + scale_color_manual(values = cols) + + + # Change the legend titles + guides( + linetype = guide_legend(title = "Direction"), + color = guide_legend(title = "Individual") + ) + + + # Add an x-axis title + xlab("Time of day (HH:MM)") + + + # Remove the y-axis 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") + ) + +``` + +You completed the final vignette of the ABISSMAL data processing and analysis pipeline. You also practiced your coding and data science skills in a biological context. Well done! It would be very helpful if you could complete the brief Google form for a post-vignette evaluation that will help us continue to improve these vignettes over time. A link to this Google form will be available in the README file for the vignettes. \ No newline at end of file diff --git a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd index dd361cc..7c294ad 100644 --- a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd +++ b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd @@ -88,7 +88,7 @@ How many entrances and exits were scored per day?

Missing data in R

-In order to count the number of each of these events scored per day, you need to know how to account for missing data in R. Missing data is often indicated using the value `NA` or "not available", which is a specific type of logical value in R. You can figure out whether a vector (or a column in a data frame) contains missing values by using the function `is.na()`, which will return TRUE when it finds an NA (missing) value in the given column. +In order to count the number of each of these events scored per day, you need to know how to account for missing data in R. Missing data is often indicated using the value `NA` or "not available", which is a specific type of logical value in R. You can figure out whether a vector (or a column in a data frame) contains missing values by using the function `is.na()`, which will return TRUE when it finds an NA (missing) value in the given vector. ```{r} ?is.na() @@ -99,7 +99,7 @@ is.na(x) ``` -In the code above, you created a vector called `x` that has 2 NA values. The function `is.na()` checks whether each element of `x` is equivalent to `NA`, and returns `TRUE` when that condition is met. +In the code above, you created a vector called `x` that has 2 NA values. The function `is.na()` checks whether each element of `x` is equivalent to `NA`, and returns `TRUE` when that condition is met (when it finds missing data). Because `is.na()` is a conditional statement, you can also use other special symbols relevant to conditional statements, such as the `!` symbol, which will invert a conditional statement. For instance, in the code below, by adding `!` before `is.na()`, you're now asking whether each element of `x` is *not* equivalent to NA: ```{r} @@ -110,11 +110,11 @@ Because `is.na()` is a conditional statement, you can also use other special sym 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. +For instance, the function `dplyr::filter()` will drop a row whenever it encounters a value of `FALSE` in a given column, and will retain a row whenever it encounters a value of `TRUE`. If you want to drop rows that contain `NA` values for a given column, you would add `!is.na(name_of_column)` inside of `dplyr::filter()`, which should return `FALSE` every time it encounters a row with `NA`, and will remove that row during filtering.

Count events per day

-Now you can write code to count the number of entrance and exit events scored per day. You will need to 1) make a new column with information about the day per timestamp, 2) drop rows with missing data for the direction scored, 3) group the data frame by day and direction scored, and then 4) count the number of rows per group. +Now you can write code to count the number of entrance and exit events scored per day. You will need to 1) make a new column with information about the day per timestamp, 2) drop rows with missing data for the direction scored (because this information cannot be scored for some movements), 3) group the data frame by day and direction scored, and then 4) count the number of rows per group. ```{r} scored_clusters %>% @@ -124,7 +124,7 @@ scored_clusters %>% ) %>% # 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 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( @@ -146,7 +146,7 @@ scored_clusters %>% ``` -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). +As specified in vignettes 03 and 04, the first perching event each day was performed by the first individual (PIT tag "1357aabbcc"), and the second was attributed to the second individual (PIT tag "2468zzyyxx"). How many movement events that were not perching events were attributed to each individual? ```{r} @@ -196,10 +196,10 @@ scored_clusters_gg <- scored_clusters %>% # 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 + # 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 + # 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")) @@ -212,7 +212,7 @@ scored_clusters_gg %>% ``` -This output looks good. You should see NAs in this data frame, but they're in the column "direction_scored", which means that the direction of some movements could not be scored. +This output looks good. You should see `NA` values in this data frame, but they're in the column "direction_scored" and associated with the eprching events, which you'll be adding to the plot in a separate layer of code later on. You can use this modified data frame to build the plot. In the code below, you'll add lines colored by individual identity and with line types encoding the direction of movement. First you'll specify the plot aesthetics: ```{r} @@ -253,13 +253,13 @@ ggplot() + # Add the custom linetype values to this plot scale_linetype_manual(values = ltys) + - # Remove the y-axis label + # 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 labels and ticks + # 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(), @@ -324,13 +324,13 @@ ggplot(data = scored_clusters_gg2) + # Add the custom linetype values to this plot scale_linetype_manual(values = ltys) + - # Remove the y-axis label + # 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 labels and ticks + # 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(), @@ -342,7 +342,6 @@ ggplot(data = scored_clusters_gg2) + # 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. @@ -387,13 +386,13 @@ gg <- ggplot(data = scored_clusters_gg3) + # Add the custom linetype values to this plot scale_linetype_manual(values = ltys) + - # Remove the y-axis label + # 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 labels and ticks + # 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(), @@ -405,7 +404,7 @@ gg <- ggplot(data = scored_clusters_gg3) + # Use the new day labels here facet_wrap(~ day_label, nrow = 3, strip.position = "left") + - # Change the aesthetics of the x-axis labels + # Change the aesthetics of the x-axis text scale_x_datetime( date_breaks = "30 mins", date_labels = "%H:%M" @@ -437,14 +436,14 @@ gg <- gg + linewidth = 2, lineend = "round" ) + - # Add the custom colors + # 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 legend. 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 legend, you'll use the argument `legend.margin` inside of the function `theme()`. +You should see that a color legend appears at the top of the plot with the addition of the new `geom_segment()` layer. Both legends are a little messy. You can improve the plot by updating the title of each legend, increasing the size of the legend text, and reducing the white space between the plot and the legends. To modify the legend titles, you'll rely on the functions `guides()` and `guide_legend()`. To increase the legend text size, you'll use the argument `legend.text` inside of the function `theme()`. To reduce white space between the plot and legends, you'll use the argument `legend.margin` inside of the function `theme()`. ```{r} # Check out more information about the function used to control margins (white space) around the legend @@ -452,7 +451,7 @@ You should see that a color legend appears at the top of the plot with the addit gg <- gg + - # Increase the legend text size and reduce white space between the plot and legend + # 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") @@ -526,10 +525,10 @@ ggplot(data = scored_clusters_gg3) + # Add an x-axis title xlab("Time of day (HH:MM)") + - # Remove the y-axis label + # Remove the y-axis title ylab("") + - # Change the aesthetics of the x-axis labels + # Change the aesthetics of the x-axis text scale_x_datetime( date_breaks = "30 mins", date_labels = "%H:%M" @@ -542,7 +541,7 @@ ggplot(data = scored_clusters_gg3) + # 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 + # 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 From a0855e97f81d422195b46a884966eee5218876aa Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Tue, 2 Apr 2024 16:22:31 -0400 Subject: [PATCH 50/69] [PCT-472]: Finished rough translation of sixth vignette --- R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd index d450fa1..6a5f5d9 100644 --- a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd +++ b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd @@ -457,7 +457,6 @@ Ahora deberias de ver que una leyenda de color aparece arriba de la grafica con gg <- gg + # Aumenta el tamano de texto de cada leyenda y reduce el espacio blanco entre la grafica y las leyendas - # 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") @@ -473,26 +472,24 @@ gg ``` -TKTK continue - -Finally, you can save this plot as an image file: +Finalmente puedes guardar esta grafica como un archivo: ```{r} gg -# Save the image file to your computer +# Guarda el archivo con la grafica en tu computadora 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. +Puedes continuar con modificar esteticas menores a este archivo para crear una figura de alta calidad para una publicacion. Por ejemplo, puedes cambiar el tamano final del imagen (`width`, `height`), tanto como la resolution (`dpi`). Puedes tambien cambiar el tamano de texto en cada eje y los titulos de cada eje, o la posicion de la leyenda mientras determinas el tamano final del imagen en el archivo. -This is the all of the code used for the final plot, condensed and reorganized: +Abajo esta todo el codigo que escribiste para la grafica final, en una forma mas condensada y reorganizada: ```{r eval = FALSE} ggplot(data = scored_clusters_gg3) + - # Add a vertical line for each non-perching event assigned to the first individual + # Anade una linea 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"), @@ -501,7 +498,7 @@ ggplot(data = scored_clusters_gg3) + linewidth = 0.5 ) + - # Add a vertical line for each non-perching event that was not assigned to either individual + # Anade una linea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos de los dos individuos geom_segment( data = scored_clusters_gg3 %>% dplyr::filter(individual_initiated == "unassigned"), @@ -510,7 +507,7 @@ ggplot(data = scored_clusters_gg3) + linewidth = 0.5 ) + - # Add the perching events as rounded segments, and now encode color through the column individual_initiated + # Anade los eventos de posar como lineas con orillas redondeadas, y puedes anadir colores que indican la identidad del individuo a traves de la columna individual_initiated geom_segment( data = scored_clusters_gg3 %>% dplyr::filter(!is.na(perching_sensor)), @@ -518,41 +515,40 @@ ggplot(data = scored_clusters_gg3) + linewidth = 2, lineend = "round" ) + - # Add the custom linetype values to this plot + # Anade los tipos de linea customizadas a la grafica scale_linetype_manual(values = ltys) + - # Add the custom colors + # Anade los colores customizados para estos eventos de posar. Los colores tambien aplican a las lineas de movimientos por individuo que no fueron eventos de posar que anadiste en capas anteriores de geom_segment() scale_color_manual(values = cols) + - # Change the legend titles + # Modifica los titulos de cada leyenda guides( linetype = guide_legend(title = "Direction"), color = guide_legend(title = "Individual") ) + - # Add an x-axis title + # Anade un titulo para el eje x xlab("Time of day (HH:MM)") + - # Remove the y-axis label + # Elimina el titulo del eje y ylab("") + - # Change the aesthetics of the x-axis labels + # Cambia la estetica de las etiquetas del eje x 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 + # Crea paneles en la grafica por dia, aqui usaras las etiquetas nuevas de dia facet_wrap(~ day_label, nrow = 3, strip.position = "left") + - # Use this function to convert the plot background to black and white + # Usa esta funcion para convertir el fondo de la grafica a blanco y negro 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 + # Usa estas funciones de estetica para elimninar el text y los rayos del eje y + # Anade un argumento para cambiar la posicion de la leyenda adentro de la grafica + # Puedes quitar la cuadricula en el eje (mayor y menor) adentro de cada panel + # Aumenta el tamano de texto de cada leyenda y reduce el espacio blanco entre la grafica y las leyendas theme( axis.text.y = element_blank(), axis.ticks.y = element_blank(), @@ -565,4 +561,4 @@ ggplot(data = scored_clusters_gg3) + ``` -You completed the final vignette of the ABISSMAL data processing and analysis pipeline. You also practiced your coding and data science skills in a biological context. Well done! It would be very helpful if you could complete the brief Google form for a post-vignette evaluation that will help us continue to improve these vignettes over time. A link to this Google form will be available in the README file for the vignettes. \ No newline at end of file +Acabas de completar el tutorial final del `pipeline` de procesar y analizar datos de ABISSMAL. Tambien practicaste tus habilidades de programar y tus habilidades de la ciencia de datos en un contexto biologico. Muy bien hecho! Nos ayudaria mucho si puedes completar la forma de Google para una evaluacion de estos tutoriales ya que los terminaste. Tus respuestas nos ayudaran mejorar estos tutoriales en el futuro. Un enlace a la forma de Google estara disponible en el archivo README para los tutoriales. \ No newline at end of file From c333b5addd34cca7e0e5fff42898b2e3869fab53 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Tue, 2 Apr 2024 17:04:22 -0400 Subject: [PATCH 51/69] [PCT-472] Changing terms --- R/vignettes/Tutorial_01_Introduccion.Rmd | 8 ++++---- R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd | 4 ++-- R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd | 2 -- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/R/vignettes/Tutorial_01_Introduccion.Rmd b/R/vignettes/Tutorial_01_Introduccion.Rmd index 8698ba8..1d1a95b 100644 --- a/R/vignettes/Tutorial_01_Introduccion.Rmd +++ b/R/vignettes/Tutorial_01_Introduccion.Rmd @@ -18,7 +18,7 @@ Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las Sería una gran ayuda si antes de empezar este tutorial puedes completar una encuesta a través de Google Forms. La información que compartes nos ayudará saber más de tu experiencia en R y analizando datos biológicos antes de empezar los tutoriales mismos, tanto como las habilidades que buscas mejorar a través de completar esta colección de tutoriales, y también nos ayudara mejorar estos tutoriales en el futuro. Puedes encontrar el enlace para la encuesta en el archivo README de los tutoriales. -En este primer tutorial, vas a leer sobre el programa RStudio, aprender como crear una versión local de el repositorio de ABISSMAL en GitHub, y aprender más sobre el flujo de análisis de datos en ABISSMAL. En este tutorial vas a aprender como: +En este primer tutorial, vas a leer sobre el programa RStudio, aprender como crear una versión local de el repositorio de ABISSMAL en GitHub, y aprender más sobre el `pipeline` de analisis de datos en ABISSMAL. En este tutorial vas a aprender como: 1. Configurar tu sesión de RStudio 2. Crear una versión local de un repositorio de GitHub @@ -99,12 +99,12 @@ ABISSMAL provee 5 funciones diferentes de R para procesar y analizar datos, y de 3. `preprocess_detections` usa como entrada los resultados de `combine_raw_data` y remueve detecciones que ocurrieron muy cerca en el tiempo. Esta función devuelve una hoja de cálculo de datos pre-procesados por tipo de sensor, y en cada hoja de cálculo, las detecciones consecutivas deberían de estar separadas por un umbral temporal predeterminado o más. Por ejemplo, cuando usas un umbral de 1 segundo, solo una detección puede ocurrir por segundo en los datos pre-procesados -4. `detect_clusters` usa como entrada los resultados de `preprocess_detections` obtenidos con 2 o más tipos de sensores. La función identifica detecciones a través de 2 o más tipos de sensores que ocurrieron cerca en el tiempo, y devuelve información temporal y metadatos sobre cada grupo o cúmulo. Cada cúmulo de detecciones representa un evento discreto de movimiento de un individuo o más que un individuo +4. `detect_clusters` usa como entrada los resultados de `preprocess_detections` obtenidos con 2 o más tipos de sensores. La función identifica detecciones a través de 2 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 -5. `score_clusters` usa como entrada los resultados de `detect_clusters`. Esta función también puede usar los resultados de `detect_perching_events` para integrar eventos de posar identificados en los datos originales de RFID o los sensores de infrarrojo, y puede usar los resultados de `preprocess_detections` para integrar eventos de grabación de vídeos que no fueron incluidos en los cúmulos detectados por `detect_clusters`. Esta función hace inferencias de comportamiento de los eventos de movimiento, incluyendo la dirección de movimiento, el magnitud de movimiento, identidad de individuo (cuando datos de RFID se encontraron en un cúmulo), y donde ocurrir 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 comportamientos y otros metadatos sobre cada evento de movimiento, y estos resultados se pueden usar para visualizaciones y análisis estadísticos. +5. `score_clusters` usa como entrada los resultados de `detect_clusters`. Esta función también puede usar los resultados de `detect_perching_events` para integrar eventos de posar identificados en los datos originales de RFID o los sensores de infrarrojo, y puede usar los resultados de `preprocess_detections` para integrar 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, el magnitud de movimiento, identidad de individuo (cuando datos de RFID se encontraron en un `cluster`), y donde ocurrir 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 comportamientos y otros metadatos sobre cada evento de movimiento, y estos resultados se pueden usar para visualizaciones y análisis estadísticos.
-![Figure 4 del manuscrito de ABISSMAL con un flujo general de trabajo a traves de las 5 funciones primarias](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/Figure4_ComputationalAnalyses.png) +![Figure 4 del manuscrito de ABISSMAL con un `pipeline` con las 5 funciones primarias](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/Figure4_ComputationalAnalyses.png) * Esta figura es de Smith-Vidaurre, G., Molina, T., Jarvis, E.D., and E.A. Hobson. 2023. Automated tracking of avian parental care. [EcoEvoRxiv preprint]((https://ecoevorxiv.org/repository/view/6268/)).

Solucionar errores en linea

diff --git a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd index bc740f4..a5338dc 100644 --- a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd +++ b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd @@ -22,7 +22,7 @@ Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las

Resumen del tutorial y objetivos de aprendizaje

-En este quinto tutorial, vas a empezar a usar las detecciones simuladas de movimientos de animales en el flujo de analisis de datos de ABISSMAL, incluyendo combinar los datos originales a traves de dias y procesar o limpiar los datos originales. Tambien vas a crear graficas de los datos procesados. Vas a continuar a usar habilidades que aprendiste en los tutoriales anteriores, y vas a aprender nuevas habilidades que incluyen: +En este quinto tutorial, vas a empezar a usar las detecciones simuladas de movimientos de animales en el `pipeline` de analisis de datos de ABISSMAL, incluyendo combinar los datos originales a traves de dias y procesar o limpiar los datos originales. Tambien vas a crear graficas de los datos procesados. Vas a continuar a usar habilidades que aprendiste en los tutoriales anteriores, y vas a aprender nuevas habilidades que incluyen: 1. Acceder funciones customizadas 2. Usar funciones customizadas @@ -167,7 +167,7 @@ View(perching) La informacion arriba te dice que hubo dos eventos de posar a las 8:00 cada dia, y dos eventos de posar a las 11:30 cada dia (como esperamos). La etiqueta de PIT para cada individuo se detecto una vez por dia, por ende, cada individuo realizo un evento de posar cada dia. -Detectar eventos de posar no es un requisito en el flujo de analisis de ABISSMAl, pero puede ser un paso util para obtener la mayor cantidad de informacion que puedes de los datos originales antes de filtrar detecciones en el siguiente paso de procesar o limpiar los datos originales. +Detectar eventos de posar no es un requisito en el `pipeline` de analisis de ABISSMAl, pero puede ser un paso util para obtener la mayor cantidad de informacion que puedes de los datos originales antes de filtrar detecciones en el siguiente paso de procesar o limpiar los datos originales.

Procesa los datos originales

diff --git a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd index 6a5f5d9..d8319fc 100644 --- a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd +++ b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd @@ -16,8 +16,6 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE) ``` -TKTK terms to replace in previous translations (flujo de analisis = pipeline, cumulo = cluster) -

Información sobre esta traducción

Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos arriba para reportar un "Issue". From 08ca234390fd2e0040caa22e1b6ff573310bb44d Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Wed, 3 Apr 2024 11:50:26 -0400 Subject: [PATCH 52/69] [PCT-472]: Final automated spellcheck --- R/vignettes/Tutorial_03_SimularDatos.Rmd | 156 ++++++------ R/vignettes/Tutorial_04_GuardarDatos.Rmd | 236 +++++++++--------- ...utorial_05_ProcesarDatos_CrearGraficas.Rmd | 172 ++++++------- R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd | 216 ++++++++-------- 4 files changed, 389 insertions(+), 391 deletions(-) diff --git a/R/vignettes/Tutorial_03_SimularDatos.Rmd b/R/vignettes/Tutorial_03_SimularDatos.Rmd index caa8ef9..254b339 100644 --- a/R/vignettes/Tutorial_03_SimularDatos.Rmd +++ b/R/vignettes/Tutorial_03_SimularDatos.Rmd @@ -22,9 +22,9 @@ Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las

Resumen del tutorial y objetivos de aprendizaje

-En este tercer tutorial, vamos a crear datos simulados de moviemientos de animals que fueron detectados por diferentes sensores. El proceso de simular estos datos reemplaza el proceso de coleccion de datos que provee ABISSMAL para grabar datos de animales en vivo. Generar estos datos simulados te va a proveer mas oportunidades para practicar habilidades basicas de escribir codigo y tener control sobre la creacion de estos datos te ayudara entender los pasos diferentes de analisis de datos que siguen. Si quieres ver datos recolectados de pajaros con el software de ABISSMAL, y el codigo que fue usado para analizar esos datos, puedes revisar el preprint del [manuscrito de metodos](https://ecoevorxiv.org/repository/view/6268/) que tiene enlaces a los datos y el codigo que son publicamente acesibles. +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 mas oportunidades para practicar habilidades básicas de escribir código y tener control sobre la creación de estos datos te ayudara entender los pasos diferentes de análisis de datos que siguen. Si quieres ver datos recolectados de pájaros con el software de ABISSMAL, y el código que fue usado para analizar esos datos, puedes revisar el preprint del [manuscrito de metodos](https://ecoevorxiv.org/repository/view/6268/) que tiene enlaces a los datos y el código que son públicamente accesibles. -A traves del proceso de simular datos en este tutorial, vas a continuar usando habilidades de programacion que aprendiste en el segundo tutorial, y vas a aprender habilidades adicionales que incluyen: +A través del proceso de simular datos en este tutorial, vas a continuar usando habilidades de programación que aprendiste en el segundo tutorial, y vas a aprender habilidades adicionales que incluyen: 1. Crear objetos como vectores y `dataframes` 2. Tipos de datos en R @@ -34,48 +34,48 @@ A traves del proceso de simular datos en este tutorial, vas a continuar usando h

Cargar paquetes

-En el tutorial anterior instalaste el `tidyverse`, una coleccion de paquetes para la ciencia de datos. Tambien aprendiste sobre directorios de trabajo y creaste un directorio nuevo en tu computadora para guardar archivos de datos o imagenes que vas a generar en los siguientes tutoriales. +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 codigo que viste en el segundo tutorial, pero ahora todo el codigo esta combinado en un solo trozo. +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 esta combinado en un solo trozo. ```{r message = FALSE, warning = FALSE} rm(list = ls()) # Limpia tu ambiente global -library(tidyverse) # Carga la coleccion de paquetes en el tidyverse +library(tidyverse) # Carga la colección de paquetes en el tidyverse ```

Crear un objeto de `path`

-El siguiente paso sera especificar tu directorio de trabajo. En el segundo tutorial, usaste un `string`, o una secuencia de caracteres entrecomillas para indicar texto adentro de codigo 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 mas 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`. +El siguiente paso sera 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 mas 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 simbolos para crear un objeto en R (`<-`), y luego la informacion que quieres asignar a este objeto. En este case, la informacion que quieres guardar adentro de este objeto es tu directorio de trabajo en el formato de un `string`, y esta informacion necesita estar entrecomillas. +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 case, la información que quieres guardar adentro de este objeto es tu directorio de trabajo en el formato de un `string`, y esta información necesita estar entrecomillas. ```{r eval = TRUE} path <- "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales" ``` -En el codigo arriba, creaste un objeto que se llama `path`. Puedes ver la informacion que contiene este objeto con escribir el nombre del objeto y ejecutar ese codigo en la consola: +En el código arriba, creaste un objeto que se llama `path`. Puedes ver la información que contiene este objeto con escribir el nombre del objeto y ejecutar ese código en la consola: ```{r eval = TRUE} path ``` -Tambien puedes ver el contenido de `path` con hacer clic en la pestana de "Environment" y revisar la columna al lado derecho del nombre del objeto. Ahora puedes confirmar que `path` es un objeto nuevo que contiene informacion sobre tu directorio de trabajo y esta disponible en tu ambiente global para mas operaciones. +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 esta disponible en tu ambiente global para mas operaciones. -Puedes practicar eliminar solamente el objeto `path` de tu ambiente global con escribir y ejecutar el codigo `rm(list = "path")`. Despues de ejecutar este codigo, deberias de inicializar `path` otra vez usando el codigo arriba. +Puedes practicar eliminar solamente el objeto `path` de tu ambiente global con escribir y ejecutar el código `rm(list = "path")`. Después de ejecutar este código, deberías de inicializar `path` otra vez usando el código arriba.

Crear marcas de tiempo para detecciones simuladas de movimientos

-Los datos primarios que colecta ABISSMAL son marcas de tiempo que indican el momento en el tiempo cuando un sensor se activo y grabo movimiento. Muchas (pero no todas) de estas detecciones se pueden asignar a uno o mas animales que se movieron cerca de un sensor, por ejemplo, cuando un pajaro entra a un contenedor de nido a traves de una antena circular de RFID ("radio frequency identification") montado en la entrada del contenedor. En los siguientes trozos de codigo, vamos a generar datos simulados que representan datos de detecciones grabados por el sensor de RFID y tambien sensores de infrarrojo. +Los datos primarios que colecta ABISSMAL son marcas de tiempo que indican el momento en el tiempo cuando un sensor se activo y grabo movimiento. Muchas (pero no todas) de estas detecciones se pueden asignar a uno o mas 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 2 pajaros adultos a traves de sensores de ABISSMAL montados en un contenedor de nido. La antena de RFID esta montada en la entrada del contenedor, y un par de sensores de infrarrojo, el par "externo", esta 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", esta montado detras 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 pajaro entra al contenedor de nido, luego la antena de RFID, y luego el par interno de sensores infrarrojos. Cuando un pajaro sale del contenedor, el par interno de sensores infrarrojos deberia de activar primero, luego la antena de RFID, y luego el par externo de sensores infrarrojos. +Digamos que estamos recolectando datos para 2 pájaros adultos a través de sensores de ABISSMAL montados en un contenedor de nido. La antena de RFID esta montada en la entrada del contenedor, y un par de sensores de infrarrojo, el par "externo", esta 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", esta 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 infrarrojos. Cuando un pájaro sale del contenedor, el par interno de sensores infrarrojos debería de activar primero, luego la antena de RFID, y luego el par externo de sensores infrarrojos. -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 estara entrecomillas para indicar que estamos usando informacion de text o secuencia de caracteres en el formato `string` de R. +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 funcion `c()`. Esta function 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 tenia un solo valor o elemento. Usar `c()` facilita combinar multiple valores en un objeto como un vector que puede tener multiple elementos. +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 tenia un solo valor o elemento. Usar `c()` facilita combinar múltiple valores en un objeto como un vector que puede tener múltiple elementos. ```{r} # Crea un vector de cuatro marcas de tiempo de RFID o cuatro elementos in formato HH:MM:SS @@ -105,26 +105,26 @@ i_irbb_ts <- c("10:00:01", "10:04:59", "11:00:01", "11:04:59") # interno ``` -Los pajaros a veces posan en la entrada del contenedor usando la antena de RFID como percha, y los sensores deberian de colectar datos sobre este comportamiento. Puedes anadir eventos de posar a los datos simulados, y aqui vas a simular eventos de posar solamente con los datos de RFID. +Los pájaros a veces posan en la entrada del contenedor usando la antena de RFID como percha, y los sensores deberían de colectar datos sobre este comportamiento. Puedes añadir eventos de posar a los datos simulados, y aquí vas a simular eventos de posar solamente con los datos de RFID. ```{r} rfid_ts <- c(rfid_ts, "08:00:00", "08:00:01", "08:00:02", "08:00:03", "11:30:00", "11:30:01", "11:30:02", "11:30:03", "11:30:04", "11:30:05") ``` -En el codigo arriba, modificaste el objeto `rfid_ts` con la funcion `c()` para anadir diez mas marcas de tiempo a este vector para tener un total de 14 elementos. Revisa la estructura del objeto modificado de `rfid_ts` usando la funcion `glimpse()`: +En el código arriba, modificaste el objeto `rfid_ts` con la función `c()` para añadir diez mas marcas de tiempo a este vector para tener un total de 14 elementos. Revisa la estructura del objeto modificado de `rfid_ts` usando la función `glimpse()`: ```{r} -# Aqui 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 numero de elementos ([1:14]), y los valores de los primeros elementos del vector +# 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 numero de elementos ([1:14]), y los valores de los primeros elementos del vector glimpse(rfid_ts) ``` -Otro tipo de informacion 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 pajaros dejan materal de nido colgando en la entrada del contenedor. En ambos casos, los sensores infrarrojos deberian de activar pero no la antena de RFID. +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 infrarrojos deberían de activar pero no la antena de RFID. ```{r} -# Simula unas fallas de deteccion de la antena de RFID a traves de ambos pares de sensore infrarrojos -# Estas fallas en deteccion del sensor de RFID surgio en cuatro eventos simulados adicionales (dos entradas y dos salidas) +# Simula unas fallas de detección de la antena de RFID a través de ambos pares de sensores infrarrojos +# Estas fallas en detección del sensor de RFID surgió 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") @@ -138,109 +138,109 @@ glimpse(o_irbb_ts) ``` -Acabas de crear datos simulados de movimientos de animales con unas detecciones que representan errores de deteccion, pero por el momento estos datos se encuentran en diferentes vectores separados y les faltan metadatos muy importantes. Por ejemplo, metadatos utiles que nos faltan incluyen informacion sobre la replica experimental, la fecha, y para los datos de RFID, el codigo alfanumerico unico de cada etiqueta PIT que fue detectada por la antena de RFID. Algunos de estos metadatos son criticos para los analisis mas adelante, como la fecha y los codigos de las etiquetas PIT. +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 replica 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 mas adelante, como la fecha y los códigos de las etiquetas PIT. -Los vectores son estructras utiles en R, pero una limitacion de los vectore es que no puedes combinar diferentes tipos de datos en un solo objeto. Puedes intentar combinar diferentes tipos de datos en un vector: +Los vectores son estructuras útiles en R, pero una limitación de los vectores es que no puedes combinar diferentes tipos de datos en un solo objeto. Puedes intentar combinar diferentes tipos de datos en un vector: ```{r} -# Crea un vector con los tipos `character`, `numeric`, y `binary` (o sea, datos de texto, valores numericos, y valores binarios) -# El resultado se deberia de imprimir directamente a la consola porque no estas guardando el resultado en un objeto -# Deberias de ver que todos los elementos se forzan a tipo `character` o `string` entrecomillas +# 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á al tipo `character` o `string` entrecomillas c("1", 1, TRUE, FALSE) # Ahora crea un vector con datos `numeric` y `binary` -# Deberias de poder ver que todos los elementos se forzan al tipo `numeric`. Los valores de TRUE y FALSE se conviertieron a los valores numericos que R usa por defecto para guardar informacion binaria (TRUE se convierte a 1, y FALSE se convierte a 0) +# Deberías de poder ver que todos los elementos se forzará al tipo `numeric`. Los valores de TRUE y FALSE se convirtieron a los valores numéricos que R usa por defecto para guardar información binaria (TRUE se convierte a 1, y FALSE se convierte a 0) c(1, 1, TRUE, FALSE) ``` -Cuando intentas combinar los tipos de dato `character`, `numeric`, y `binary` en el mismo vector, todos los elementos del vector se convierten al tipo de 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 numerico. En este ejemplo, tambien aprendiste que los valores binarios TRUE y FALSE en R son equivalentes a los valores numericos 1 y 0, respectivamente. +Cuando intentas combinar los tipos de dato `character`, `numeric`, y `binary` en el mismo vector, todos los elementos del vector se convierten al tipo de data `character`. Algo parecido resulta cuando intentas combinar datos de tipo `numeric` y `binary` en el mismo vector, pero en este caso, los valores se convierten a numérico. En este ejemplo, también aprendiste que los valores binarios TRUE y FALSE en R son equivalentes a los valores numéricos 1 y 0, respectivamente.

Crear vectores de metadatos

-Para los analsis 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 informacion sobre la replica experimental, la fecha, y la identidad de la etiqueta PIT para cada deteccion. +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 replica experimental, la fecha, y la identidad de la etiqueta PIT para cada detección. -Para crear un vector de metadatos sobre la replica experimental, vas a usar la funcion `rep()` para repetir la informacion sobre la replica experimental automaticamente, en vez de copiar y pegar las misma informacion varias veces. Para configurar el numero de veces que la informacion sobre la replica experimental se vaya a repetir, tambien vas a usar la funcion `length()` para calcular lo largo del vector `rfid_ts` automaticamente, y comunicarle este resultado a `rep()`. Crear un vector de metadatos que tiene el mismo largo que el vector de `rfid_ts` sera util para combinar estos vectores en un solo objeto mas adelante. +Para crear un vector de metadatos sobre la replica experimental, vas a usar la función `rep()` para repetir la información sobre la replica experimental automáticamente, en vez de copiar y pegar las misma información varias veces. Para configurar el numero de veces que la información sobre la replica 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` sera útil para combinar estos vectores en un solo objeto mas adelante. ```{r} -# La documentacion nos dice que rep() espera dos argumentos, `x` y `time` +# La documentación nos dice que rep() espera dos argumentos, `x` y `time` ?rep -# Crea un vector con informacion sobre la replica experimental -# El argumento `x` contiene la informacion de metadatos que se va a repetir -# El argumento `times` especifica la cantidad de veces que esta informacion se va a repetir +# Crea un vector con información sobre la replica 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) -# Tambien puedes ejecutar codigo sin escribir los nombres de los argumentos, siempre y cuando los argumentos se escriben en el mismo orden que la funcion espera: +# También puedes ejecutar código sin escribir los nombres de los argumentos, siempre y cuando los argumentos se escriben en el mismo orden que la función espera: exp_rep <- rep("Pair_01", length(rfid_ts)) glimpse(exp_rep) ``` -Usar `times = length(rfid_ts)` es mejor costumbre que configur lo largo de `rfid_ts` mmanualmente (por ejemplo, `times = 14`). Configurar el valor de `times` manualmente es suponer que el objeto `rfid_ts` no ha cambiado dentro de una sesion de escribir codigo, o entre sesiones diferentes, y esto puede ser una suposicion peligrosa. Cuando usas `times = length(rfid_ts)` te aseguras que el codigo arriba va a crear un vector de metadatos del mismo largo que `rfid_ts` sin importar que tantas modificaciones le hayas hecho a `rfid_ts` en el codigo arriba. +Usar `times = length(rfid_ts)` es mejor costumbre 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 dentro 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 que tantas modificaciones le hayas hecho a `rfid_ts` en el código arriba. -Puedes tambien 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 utiles para revisar suposiciones en tu codigo, o para construir nuevos datos y funciones. +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. ```{r} -# Si esta condicion se cumple, el resultado en la consola deberia de ser "[1] TRUE" +# Si esta condición se cumple, el resultado en la consola debería de ser "[1] TRUE" length(rfid_ts) == length(exp_rep) ``` -En la frase condicional arriba, estas usando los simbolos `==` para preguntar si los dos vectores `rfid_ts` y `exp_rep` tienen la misma cantidad de elementos (si tienen el mismo largo). +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). -Tambien puedes usar los simbolos `!=` para preguntar si los dos vectores `rfid_ts` y `exp_rep` *no* tienen la misma cantidad de elementos (si *no* 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 (si *no* tienen el mismo largo): ```{r} -# El resultado de esta frase deberia de ser FALSE, porque estos vectores 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) ``` -Como un ejemplo, puedes modificar `rfid_ts` para que tenga un numero de elementos diferente a `exp_rep`. Abajo puedes ver algunas formas diferentes de filtrar o eliminra cuatro elementos del vector `rfid_ts` para que tenga diez elementos total. +Como un ejemplo, puedes modificar `rfid_ts` para que tenga un numero 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 total. ```{r} -## Crear indices numericos para filtrar un objeto +## Crear indices numéricos para filtrar un objeto -# Puedes usar el simbolo `:` para crear una secuencia de numeros de los indices 5 a lo largo de rfid_ts +# Puedes usar el símbolo `:` para crear una secuencia de números de los indices 5 a lo largo de rfid_ts 5:length(rfid_ts) -# Tambien puedes usar la funcion `seq()` para crear la misma secuencia de indices numericos que ves arriba +# También puedes usar la función `seq()` para crear la misma secuencia de indices numéricos que ves arriba seq(from = 5, to = length(rfid_ts), by = 1) -# Si quieres filtrar elementos no consecutivos, puedes crear un vector de indices con la funcion `c()` +# Si quieres filtrar elementos no consecutivos, puedes crear un vector de indices con la función `c()` c(1, 3, 5, 6, 8, 10, 11, 12, 13, 14) -## Filtrar un vector por indices numericos +## Filtrar un vector por indices numéricos -# Cuando insertas cualquiera de las expresiones arriba adentro de los corchetes que vienen despues del nombre del vector, puedes seleccionar los elementos de indice cinco a lo largo de rfid_ts, y eliminar los primeros cuatro elementos +# Cuando insertas cualquiera de las expresiones arriba adentro de los corchetes que vienen después del nombre del vector, puedes seleccionar los elementos de indice cinco a lo largo de rfid_ts, y eliminar los primeros cuatro elementos rfid_ts[5:length(rfid_ts)] rfid_ts[seq(from = 5, to = length(rfid_ts), by = 1)] rfid_ts[c(1, 3, 5, 6, 8, 10, 11, 12, 13, 14)] -# Puedes usar cualquier de los metodos arriba para crear una secuencia de indices que quieres eliminar, y luego usar el simbolo `-` adentro de los corchetes para eliminar los elementos en esos indices particulares. Por ejemplo: -rfid_ts[-c(1:4)] # los numeros deberian de estar adentro de la funcion `c()` para que este tipo de filtrar de forma invertida funcione +# Puedes usar cualquier de los métodos arriba para crear una secuencia de indices que quieres eliminar, y luego usar el símbolo `-` adentro de los corchetes para eliminar los elementos en esos indices particulares. Por ejemplo: +rfid_ts[-c(1:4)] # los números deberían de estar adentro de la función `c()` para que este tipo de filtrar de forma invertida funcione ``` -Luego puedes revisar si esta version modificada de `rfid_ts` es el mismo largo que `exp_rep`: +Luego puedes revisar si esta versión modificada de `rfid_ts` es el mismo largo que `exp_rep`: ```{r} -# Esta frase deberia de resultar en TRUE, porque estos vectores ya no tienen el mismo largo +# 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) ```

Crear `dataframes` con datos primarios y metadatos

-Es importante combinar los metadatos con los datos primarios para analisis futuros, y puedes combinarlos usando un tipo de objeto que se llama un `dataframe`. Los `dataframes` son parecidos a las hojas de calculo porque tienen dos dimensiones (filas y columnas), y puedes guardar multiples tipos de datos diferentes en el mismo `dataframe`. Tambien puedes guardar `dataframes` en hojas de calculo o archivos fisicos en tu computadora. +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 calculo 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 calculo 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, deberias de recibir un mensaje de error en la consola especificando que los dos argumentos no tienen el mismo numero de filas: +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 numero de filas: ```{r eval = FALSE} sim_dats <- data.frame(exp_rep, rfid_ts[-c(1:4)]) @@ -259,9 +259,9 @@ glimpse(sim_dats) ``` -Cuando revisas la estructura del objeto `sim_dats`, el `dataframe` nuevo, puedes ver que tiene 14 filas y 2 columnas. Para cada columna (despues del simbolo de "$"), puedes ver que el nombre de la columna (aqui `exp_rep` y `rfid_ts`), el tipo de dato en cada columna (en este momento cada column es del tipo `character`), y luego los valores de cada columna en las primeras filas del `dataframe`. +Cuando revisas la estructura del objeto `sim_dats`, el `dataframe` nuevo, puedes ver que tiene 14 filas y 2 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 anadir un nombre nuevo y el simbolo `=` antes de cada vector. Abajo el vector `exp_rep` se convierte en la column `replicate` con la replica experimental y el vector `rfid_ts` se convierte en la columna `timestamps` con las marcas de tiempo. +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 replica experimental y el vector `rfid_ts` se convierte en la columna `timestamps` con las marcas de tiempo. ```{r} sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) @@ -270,7 +270,7 @@ glimpse(sim_dats) ``` -Podemos anadir metadatos adicionales a este `dataframe`, como informacion sobre la fecha de coleccion 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`. +Podemos añadir metadatos adicionales a este `dataframe`, como información sobre la fecha de colección de datos. Puedes primero añadir una columna para el año usando el tipo de datos `double` en R que es un tipo de dato `numeric`. ```{r} sim_dats <- cbind(sim_dats, rep(2023, length(rfid_ts))) @@ -279,19 +279,19 @@ glimpse(sim_dats) ``` -La columna del año tiene un nombre extraño cuando añadimos una nueva columna usando `cbind()`, y si revisas el codigo de arriba, puedes ver que el nombre de esta columna se parece mucho al codigo que escribiste adentro de `cbind()`. Puedes usar la funcion `names()` e indexar con corchetes para cambiar el nombre extraño a un nombre mejor, como "year": +La columna del año tiene un nombre extraño cuando añadimos una nueva columna usando `cbind()`, y si revisas el código de arriba, puedes ver que el nombre de esta columna se parece mucho al código que escribiste adentro de `cbind()`. Puedes usar la función `names()` e indexar con corchetes para cambiar el nombre extraño a un nombre mejor, como "year": ```{r} -# Esta funcion devuelve un vector de los nombres de las columnas del `dataframe` +# Esta función devuelve un vector de los nombres de las columnas del `dataframe` names(sim_dats) -# Usa indexar con corchetes y la funcion `ncol()` para encontrar el ultimo nombre entre los nombres de todas las columnas, porque esta ultima columna contiene la informacion sobre el año +# Usa indexar con corchetes y la función `ncol()` para encontrar el ultimo nombre entre los nombres de todas las columnas, porque esta ultima columna contiene la información sobre el año ncol(sim_dats) # Hay 3 columnas en este `dataframe` -# Esta expresion devuelve el nombre de la ultima columna +# Esta expresión devuelve el nombre de la ultima columna names(sim_dats)[ncol(sim_dats)] -# Puedes sobreescribir el nombre de la ultima columna con un nombre nuevo +# Puedes sobrescribir el nombre de la ultima columna con un nombre nuevo names(sim_dats)[ncol(sim_dats)] <- "year" # Confirma que el nombre de la columna de año se actualizo de la forma que esperas @@ -302,13 +302,13 @@ glimpse(sim_dats)

Crear `dataframes` usando el `tidyverse`

-En el codigo arriba, usaste codigo de R base para anadir una columna nueva y actualizar el nombre de esa columna. Te tomo varias lineas de codigo para completar estas operaciones. Puedes reducir la cantidad de codigo que necesitas para estos pasos si eliminas las lineas de codigo que usaste para revisar las operaciones. Pero otra forma para reducir la cantidad de codigo que escribes para esta serie de operaciones es usar la notacion y coleccion de funciones del `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 tomo varias lineas de código para completar estas operaciones. Puedes reducir la cantidad de código que necesitas para estos pasos si eliminas las lineas de código que usaste para revisar las operaciones. Pero otra forma para reducir la cantidad de código que escribes para esta serie de operaciones es usar la notación y colección de funciones del `tidyverse`: ```{r} # Crear el `dataframe` de nuevo con dos columnas sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) -# Usa el tidyverse para anadir el año como la tercera columna +# 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)) @@ -318,11 +318,11 @@ glimpse(sim_dats) ``` -Acabas de anadir una columna para el año con el nombre correcto en menos lineas de codigo. En la notacion del `tidyverse`, el simbolo de `%>%` significa una operacion de `pipe`, en que usas el objeto antes del simbolo de `%>%` como entrada para la operacion o funcion que sigue el simbolo de `%>%`. Arriba usaste el objeto de `sim_dats` como entrada para la funcion `mutate()`, que usaste para crear la columna `year`. +Acabas de añadir una columna para el año con el nombre correcto en menos lineas 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 notacion `dplyr::` antes de `mutate()` indica que la funcion `mutate()` se deberia de acceder desde el paquete que se llama `dplyr`. Incluir el nombre del paquete con dos puntos repetidos dos veces es una notacion importante para usar cuando hay multiples funciones accesibles en tu ambiente global con el mismo nombre. Por ejemplo, si usas otros paquetes aparte de `dplyr` que tambien tienen funciones que se llaman `mutate()`, y no especificas cual paquete quieres usar, puedes terminar con errores inmediatos (como cuando el codigo no se puede ejecutar). Incluso si el codigo ejecuta, usar la operacion equivocada de otra funcion de `mutate()` puede introducir errores a tus analisis mas adelante que son dificiles de identificar. +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 para 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 mas adelante que son difíciles de identificar. -Las oparaciones de `piping` con el simbolo `%>%` (o un `pipe`) pueden simplicar el codigo que escribes porque no creas tantos objetos intermedios como cuando usas R base. Por otro lado, por esta misma razon puede tomar practica solucionar errors con operaciones de `piping` cuando estas operaciones son largas y anidadas. Una forma util para revisar resultados intermedios adentro de operaciones largas de `piping` es incluir la funcion `glimpse()` entre diferentes pasos de la operacion: +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 practica 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: ```{r} # Crear el data frame otra vez con solo dos columnas @@ -330,36 +330,36 @@ 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 version de sim_dats + glimpse() %>% # Ver la estructura de la primera versión de sim_dats dplyr::mutate( - # La expresion nrow(.) significa "obtener el numero de filas para el objeto actual". El objeto en este caso es sim_dats + # La expresión nrow(.) significa "obtener el numero de filas para el objeto actual". El objeto en este caso es sim_dats year = rep(2023, nrow(.)) ) %>% - glimpse() # Ver la estructura de la version mas reciente de sim_dats con la columna nueva de "year" + glimpse() # Ver la estructura de la versión mas reciente de sim_dats con la columna nueva de "year" ``` -En el codigo arriba, tambien aprendiste una forma nueva para repetir un valor adentro de una operacion de `piping` con la notacion `.` adentro de una funcion, que significa que la operacion ejecutara con el objeto actual. En el ejemplo arriba, `.` se refiere al objeto `sim_dats` que se uso como entrada para la operacion entera de `piping`. Como el simbolo `.` esta adentro de la funcion `nrow()`, la funcion deberia de devolver el numero de filas de `sim_dat`. +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 ejecutara con el objeto actual. En el ejemplo arriba, `.` se refiere al objeto `sim_dats` que se uso como entrada para la operación entera de `piping`. Como el símbolo `.` esta adentro de la función `nrow()`, la función debería de devolver el numero de filas de `sim_dat`. -Puedes usar una operacion parecida para anadir dos columnas al `dataframe` que contienen informacion del mes y dia: +Puedes usar una operación parecida para añadir dos columnas al `dataframe` que contienen información del mes y día: ```{r} -# Usar el tidyverse para anadir el año como la tercera columna +# Usar el tidyverse para añadir el año como la tercera columna sim_dats %>% - glimpse() %>% # Ver la estructura de la version original de sim_dats + glimpse() %>% # Ver la estructura de la versión original de sim_dats dplyr::mutate( year = 2023 ) %>% - glimpse() %>% # Ver la estructura de la version intermedia de sim_dats con la nueva columna del año - # Tambien puedes anadir columnas para el mes ("month") y dia ("day") + 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 version final de sim_dats con las columnas adicionales con el mes y el dia + glimpse() # Ver la estructura de la versión final de sim_dats con las columnas adicionales con el mes y el día ``` -En el codigo arriba, anadiste dos columnas numericas mas al `dataframe` y lo hiciste sin necesitar usar la funcion `rep()` para repetir valores. Esto fue posible porque usaste un `dataframe` que ya existia como la entrada a las operaciones de `dplyr::mutate()`, y el unico valor que especificaste para cada columna nueva del año, mes, y dia se repitio automaticamente para llenar todas las filas en el data frame para cada columna. Especificar un solo valor para una nueva columna puede ayudar reducir la cantidad de codigo que escribes, pero solo cuando de verdad quieres que el mismo valor se repite por todas las filas del `dataframe`. +En el código arriba, añadiste dos columnas numéricas mas 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 data frame para cada columna. Especificar un solo valor para una nueva columna puede ayudar reducir la cantidad de código que escribes, pero solo 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 codigo 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 calculo como archivos fisicos en tu computadora. \ No newline at end of file +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 calculo como archivos físicos en tu computadora. \ No newline at end of file diff --git a/R/vignettes/Tutorial_04_GuardarDatos.Rmd b/R/vignettes/Tutorial_04_GuardarDatos.Rmd index 8815d5a..6cd3b0c 100644 --- a/R/vignettes/Tutorial_04_GuardarDatos.Rmd +++ b/R/vignettes/Tutorial_04_GuardarDatos.Rmd @@ -26,7 +26,7 @@ En este cuarto tutorial, vas a guardar hojas de calculo de las detecciones simul 1. Indexar y filtrar `dataframes` 2. Revisar la estructura de `dataframes` con R base y el `tidyverse` -3. Guardar objetos de R como archivos fisicos en tu computadora +3. Guardar objetos de R como archivos físicos en tu computadora 4. Leer archivos de tu computadora a R 5. Escribir y probar bucles @@ -36,36 +36,36 @@ En este cuarto tutorial, vas a guardar hojas de calculo de las detecciones simul rm(list = ls()) # Limpia tu ambiente global -library(tidyverse) # Carga la coleccion de paquetes del tidyverse +library(tidyverse) # Carga la colección de paquetes del tidyverse -path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Inicializar un objeto con el path de tu directorio de trabajo +path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Inicializa un objeto con el path de tu directorio de trabajo ```

Crear los datos simulados

-En el codigo abajo, vas a recrear los datos simulados de RFID y sensores infrarrojos del tutorial anterior. Aqui estamos combinando el codigo en menos trozos comparado con el tercer tutorial: +En el código abajo, vas a recrear los datos simulados de RFID y sensores infrarrojos del tutorial anterior. Aquí estamos combinando el código en menos trozos comparado con el tercer tutorial: ```{r} # Crea un vector de cuatro marcas de tiempo de RFID en formato HH:MM:SS rfid_ts <- c("10:00:00", "10:05:00", "11:00:00", "11:05:00") -# Anade eventos de posar a los datos de RFID +# 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) ``` -Aqui "IRBB" significa "infrared beam breakers" o sensores de infrarrojo. +Aquí "IRBB" significa "infrared beam breakers" o sensores de infrarrojo. ```{r} # Simula marcas de tiempo para los pares externos ("o_") e internos ("i_") de sensores infrarrojos 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 deteccion por la antena de RFID en las marcas de tiempo de cada par de sensores infrarrojos -# Estos errores de deteccion surgieron en cuatro movimientos adicionales: dos entradas y dos salidas +# Simula unos errores de detección por la antena de RFID en las marcas de tiempo de cada par de sensores infrarrojos +# 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") @@ -77,9 +77,9 @@ glimpse(i_irbb_ts) ``` -

Simula tres dias de coleccion de datos de RFID

+

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

-En el codigo abajo, vas a combinr 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 dia, y tambien una columna con los valores de dos etiquetas PIT (una etiqueta por individuo simulado), y una columna con informacion sobre el tipo de sensor. Deberias de reconocer partes de este codigo desde el tutorial anterior: +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: ```{r} # Crea un vector para la replica experimental @@ -87,13 +87,13 @@ 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 (4 detecciones) al primer individuo, y el segundo evento de posar (6 detecciones) al segundo individuo -# Estas tres expresiones de rep() estan combinadas en un solo vector usando la funcion c() +# 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 replica experimental y las marcas de tiempo sim_dats_rfid <- data.frame(chamber_id = exp_rep, timestamps = rfid_ts) -# Sobrescribe el datafram con la version modificada que tiene columnas para el año, el mes, y el dia +# 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 @@ -102,7 +102,7 @@ sim_dats_rfid <- sim_dats_rfid %>% month = 08, day = 01 ) %>% - # Anade los metadatos de las etiquetas PIT en una columna nueva + # Añade los metadatos de las etiquetas PIT en una columna nueva dplyr::mutate( PIT_tag = PIT_tag ) %>% @@ -114,9 +114,9 @@ glimpse(sim_dats_rfid) ``` -Ahora puedes usar este `dataframe` para simular el proceso de coleccion de datos a traves de dos dias mas. Para crear mas observaciones (filas) para dos dias adicionales, puedes adjuntar filas de una copia modificada de `sim_dats_rfid` al objeto original de `sim_dats_rfid`. +Ahora puedes usar este `dataframe` para simular el proceso de colección de datos a través de dos días mas. Para crear mas 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 codigo abaho, estas usando una operacion `pipe` para usar `sim_dats_rfid` como entrada en `bind_rows()`, y con esta notacion estas especificando que `sim_dats_rfid` es el objeto original al cual quieres adjuntar mas filas. Luego el codigo adentro de `bind_rows()` especifica el `dataframe`, o las filas nuevas, que quieres adjuntar a `sim_dats_rfid`. En este case, el codigo adentro de `bind_rows()` provee `sim_dats_rfid` a `dplyr::mutate()` para modificar la columna de `day` para representar un dia adicional de coleccion de datos. Luego repites este proceso para anadir un tercer dias de recolectar datos: +En el código abajo, estas usando una operación `pipe` para usar `sim_dats_rfid` como entrada en `bind_rows()`, y con esta notación estas especificando que `sim_dats_rfid` es el objeto original al cual quieres adjuntar mas 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` para representar un día adicional de colección de datos. Luego repites este proceso para añadir un tercer días de recolectar datos: ```{r} sim_dats_rfid <- sim_dats_rfid %>% @@ -139,43 +139,43 @@ glimpse(sim_dats_rfid) # Tres veces el numero original de filas, se ve bien

Revisar columnas de `dataframe` con R base

-Acabas de revisar la estructura del `dataframe` para confirmar que los datos simulados tiene datos recolectados a traves de tres dias. Tambien puedes revisar los valores unicos que estan presentes en la columna de `day`. Abajo puedes ver una forma de revisar los valores unicos adentro de una columna de un `dataframe`, usando dos ejemplos diferentes de notacion de R base para acceder columnas adentro de un `dataframe`: +Acabas de revisar la estructura del `dataframe` para confirmar que los datos simulados tiene datos recolectados a través de tres días. También puedes revisar los valores únicos que están presentes en la columna de `day`. Abajo puedes ver una forma de revisar los valores únicos adentro de una columna de un `dataframe`, usando dos ejemplos diferentes de notación de R base para acceder columnas adentro de un `dataframe`: ```{r} -# Escribir una expresion con el nombre de un objeto dataframe, un simbolo $, y el nombre de una columna te ayuda sacar o acceder una columan a la vez de un dataframe. Una columna de un dataframe es un vector, por ende cuando ejecutas este codigo deberias de ver un vector de valores impreso en la consola +# 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 -# Puedes tambien acceder una columna en un dataframe con indexar si escribes dos pares corchetes (ambos pares de abrir y cerrar) despues del nombre del dataframe, y colocas el nombre de la columna entrecomillas adentro de del par interno de corchetes +# 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 de del par interno de corchetes sim_dats_rfid[["day"]] -# Puedes usar la funcion unique() para ver los valores unicos adentro de un vector, incluyendo una columna en un dataframe -unique(sim_dats_rfid$day) # Tres dias, se ve bien +# 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 -unique(sim_dats_rfid[["day"]]) # Tres dias, se ve bien +unique(sim_dats_rfid[["day"]]) # Tres días, se ve bien ```

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

-Tambien puedes revisar valores unicos en una columna usando funciones del `tidyverse`. En la expresion abajo, vas a usar una expresion de `pipe` para proveer el `dataframe` `sim_dats_rfid` a la funcion `pull()`, que va a facilitar acceder la columna `day` del dataframe como un vector. Luedo este vector de la columna `day` se va a usar como entrada a la funcion `unique()` para revisar los valores unicos del vector mismo. La funcion `unique()` no require un argumento adentro de los parentesis porque ya recibio el valor de entrada que necesita a traves de la operacion de `piping`. +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`. ```{r} -# Tres dias, se ve bien +# Tres días, se ve bien sim_dats_rfid %>% pull(day) %>% unique() ``` -

Simula tres dias de recolectar datos de los sensores infrarrojo

+

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

-Ahora puedes repetir este proceso de crear un `dataframe` con metadatos para los datos de los sensores de infrarrojo. Dado que los sensores de infrarrojo no colectan informacion sobre la identidad unica de individuos, vas a anadir columnas para el año, el mes, el dia, y el tipo de sensor. Tambien vas a simular la coleccion de datos para estos sensores a traves de los mismos tres dias que los datos simulados de RFID. +Ahora puedes repetir este proceso de crear un `dataframe` con metadatos para los datos de los sensores de infrarrojo. Dado que los sensores de infrarrojo no colectan información sobre la identidad única de individuos, vas a añadir columnas para el año, el mes, el día, y el tipo de sensor. También vas a simular la colección de datos para estos sensores a través de los mismos tres días que los datos simulados de RFID. ```{r} -# Sobreescribe el vector exp_rep con un vector nuevo que tiene el mismo largo que los vectores o_irbb_ts y i_irbb_ts juntos +# 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)) -# Anade las marcas de tiempo de ambos pares de sensores infrarrojo a la misma columna usando c() +# 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 %>% @@ -183,8 +183,8 @@ sim_dats_irbb <- sim_dats_irbb %>% year = 2023, month = 08, day = 01, - # Anade un identificador unico para cada par de sensores - # Cada etiqueta unica se repitira por lo largo del vector de marcas de tiempo de cada par de sensores infrarrojo + # 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))) ) @@ -206,47 +206,47 @@ sim_dats_irbb <- sim_dats_irbb %>% glimpse(sim_dats_irbb) # Tres veces el numero de filas, se ve bien -# Tres dias, se ve bien +# Tres días, se ve bien sim_dats_irbb %>% pull(day) %>% unique() ``` -

Guarda un `dataframe` como un archivo fisico

+

Guarda un `dataframe` como un archivo físico

-Los `dataframes` que creas y manipulas en R se pueden guardar como archivos fisicos 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 funcion `write.csv()` para guardar `dataframes` a hojas de calculo `.csv` en tu computadora: +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 calculo `.csv` en tu computadora: ```{r eval = FALSE} ?write.csv ``` -Para escribir un archivo fisico 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 informacion a `write.csv()` con combinar tu directorio de trabajo y el nombre del archivo usando la funcion `file.path()`. Para este ejemplo, vas a crear un archivo de preuba mientras practicas como usar `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 como usar `write.csv()`: ```{r} # Combina el path para tu directorio de trabajo con el nombre del archivo que quieres escribir -# La funcion file.path() combinara ambas piezas de informacion en un solo path para este archivo +# 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 ubicacion donde vas a guardar el archivo, y luego el nombre del archivo +# Este objeto contiene la ubicación donde vas a guardar el archivo, y luego el nombre del archivo rfid_file ``` -Luego puedes proveer el `dataframe` a `write.csv()` usando un `pipe` y puedes especificar informacion adicional para crear el archivo `.csv`, como si quieres anadir una columna adicional de identidades numericas de las filas: +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: ```{r eval = FALSE} sim_dats_rfid %>% # Escribe el dataframe como una hoja de calculo en formato .csv. No incluyes los nombres de las filas (row.names = FALSE) - # El simbolo "." abajo significa que la funcion write.csv() va a operar sobre el objeto que proveyo el pipe, que en este caso es el objeto sim_dats_rfid + # 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 documentacion para la funcion `write.csv()`, esta funcion va a incluir las nombres de las columnas en la hoja de calculo por defecto. La funcion tambien no va a adjuntar esta informacion en el `dataframe` al archivo de `.csv` si esta hoja de calculo ya existe, o sea, si ya creaste el archivo de `.csv` y vuelves a correr el codigo arriba, el archivo se va a sobrescribir por defecto. +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 calculo 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 calculo 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. +Puedes revisar que `write.csv()` función como esperabas con usar `list.files()` para ver los archivos en tu directorio de trabajo. ```{r eval = FALSE} # Ve una lista de todos los archivos en este path @@ -254,17 +254,17 @@ list.files(path) ``` -Tambien puedes usar `list.files()` para customizar una busqueda con el argumento `pattern`. Usar el argumento `pattern` es parecido a buscar una palabra especifica adentro de un documento de texto. El simbolo de "$" despues de ".csv" significa que la funcion deberia de buscar todos los archivos que *terminan* en el patron ".csv". +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 especifica adentro de un documento de texto. El símbolo de "$" después de ".csv" significa que la función debería de buscar todos los archivos que *terminan* en el patrón ".csv". ```{r eval = FALSE} -# Devuelve solo archivos que terminan en el patron ".csv" en este path particular +# Devuelve solo archivos que terminan en el patrón ".csv" en este path particular list.files(path, pattern = ".csv$") ```

Leer una hoja de calculo

-Ahora puedes leer uno de estos archivos con R usando la funcion `read.csv()`. En el codigo abajo, vas a proveer el resultado de `read.csv()` a la funcion `glimpse()` para revisar la estructura del `dataframe` creado en R cuando leiste el archivo. El resultado de este codigo se imprime a la consola porque no esta guardado adentro de un objeto. +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 esta guardado adentro de un objeto. ```{r eval = FALSE} read.csv(file.path(path, "test_file.csv")) %>% @@ -272,7 +272,7 @@ read.csv(file.path(path, "test_file.csv")) %>% ``` -Ahora que practicaste usar `write.csv()` y `read.csv()`, puedes eliminar el archivo temporal que creaste con proveer el objeto `rfid_file` a la funcion `file.remove()`. +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()`. ```{r eval = FALSE} rfid_file <- file.path(path, "test_file.csv") @@ -282,22 +282,22 @@ file.remove(rfid_file) ``` -

Guardar datos simuladoes para analisis con ABISSMAL

+

Guardar datos simulados para análisis con ABISSMAL

-En el codigo 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 calculo diferente por el tipo de sensor y el dia de coleccion de datos. Estas hojas de calculo 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 empiricos de animales. +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 calculo diferente por el tipo de sensor y el día de colección de datos. Estas hojas de calculo se tienen que guardar adentro de una carpeta por tipo de sensor. ABISSMAL guarda los datos originales de esta misma forma cuando el sistema se usa para colectar datos empíricos de animales.

Filtrar un `dataframe`

-Vas a practicar como usar la funcion `dplyr::filter()` para filtrar filas de un `dataframe` por dia y luego guardar un `dataframe` filtrado con `write.csv()`. Para filtrar un `dataframe`, puedes usar una frase condicional adentro de la funcion `dplyr::filter()`: +Vas a practicar como usar la función `dplyr::filter()` para filtrar filas de un `dataframe` por día y luego guardar un `dataframe` filtrado con `write.csv()`. Para filtrar un `dataframe`, puedes usar una frase condicional adentro de la función `dplyr::filter()`: ```{r} -# Provee el dataframe a la funcion filter() con un "pipe" +# 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 dia era igual a uno (el primer dia de coleccion de datos) + # Filtra el dataframe con seleccionar todas las filas en que la columna de día era igual a uno (el primer día de colección de datos) dplyr::filter(day == 1) %>% glimpse() -# Revisa que este paso de filtrar se hizo correctamente. El unico valor adentro de la columna del dia deberia de ser uno, y se ve bien +# 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) %>% @@ -305,15 +305,15 @@ sim_dats_rfid %>% ``` -Puedes obtener resultados similares cuando inviertes la frase condicional adentro de `dplyr::filter()` para eliminar los dias que **no** fueron ni el segundo dia ni el tercer dia de coleccion de datos. Abajo combinaste dos frases condicionales usando el simbolo de "&". +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 "&". ```{r} sim_dats_rfid %>% - # Filtra el dataframe con seleccionar todas las filas en que los valores en la columna de dia no son iguales a 2 o 3 + # 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() -# Usa la funcion par ver los valores uncios en una columna para revisar que el proceso de filtrar se completo bien. Se ve bien +# 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) %>% @@ -321,7 +321,7 @@ sim_dats_rfid %>% ``` -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 dia: +Ahora que practicaste filtrar un `dataframe`, puedes combinar este paso de filtrar con escribir un archivo `.csv` que contiene el `dataframe` filtrado por los datos colectados en un solo día: ```{r eval = FALSE} # Crea un directorio nuevo adentro de tu directorio de trabajo para guardar datos @@ -333,15 +333,15 @@ 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 -# Asegurate de especificar que el archivo se va a guardar adentro de la carpeta neuva "RFID" +# 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 dia de coleccion de datos +# 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 calculo en formato .csv. No incluyes nombres para las filas - # Recuerda que el simbolo "." significa que la funcion ca a usar el objeto que proveyo la operacion de "pipe", que aqui es el dataframe filtrado para seleccionar solo el primer dia de coleccion de datos + # 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) ``` @@ -360,9 +360,9 @@ file.remove(rfid_file)

Practicar escribir un bucle

-Podrias repetir el codigo arriba seis veces (tres veces por sensor) para escribir un `dataframe` por cada dia de coleccion de datos por sensor. Pero es mejor evitar repetir el mismo codigo varias veces, porque cuando escribes codigo de esta forma es mas dificil mantener archivos organizados de codigo y tambien es mas facil introducir errors mientras procesas y analizas datos. Cuando necesitas ejecutar el mismo codigo varias veces, es mejor escribir un bucle. Escribir bucles es una habilidad muy importante y vamos a construir un bucle paso por paso. +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 mas difícil mantener archivos organizados de código y también es mas 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 como escribir un bucle con la funcion `lapply()`. +Vas a practicar como escribir un bucle con la función `lapply()`. ```{r} ?lapply @@ -375,11 +375,11 @@ length(files) ``` -Ahora puedes empezar a escribir la estructura de un bucle. En el codigo abajo, el argumento `X` es el numero de veces que se va a ejecutar el bucle. En este caso, `X` es un vector numerico de uno al largo del vector `files` y contiene los numeros uno y dos. Por end, el bucle va a ejecutar dos veces, y cada valor consecutivo en `X` se va a usar en cada iteracion correspondiente del bucle para escribir un archivo a la vez. -El argumento `FUN` es una funcion customizada que fue escrita usando la notacion `function(x){}`. Todo el codigo adentro de las llaves curvas (abre y cierre) se ejecutara en cada iteracion del bucle. El argumento `x` adentro de `funcion()` es el variable de iteracion, o el variable que va a tomar un valor diferente del vector `X` en cada iteracion. +Ahora puedes empezar a escribir la estructura de un bucle. En el código abajo, el argumento `X` es el numero 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 ejecutara en cada iteración del bucle. El argumento `x` adentro de `funcion()` es el variable de iteración, o el variable que va a tomar un valor diferente del vector `X` en cada iteración. ```{r} -# En este bucle el variable de iteracion x va a tomar cada valor del vector en el argumento X. Por ejemplo, en la primera iteracion del bucle, x va a tomar el valor numerico de 1. En la segunda iteracion del bucle, x va a tomra el valor numerico de 2. Para probar esta logica puedes ejecutar el bucle abajo, y ver el valor de x que va a imprimir en cada iteracion en la consola +# En este bucle el 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 va a imprimir en cada iteración en la consola lapply(X = 1:length(files), FUN = function(x){ x @@ -388,13 +388,13 @@ lapply(X = 1:length(files), FUN = function(x){ ``` -Como puedes ver, el resultado de este bucle es una lista con dos elementos. Cada elemento de la lista esta rodeado de dos pares de corchetes ([[1]] and [[2]]) y contiene un vector con un largo de uno que contiene el valor del variable de iteracion (uno y dos, respectivamente). +Como puedes ver, el resultado de este bucle es una lista con dos elementos. Cada elemento de la lista esta rodeado de dos pares de corchetes ([[1]] y [[2]]) y contiene un vector con un largo de uno que contiene el valor del variable de iteración (uno y dos, respectivamente). -El variable de iteracion, o `x`, no existe como un objetp afuera de la funcion del bucle. Si imprimes `x` afuera del bucle, no se va a encontrar ese objeto. Si creaste un objeto `x` afuera del bucle arria, veras los contenidos de este objeto cuando imprimes `x`. O sea, escribir un bucle que usa `x` como el variable de iteracion no afectara otras lineas de codigo que usan un objeto que se llama `x` afuera de la funcion, y viceversa. +El 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, veras los contenidos de este objeto cuando imprimes `x`. O sea, escribir un bucle que usa `x` como el variable de iteración no afectara otras lineas de código que usan un objeto que se llama `x` afuera de la función, y viceversa. -El variable de iteracion de una funcion puede ser otras letras del alfabeto como `i`, `j`, `y`, `z`, o una combinacion de multiples letras, numeros, guiones bajo, o periodos, siempre y cuando el nombre de la variable empieza con una letra. +El 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 util del variable de iteracion 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: +Una propiedad útil del 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: ```{r} lapply(X = 1:length(files), FUN = function(x){ @@ -405,12 +405,12 @@ lapply(X = 1:length(files), FUN = function(x){ ``` -Tambien puedes modificar el codigo adentro de la funcion custoimizada para guardar cada archivo si metes la expresion `files[x]` adentro de la funcion `write.csv()` y usas una operacion de `piping` para usar un `dataframe` como entrada para `write.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()`. ```{r eval = FALSE} lapply(X = 1:length(files), FUN = function(x){ - # En cada iteracion del bucle, vas a guardar el dataframe `files[x]` en una hoja de calculo con el nombre de archivo de la iteracion actual + # En cada iteración del bucle, vas a guardar el dataframe `files[x]` en una hoja de calculo con el nombre de archivo de la iteración actual sim_dats_rfid %>% write.csv(file = files[x], row.names = FALSE) @@ -418,14 +418,14 @@ lapply(X = 1:length(files), FUN = function(x){ ``` -Deberias 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 ubicacion: +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: ```{r} list.files(file.path(path, "Data/RFID")) ``` -Acabas de escribir dos hojas de calculo usando un bucle, pero escribistes el mismo `dataframe` a cada hoja de calculo. Para poder escribir un `dataframe` diferente a cada hoja de calculo, puedes anadir el paso de filtrar el `dataframe`que aprendiste arriba. En el codigo abajo, tambien vas a crear otro objecto de vector que se llama `days` (dias) y luego usaras el variable de iteracion para filtrar `sim_dats_rfid` y escribir una hoja de calculo por cada dia en `days`. +Acabas de escribir dos hojas de calculo usando un bucle, pero escribiste el mismo `dataframe` a cada hoja de calculo. Para poder escribir un `dataframe` diferente a cada hoja de calculo, 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 usaras el variable de iteración para filtrar `sim_dats_rfid` y escribir una hoja de calculo por cada día en `days`. ```{r eval = FALSE} days <- c(1, 2, 3) @@ -433,16 +433,16 @@ days <- c(1, 2, 3) lapply(X = 1:length(files), FUN = function(x){ sim_dats_rfid %>% - # Filtra el dataframe una dia a la vez + # Filtra el dataframe una día a la vez dplyr::filter(day == days[x]) %>% - # Escribe el dataframe filtrado por el dia actual a una hoja de calculo para ese dia + # Escribe el dataframe filtrado por el día actual a una hoja de calculo para ese día write.csv(file = files[x], row.names = FALSE) }) ``` -Puedes borrar estos archivos que creaste de prueba. En el codigo abajo, vas a ver otro ejemplo de buscar un patron 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 simbolo de `^`, el cual significa que quieres buscar todos los archivos que *empiezan* con el patron "test". Tambien esta especificandp que quieres devolver la ubicacion (`path`) completa de cada archivo usando el argumento `full.names = TRUE`, para que la funcion `file.remove()` tenga toda la informacion que necesita para borrar estos archivos de prueba. +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 esta especificando que quieres devolver la ubicación (`path`) completa de cada archivo usando el argumento `full.names = TRUE`, para que la función `file.remove()` tenga toda la información que necesita para borrar estos archivos de prueba. ```{r eval = FALSE} rem_files <- list.files(file.path(path, "Data/RFID"), pattern = "^test", full.names = TRUE) @@ -454,7 +454,7 @@ file.remove(rem_files)

Usa un bucle para guardar la hoja de calculo de RFID

-Ahora puedes juntar todo lo que aprendiste arriba y usar el bucle para escribir una hoja de calculo por dia para el sensor de RFID. +Ahora puedes juntar todo lo que aprendiste arriba y usar el bucle para escribir una hoja de calculo por día para el sensor de RFID. ```{r eval = FALSE} # Crea un vector de los nombres de los archivos que quieres guardar @@ -464,41 +464,41 @@ files <- c( "RFID_simulated_Pair-01_2023_08_03.csv" ) -# Anade el path para la ubicacion o carpeta al nombre de cada archivo +# 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 dias para poder escribir una hoja de calculo por dia en cada iteracion del bucle +# Inicializa un vector de los días para poder escribir una hoja de calculo 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 estas especificando los valores de los argumentos en el orden que la funcion espera por defecto +# Puedes eliminar los nombres de los argumentos de lapply() porque estas 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 dia actual en esta iteracion + # 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 calculo correcta para el dia actual + # Escribe el dataframe filtrado a la hoja de calculo correcta para el día actual write.csv(file = files[x], row.names = FALSE) })) ``` -En el codio arriba, deberias de poder ver un cambio adicional que hicimos al bucle con rodearlo con la funcion `invisible()`. Esta funcion silencia al resultado que se imprima a la consola (que viste cuando ejecutaste trozos de codigo arriba), en que el resultado de cada iteracion de `lapply()` esta rodeado de dos pares de corchetes, y luego un solo par de corchetes. `lapply()` es una funcion que devuelve una lista, y cuando la funcion se ejecuta correctamenta pero no hay resultados para imprimir (como cuando creas un archivo fisico), la funcion deberia de devolver valores de `NULL` que significan resultados vacios. Este comportamiento se espera con nuestro uso de `lapply()` porque usamos la funcion para escribir archivos fisicos y no para devolver resultados importantes a la consola. Ya que puedes usar `list.files()` para revisar que `lapply()` se ejecuto bien, usar `invisible()` te ayudara minimizar la cantidad de texto que tienes que revisar en tu consola. +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 al 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()` esta rodeado de dos pares de corchetes, y luego un solo par de corchetes. `lapply()` es una función que devuelve una lista, 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 importantes a la consola. Ya que puedes usar `list.files()` para revisar que `lapply()` se ejecuto bien, usar `invisible()` te ayudara minimizar la cantidad de texto que tienes que revisar en tu consola. ```{r eval = FALSE} -# Los archivos nuevos de .csv para cada dia de datos de RFID estan presentes en el directorio esperado, se ve bien +# 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 automaticamente escribir los datos de cada dia para cada tipo de sensor. +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. ```{r eval = FALSE} files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv") -# Anade el path para el directorio correcto a los nombres de los archivos que quieres borrar +# 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 @@ -506,21 +506,21 @@ file.remove(files) ``` -Si quieres mas pratcica escribiendo bucles, puedes escribir un bucle para guardar una hoja de calculo para cada dia de coleccion de datos para los sensores infrarrojo. +Si quieres mas practica escribiendo bucles, puedes escribir un bucle para guardar una hoja de calculo para cada día de colección de datos para los sensores infrarrojo.

Escribe un bucle anidado para crear hojas de calculo

-Si quieres minimizar la cantidad de codigo que escribes para guardar los datos por tipo de sensor y por dia, puedes guardar archvos de ambos tipos de sensores (RFID y sensores infrarrojos) en el mismo bucle. Para continuar, deberias de crear otro directorio para los datos de los sensores infrarrojos: +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 infrarrojos) en el mismo bucle. Para continuar, deberías de crear otro directorio para los datos de los sensores infrarrojos: ```{r eval = FALSE} dir.create(file.path(path, "Data", "IRBB")) ``` -Para lograr filtrar y escribir datos para ambos tipos de sensores a traves de los dias de coleccion 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: +Para lograr filtrar y escribir datos para ambos tipos de sensores a través de los días de colección de datos, vas a usar un tipo de objeto en R que se llama un `list`. Vas a usar estos `lists` adentro del bucle anidado: ```{r} -# Crea una lista de los nombres de archivos customizados para guardar datos para cada tipo de sensor y dia +# Crea una lista 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") @@ -530,7 +530,7 @@ glimpse(files) ``` -Los `lists` son objetos utiles porque son muy flexibles. A diferencia de vectores, una sola lista puede contener varios diferentes tipos de datos. A diferecia de un `dataframe`, los elementos de una lista no necesitan tener las mismas dimensiones. Los elementors de una lista tambien 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 funcion `c()`. +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: ```{r} @@ -539,13 +539,13 @@ Puedes indexar un `list` de una forma parecida a indexar vectores y `dataframes` files[1] glimpse(files[1]) -# Usar dos pares de corchetes devuelvo solo el elemento actual, o sea elimina el estructura de list para demostrar el estructura original de ese elemento (aqui este elemento es un vector) +# Usar dos pares de corchetes devuelvo solo el elemento actual, o sea elimina el estructura de list para demostrar el estructura original de ese elemento (aquí este elemento es un vector) files[[1]] glimpse(files[[1]]) ``` -Un `list` tambien puede tener nombres para sus elementos, y los elementos se pueden acceder por nombre: +Un `list` también puede tener nombres para sus elementos, y los elementos se pueden acceder por nombre: ```{r} # Crea un list de los nombres de los archivos que quieres guardar @@ -564,14 +564,14 @@ Accede los elementos de este `list` por nombre: # Usar un par de corchetes devuelve el elemento "RFID" como una lista files["RFID"] -# Usar el simbolo de dolar "$" o dos pares de corchetes devuelve el elemento "RFID" en su formato original +# Usar el símbolo de dolar "$" o dos pares de corchetes devuelve el elemento "RFID" en su formato original files$RFID files[["RFID"]] ``` -Los `lists` con tipos de objetos muy utiles para operaciones con bucles anidados. Por ejemplo, si quieres escribir una hoja de calculo por tipo de sensor y por dia, vas a necesitar 1) un bucle para iterar a traves de los tipos de sensors y 2) un bucle para iterar a traves de dias de coleccion de datos para cada sensor. Puedes usar listas para crear estructuras anidadas de datos que puedes proveer a un bucle anidado, para aseguar 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 estan ordernados primero por el tipo de sensor (cada elemento del `list`) y luego por dia de coleccion de datos (cada elemento del vector adentro de cada elemento del `list`): +Los `lists` con tipos de objetos muy útiles para operaciones con bucles anidados. Por ejemplo, si quieres escribir una hoja de calculo 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`): ```{r} # Crea un vector de los nombres de los sensores @@ -595,8 +595,8 @@ file_dirs <- list( file_dirs -# Crea un list de los dias de coleccion de datos para cada tipo de sensor -# Esto puede ser un solo vector en vez de una lista porque quieres guardar el mismo numero de dias por sensor, pero una lista es util por si quieres cambiar los dias mismos o el numero de dias que quieres guardar por sensor +# 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 numero de días por sensor, pero una lista es útil por si quieres cambiar los días mismos o el numero de días que quieres guardar por sensor days <- list( `RFID` = c(1, 2, 3), `IRBB` = c(1, 2, 3) @@ -604,7 +604,7 @@ days <- list( days -# Ahora necesitas crear un list de los dataframes que quieres usar para crear los archivos. Como ya sabes como filtrar dataframes por dia, ese codigo puede ir adentro de los bucles para minimizar la cantidad de codigo que escribes. Aqui vas a especificar el dataframe que usaras en las operaciones de filtrar para cada tipo de sensor +# Ahora necesitas crear un list de los dataframes que quieres usar para crear los archivos. Como ya sabes como 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 usaras en las operaciones de filtrar para cada tipo de sensor dats <- list( `RFID` = sim_dats_rfid, `IRBB` = sim_dats_irbb @@ -614,48 +614,48 @@ glimpse(dats) ``` -Cuando hayas establecido las estructuras de datos para informar la operacion del bucle, puedes escribir el bucle anidado mismo. Este bucle anidado es una estructra compleja, y por ende es util probar el bucle con valores determinados de cada variable de iteracion (`x` y `y` abajo). +Cuando hayas establecido las estructuras de 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). -Despues de escribir este bucle pero antes de ejecutar la estructura completa del bucle, deberias de probar el codigo adentro de cada capa del bucle. Para lograr esto, puedes inicializar los valores de los variables de iteracion y luego correr el codigo 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 codigo para una sola iteracion (abajo vas a ver la primera iteracion para cada capa del bucle cuando ambos `x` y `y` tiene el valor numerico de uno). +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, deberias de ejecutar el codigo para "congelar" los variables de iteracion en la primera iteracion de cada bucle ( o sea, inicializar `x` y `y` con el valor de uno). Luego deberias de ejecutar el codigo adentro de cada bucle, empezando con la creacion de `days_tmp`, luego las operaciones de indexar y filtrar el `dataframe`, y luego filtrar los nombres de los archivos. No deberias de ejecutar las lineas con `lapply()` porque quieres evitar ejecutar los bucles completos hasta que estes segura que el codigo adentro de cada bucle funciona de la forma que esperas. +Para lograr este tipo de chequeo, deberías de ejecutar el código para "congelar" los 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 lineas 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 los variables de iteracion no existen afuera de un bucle. Esta forma de probar el codigo adentro del bucle, en que no estas ejecutando los bucles mismos, es equivalente a probar el codigo afuera del bucle y por ende, los valores de los variables de iteracion que inicializes afuera del bucle se van a respetar. +*Nota importante*: Arriba aprendiste que los variables de iteración no existen afuera de un bucle. Esta forma de probar el código adentro del bucle, en que no estas ejecutando los bucles mismos, es equivalente a probar el código afuera del bucle y por ende, los valores de los variables de iteración que inicializes afuera del bucle se van a respetar. -Mientras revisas el codigo adentro de cada bucle, deberias de ver que entre el bucle exterior y el bucle interior, vas a usar el nombre del sensor para la primera iteracion del bucle exterior ("RFID") para determinar los dias 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 dias que quieres por sensor. Luego vas a indexar el nombre del archivo para el tipo de sensor actual y el dia actual con una combinacion de indexar la lista de nombres de los archivos con uno o dos pares de corchetes. Abajo, las lineas que abren y cierrn los bucles mismos estan comentados para guiar tu chequeo (o sea para guiar cuales lineas de codigo deberias de ejecutar): +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 lineas que abren y cierran los bucles mismos están comentados para guiar tu chequeo (o sea para guiar cuales lineas de código deberías de ejecutar): ```{r eval = FALSE} -# Congela las variables de iteracion para el chequeo +# Congela las variables de iteración para el chequeo x <- 1 y <- 1 -# El bucle exterior: empieza con iterar a traves de los sensores -# invisible(lapply(1:length(sensors), function(x){ +# El bucle exterior: empieza con iterar a través de los sensores +# invisible(lapply(1:length(sensores), function(x){ - # Para obtener los dias correctos para el tipo de sensor actual, deberias de indexar el list nombrado de dias + # 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 dias para el tipo de sensor actual + # 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 traves de dias para cada tipo de sensor + # 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 data frame del list + # 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 data frame por el dia actual puedes usar y para indexar el vector temporal de dias (para extraer un solo elemento de este vector) + # 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 esta adentro de la lista. Luego usa el variable y para indexar este vector con un par de corchetes para acceder el nombre de archivo correcto para esta iteracion + # Usa dos pares de corchetes para acceder el vector de los nombres de los archivos para el tipo de sensor actual que esta adentro de la lista. Luego usa el 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] - # Tambien vas a combinar el nombre del archivo con el path correcto: + # También vas a combinar el nombre del archivo con el path correcto: file.path(file_dirs[[x]], files[[x]][y]) # }) @@ -664,25 +664,25 @@ y <- 1 ``` -Ahora deberias de tener una mejor idea sobre como cada bucle opera a traves de diferentes estructuras de datos para realizar la tarea que quieres (en este caso, escribir una hola de calculo por tipo de sensor y dia). Luego puedes modificar la estructura entera de los bucles para reemplazar las lineas que escribiste para eln chequeo con las operaciones finales que quieres realizar: +Ahora deberías de tener una mejor idea sobre como cada bucle opera a través de diferentes estructuras de datos para realizar la tarea que quieres (en este caso, escribir una hola de calculo por tipo de sensor y día). Luego puedes modificar la estructura entera de los bucles para reemplazar las lineas que escribiste para el chequeo con las operaciones finales que quieres realizar: ```{r eval = FALSE} -# El bucle exterior: empieza con iterar a traves de los sensores +# El bucle exterior: empieza con iterar a través de los sensores invisible(lapply(1:length(sensors), function(x){ - # Para obtener los dias correctos para el tipo de sensor actual, deberias de indexar el list nombrado de dias + # 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 traves de dias para cada tipo de sensor para escribir una hoja de calculo por tipo de sensor y dia + # El bucle interior: itera a través de días para cada tipo de sensor para escribir una hoja de calculo por tipo de sensor y día lapply(1:length(days_tmp), function(y){ - # Usa el variable x para acceder el dataframe para el tipo de sensor actual y luego usar el variable y para filtrar este dataframe por el dia actual + # Usa el variable x para acceder el dataframe para el tipo de sensor actual y luego usar el variable y para filtrar este dataframe por el día actual dats[[x]] %>% - # Filtra el dataframe por el dia actual + # Filtra el dataframe por el día actual dplyr::filter(day == days_tmp[y]) %>% - # Usa una operacion 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 dia actual + # 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) }) @@ -691,7 +691,7 @@ invisible(lapply(1:length(sensors), function(x){ ``` -Esta estructura de bucle deberia de haber creado un archivo por tipo de sensor y dia 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: +Esta estructura de bucle debería de haber creado un archivo por tipo de sensor y día en el directorio correcto por tipo de sensor. Puedes revisar que estos seis archivos existen adentro del directorio para cada tipo de sensor en tu directorio de trabajo: ```{r eval = FALSE} list.files(file.path(path, "Data/RFID")) diff --git a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd index a5338dc..8a6a2af 100644 --- a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd +++ b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd @@ -22,11 +22,11 @@ Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las

Resumen del tutorial y objetivos de aprendizaje

-En este quinto tutorial, vas a empezar a usar las detecciones simuladas de movimientos de animales en el `pipeline` de analisis de datos de ABISSMAL, incluyendo combinar los datos originales a traves de dias y procesar o limpiar los datos originales. Tambien vas a crear graficas de los datos procesados. Vas a continuar a usar habilidades que aprendiste en los tutoriales anteriores, y vas a aprender nuevas habilidades que incluyen: +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 que incluyen: 1. Acceder funciones customizadas 2. Usar funciones customizadas -3. Crear graficas con `ggplot` +3. Crear gráficas con `ggplot`

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

@@ -34,43 +34,43 @@ En este quinto tutorial, vas a empezar a usar las detecciones simuladas de movim rm(list = ls()) # Limpia tu ambiente global -library(tidyverse) # Carga la coleccion de paquetes del tidyverse -library(data.table) # Carga otros paquetes requiridos por las funciones de ABISSMAL +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" # Inicializar un objeto con el path de tu directorio de trabajo +path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Inicializa un objeto con el path de tu directorio de trabajo ```

Cargar las funciones de ABISSMAL

-Las funciones customizadas de R en ABISSMAL estan guardados en archivos fisicos (extension .R) adentro de la version 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 fisicos de R para que las funciones esten disponibles en tu ambiente global. En el codigo abajo, vas a usar la funcion `source()` para cargar tres de las cinco funciones primarias de ABISSMAL, y tambien un archivo que contiene funciones de apoyo: +Las funciones customizadas de R en ABISSMAL están guardados 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: ```{r} -# Carga la funcion que combina los datos originales +# Carga la función que combina los datos originales source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/combine_raw_data.R") -# Carga la funcion que detecta eventos de posa en los datos originales +# 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 funcion que procesa los datos originales +# 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 funcion arriba require +# Carga un archivo con funciones de apoyo que cada función arriba requiere source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") ``` -

Accede informacion sobre las funciones de ABISSMAL

+

Accede información sobre las funciones de ABISSMAL

-Despues de ejecutar las lineas de codigo arriba, deberias de ver que una coleccion entera de funciones estan disponibles en tu ambiente global (revisa la pestana de `Environment`). Las funciones que empiezan con `check_` son funciones de apoyo. Si haces `scroll` para abajo, puedes ver que tres de las funciones primarias de ABISSMAL (`combine_raw_data`, `detect_perching_events`, `preprocess_detections`) tambien estan disponsibles en tu ambiente global. En la columna al lado derecho de los nombres de las funciones tambien podras ver algo de informacion sobre los argumentos de cada funcion. +Después de ejecutar las lineas de código arriba, deberías de ver que una colección entera de funciones están disponibles en tu ambiente global (revisa la pestaña de `Environment`). Las funciones que empiezan con `check_` son funciones de apoyo. Si haces `scroll` para 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 mas informacion sobre cada una de las tres funciones primarias, puedes hacer clic en el icono blanco cuadrado a la mera derecha de cada funcion en la pestana de `Environment`, o ejecutar el codigo `View(nombre_de_la_funcion)`. Este comando deberia de abrir el archivo de la funcion actual en una pestana nueva adentro de tu panel de fuente. En el archico de cada funcion, vas ver lineas de documentacion que empiezan con los simbolos "`#@", luego el nombre de la funcion y una descripcion, y luego una descripcion de cada argumento (paramtero) para la funcion. Si haces scroll para abajo, podras ver una seccion con detalles sobre la funcion misma, incluyendo la informacion que devuelve. Esta documentacion esta escrita en ingles por el momento. Despues de las lineas de documentacion veras el codigo de la funcion misma. +Para obtener mas información sobre cada una de las tres funciones primarias, puedes hacer clic en el icono 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 lineas 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 para abajo, podrás ver una sección con detalles sobre la función misma, incluyendo la información que devuelve. Esta documentación esta escrita en ingles por el momento. Después de las lineas de documentación veras el código de la función misma.

Combinar los datos originales

-Cuando hayas cargado las funciones de ABISSMAL, podras usar la primera funcion, `combine_raw_data()`, para combinar los datos colectados a traves de dias y tipos de sensors en una sola hoja de calculo por sensor. Vas a empezar con combinar los datos originales para el sensor de RFID que fueron colectados a traves de dias diferentes en una hoja de calculo para este sensor. +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 tipos de sensores en una sola hoja de calculo 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 calculo para este sensor. -Vas a proveerle informacion a la funcion `combine_raw_data()` a traves de los siguientes argumentos: +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 @@ -78,11 +78,11 @@ Vas a proveerle informacion a la funcion `combine_raw_data()` a traves de los si * `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 calculo de los datos originales combinados. La funcion creara esta carpeta si no existe en tu computadora +* `out_dir` es la carpeta donde quieres guardar la hoja de calculo de los datos originales combinados. La función creara 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 seccion de "Time zones" en la documentacion para `DateTimeClasses` en R para mas informacion (`?DateTimeClasses`) +* `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 mas información (`?DateTimeClasses`) -* `POSIXct_format` es una secuencia de caracteres que contiene la informacion del formato `POSIXct` para combinar fechas y marcas de tiempo en una sola columna. Por defecto la funcion devolvera el año como un numero con cuatro digitos y el mes y el dia como numeros con dos digitos, separados por guiones. La fecha y el tiempo estaran separados por un espacio, y la hora, el minuto, y el segundo (en decimales), todos con dos digitos, estaran separados por dos puntos +* `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 numero 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, y la hora, el minuto, y el segundo (en decimales), todos con dos dígitos, estarán separados por dos puntos ```{r} # Combina los datos originales para los sensores de RFID e infrarrojo en procesos separados @@ -106,18 +106,18 @@ glimpse(rfid_data) ``` -Leer estos datos a R creo un objeto `dataframe`. Deberias de poder ver que hay unas columnas nuevas creadas por la funcion, como la columna `data_type`. Para esta hoja de calculo, las columnas `sensor_id` y `data_type` contienen la misma informacion, pero es util tener columnas separadas para poder estar al tanto de la identidad unica del sensor y el tipo de sensor cuando usas multiples sensores del mismo tipo (por ejemplo, dos pares de sensores infrarrojos tendran numeros de identidad unicos en la columna de `senso_id`). +Leer estos datos a R creo 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 calculo, 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 infrarrojos tendrán números de identidad únicos en la columna de `senso_id`). -La funcion `combine_raw_data()` tambien creo 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 funcion anadio 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, veras que las hojas de calculo originales por dia se preservaron y no fueron ni eliminados ni sobrescritos. +La función `combine_raw_data()` también creo 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, veras que las hojas de calculo originales por día se preservaron y no fueron ni eliminados ni sobrescritos. -Tambien puedes ejecutar `combine_raw_data()` con los datos originales de multiples sensores a la vez con proveer un vector con las etiquetas de estos sensores al argumento `sensores`. Los datos para cada tipo de sensor todavia se guardaran en hojas de calculo separados, y evitas tener que escribir el mismo codigo varias veces para ejecutar `combine_raw_data()` para multiples sensores: +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 guardaran en hojas de calculo separados, y evitas tener que escribir el mismo código varias veces para ejecutar `combine_raw_data()` para múltiples sensores: ```{r} combine_raw_data(sensors = c("RFID", "IRBB"), path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") ``` -Los datos de RFID se sobrescribiran, y deberias de ver una hoja de calculo adicional con los datos originales de los sensores infrarrojos: +Los datos de RFID se sobrescribirán, y deberías de ver una hoja de calculo adicional con los datos originales de los sensores infrarrojos: ```{r} list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") @@ -126,16 +126,16 @@ list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$")

Detectar eventos de posar

-Puedes usar los datos originales combinados de sensores diferentes en las siguientes funciones de ABISSMAL para empezar a hacer inferencias de comportamiento de los datos de deteccion de movimiento. Por ejemplo, puedes detectar eventos de posar en los datos originales de RFID con la funcion `detect_perching_events()`. Puedes leer mas sobre cada argumento en el archivo de R que contiene esta funcion. +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 mas sobre cada argumento en el archivo de R que contiene esta función. ```{r} detect_perching_events(file_nm = "combined_raw_data_RFID.csv", threshold = 2, run_length = 2, sensor_id_col_nm = "sensor_id", timestamps_col_nm = "timestamp_ms", PIT_tag_col_nm = "PIT_tag", rfid_label = "RFID", general_metadata_cols = c("chamber_id", "sensor_id"), path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", out_file_prefix = "perching_events", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") ``` -`detect_perching_events()` puede operar en solo un archivo y tipo de sensor a la vez. La funcion automaticamente crea una carpeta que se llama "prcoessed" (para datos procesados) y guardara un archivo de .csv adentro de esa carpeta si pudo detectar eventos de posar usando el umbral temporal actual (`threshold`, en segundos), y la duracion de secuencias de deteccion actual (`run_length`, en numero de detecciones). +`detect_perching_events()` puede operar en solo 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 guardara 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 numero de detecciones). -Cuando creamos datos simulados en los ultimos tutoriales, simulaste eventos de posar en los datos de RFID. Pudiste recuperar estos eventos de posar usando `detect_perching_events()`? +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()`? ```{r} perching <- read.csv(file.path(path, "Data", "processed", "perching_events_RFID.csv")) @@ -144,34 +144,34 @@ glimpse(perching) ``` -`detect_perching_events()` identifico un total de seis eventos de posar, que es el mismo numero que simulaste en el tutorial anterior (dos eventos de posar por dia a traves de tres dias). Puedes revisar los valores adentro del `dataframe` para ver mas informacion sobre estos eventos de posar: +`detect_perching_events()` identifico un total de seis eventos de posar, que es el mismo numero 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 mas información sobre estos eventos de posar: ```{r} -# Las marcas de tiempo cuando cada evento de posar empezo +# Las marcas de tiempo cuando cada evento de posar empezó perching$perching_start # Las marcas de tiempo cuando cada evento de posar termino perching$perching_end -# La etiqueta unica de PIT que contiene informacion sobre la identidad del individuo que estuvo posando en la antena de RFID +# 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 ``` -Tambien puedes visualizar el `dataframe` entero en un panel separado: +También puedes visualizar el `dataframe` entero en un panel separado: ```{r eval = FALSE} View(perching) ``` -La informacion arriba te dice que hubo dos eventos de posar a las 8:00 cada dia, y dos eventos de posar a las 11:30 cada dia (como esperamos). La etiqueta de PIT para cada individuo se detecto una vez por dia, por ende, cada individuo realizo un evento de posar cada dia. +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 detecto una vez por día, por ende, cada individuo realizo un evento de posar cada día. -Detectar eventos de posar no es un requisito en el `pipeline` de analisis de ABISSMAl, pero puede ser un paso util para obtener la mayor cantidad de informacion que puedes de los datos originales antes de filtrar detecciones en el siguiente paso de procesar o limpiar los datos originales. +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 detecciones en el siguiente paso de procesar o limpiar los datos originales.

Procesa 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 funcion `preprocess_detections()`. Los datos originales a veces contienen multiple detecciones separadas por poco tiempo (como las detecciones de RFID cuando un individuo esta posando en la antena), y estos multiples detecciones pueden causar ruido cuando tratas de hacer inferencias de comportamiento con datos colectados por multiples sensores. Cuando le provees "thin" al argumento `mode`, `preprocess_detections()` elimina detecciones separaas 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 todavia representan eventos discretos de movimiento. +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 esta posando en la antena), y estos múltiples detecciones pueden causar ruido cuando tratas de hacer inferencias de comportamiento con datos colectados por múltiples sensores. Cuando le provees "thin" al argumento `mode`, `preprocess_detections()` elimina detecciones separadas por un periodo corto de tiempo (usando el valor del umbral temporal en segundos para el argumento `thin_threshold`), y devuelve datos filtrados de detecciones que todavía representan eventos discretos de movimiento. `preprocess_detections()` opera en un solo tipo de sensor a la vez: ```{r} @@ -180,14 +180,14 @@ preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group ``` -Ahora deberias de ver un archvio de `.csv` adicional que se llama "pre_processed_data_RFID.csv" en la carpeta "processed": +Ahora deberías de ver un archivo de `.csv` adicional que se llama "pre_processed_data_RFID.csv" en la carpeta "processed": ```{r} list.files(file.path(path, "Data/processed")) ``` -Puedes leer este archivo a R para ver su estructura. Deberias de ver menos filas en este `dataframe` comparado con la hoja de calculo de los datos originales: +Puedes leer este archivo a R para ver su estructura. Deberías de ver menos filas en este `dataframe` comparado con la hoja de calculo de los datos originales: ```{r} rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) @@ -209,29 +209,29 @@ glimpse(irbb_pp) ``` -

Visualizar datos de RFID en una grafica de codigo de barras

+

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

-Ahora que combinaste y procesaste los datos originales por sensor, es hora de visualizar estos conjuntos de datos diferentes. Hacer graficas mientras escribes codigo es importante para generar figures de alta calidad para publicaciones y presentaciones y tambien para revisar tu proceso de analizar datos. +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 codigo abajo, vas a aprender como usar funciones del paquete `ggplot2` para hacer una grafica del estilo de codigo de barras con los datos originales y procesados de RFID. +En el código abajo, vas a aprender como 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, tambien los eventos de posar de RFID, y convertir las marcas de tiempo al formato `POSIX`. +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`. ```{r} rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) %>% - # Tienes que convertir las marcas de tiempo al formato POSIX cada vez que los datos se leen a R para hacer graficas + # 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 expresion "-desc()" adentro de la funcion arrange() indica que las marcas de tiempo se ordenaran de menos a mas recientes + # La expresión "-desc()" adentro de la función arrange() indica que las marcas de tiempo se ordenaran de menos a mas recientes dplyr::arrange(-desc(timestamp_ms)) -# Deberias de ver que la columna timestamp_ms con las marcas de tiempo esta en el formato "dttm", significando que la conversion a formato POSIX sea realizo bien +# Deberías de ver que la columna timestamp_ms con las marcas de tiempo esta en el formato "dttm", significando que la conversión a formato POSIX sea realizo bien glimpse(rfid_raw) rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) %>% - # Tienes que convertir las marcas de tiempo al formato POSIX cada vez que los datos se leen a R para hacer graficas + # 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")) ) %>% @@ -240,7 +240,7 @@ rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv" glimpse(rfid_pp) rfid_perch <- read.csv(file.path(path, "Data/processed/perching_events_RFID.csv")) %>% - # Tienes que convertir las marcas de tiempo de incio y final de cada evento de posar al formato POSIX cada vez que los datos se leen a R para hacer graficas + # 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")) @@ -251,7 +251,7 @@ glimpse(rfid_perch) ``` -Luego puedes combinar los datos originales y procesados en un solo `dataframe` para facilitar visualizar todos estos datos en la misma grafica. Vas a anadir una columna nueva (`dataset`) con etiquetas para identificar los dos conjuntos de datos diferentes. +Luego puedes combinar los datos originales y procesados en un solo `dataframe` para facilitar visualizar todos estos datos en la misma gráfica. Vas a añadir una columna nueva (`dataset`) con etiquetas para identificar los dos conjuntos de datos diferentes. ```{r} rfid_combined <- rfid_raw %>% @@ -272,32 +272,32 @@ glimpse(rfid_combined) ``` -Vas a construir la grafica con estos datos usando funciones de `ggplot2`, un paquete que es parte del `tidyverse` pero que tambien se puede instalar y usar afuera del `tidyverse`. Puedes revisar este [enlace](https://ggplot2.tidyverse.org/) que tiene mas recursos (en ingles) para aprender como usar la notacion de `ggplot` para hacer diferentes tipos de graficas. Estos recursos incluyen secciones de tres libros diferentes con ejercicios para practicar hacer graficas a diferentes niveles de experiencia, y tambien un curso en linea y un seminario en linea. Puedes encontrar otros recursos en espanol en linea, como esta [guia para `ggplot2`](https://raw.githubusercontent.com/rstudio/cheatsheets/main/translations/spanish/data-visualization_es.pdf). +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](https://ggplot2.tidyverse.org/) que tiene mas recursos (en ingles) para aprender como 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 a diferentes niveles de experiencia, y también un curso en linea y un seminario en linea. Puedes encontrar otros recursos en español en linea, como esta [guia para `ggplot2`](https://raw.githubusercontent.com/rstudio/cheatsheets/main/translations/spanish/data-visualization_es.pdf). -El paquete de `ggplot2` tiene una notacion unica para construir graficas, en que empiezas haciendo la grafica con llamar la funcion `ggplot()` y luego anades caracteristicas con anadir capas de otras funciones de `ggplot2` con el simbolo de `+`. +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()`, veras que la funcion inmediatamente dijuba una grafica vacia en tu panel de `Plots` (graficas) en RStudio. +Si llamas `ggplot()`, veras que la función inmediatamente dibuja una gráfica vacía en tu panel de `Plots` (gráficas) en RStudio. ```{r} ggplot() ``` -La grafica seguira vacia includo cuando le provees informacion sobre tus datos para poder configurar la estetica en los siguientes pasos. +La gráfica seguirá vacía incluso cuando le provees información sobre tus datos para poder configurar la estética en los siguientes pasos. ```{r} ggplot(data = rfid_combined) ``` -Necesitaras anadir otras funciones esteticas a esta capa fundamental de la grafica para poder ver tus datos. Las funciones que usaras para anadir detalles esteticos a la grafica vacia dependeran del tipo de grafica que quieres crear. En este ejemplo, vas a generar una grafica de codigo de barras, en que cada marca de tiempo esta representado por una linea vertical delgada. Las grafica de codigo de barra pueden ser graficas utiles cuando trabajas con marcas de tiempo, porque la informacion mas importante se contiene en una dimension (el tiempo en el eje x). Si fueras a resumir el numero de marcas de tiempo grabado cada dia, seria mejor hacer una grafica de lineas. +Necesitaras añadir otras funciones estéticas a esta capa fundamental de la gráfica para poder ver tus datos. Las funciones que usaras 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 esta representado por una linea 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 mas importante se contiene en una dimensión (el tiempo en el eje x). Si fueras a resumir el numero de marcas de tiempo grabado cada día, seria mejor hacer una gráfica de lineas. -Puedes anadir la funcion `geom_segment()` como la siguiente capa encima de la capa fundamental de la grafica. `geom_segment()` facilita anadir linea a una grafica, y las lineas pueden comunicar informacion en una o dos dimensiones (por su ancho en el eje x y su altura en el eje y). +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 linea a una gráfica, y las lineas pueden comunicar información en una o dos dimensiones (por su ancho en el eje x y su altura en el eje y). ```{r} ggplot(data = rfid_combined) + - # Anade una linea vertical para cada marca de tiempo + # Añade una linea vertical para cada marca de tiempo geom_segment( aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 @@ -305,14 +305,14 @@ ggplot(data = rfid_combined) + ``` -En el codigo arriba, `geom_segment()` anada una linea vertical a la grafica para cada deteccion 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 funcion que las lineas deberian de tener colores que corresponden al conjunto particular de datos. El argumento `color` tiene que estar adentro de la funcion `aes()` (que controla la estetica de esta capa de informacion) para que esta asignacion de colores se realice por el conjunto de datos. +En el código arriba, `geom_segment()` añade una linea 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 lineas 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 lineas se asignaran automaticamente por `ggplot` usando los colores por defecto del paquete, pero puedes cambiar estso colores usando la funcion `scale_color_manual()`: +Los colores de las lineas se asignaran automáticamente por `ggplot` usando los colores por defecto del paquete, pero puedes cambiar estos colores usando la función `scale_color_manual()`: ```{r} ggplot(data = rfid_combined) + - # Anade una linea vertical para cada marca de tiempo + # Añade una linea vertical para cada marca de tiempo geom_segment( aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 @@ -322,9 +322,9 @@ ggplot(data = rfid_combined) + ``` -Las lineas 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 grafica. +Las lineas 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 automaticamente determinar la estetica de la grafica, como el metodo 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 unicos de cada columna como numeros enteros y luego guarda los valores unicos de las secuencias de caracteres en una propiedad que se llama `levels` ("niveles" o "categorias"). Puedes cambiar el orden en que los valores unicos de una columna en formato `factor` se anaden a la grafica cuando cambias el orden de los `levels` de la columna: +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: ```{r} # Cambia la columna dataset al tipo de datos "factor" @@ -337,17 +337,17 @@ rfid_combined <- rfid_combined %>% # La columna de dataset ahora es tipo "fct" o "factor" glimpse(rfid_combined) -# Los niveles de la columna ahora estan ordenados para que el valor "raw" venga primero, en vez de estar en orden alfabetico +# 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) ``` -Despues de convertir la columna de `dataset` al tipo `factor` y reorganizar los `levels` de los valores unicos en esta columna, las categorias de esta columna deberian de aparecer en el orden correcto en la leyenda de la grafica y no en orden alfabetico. Tambien deberias de ver que los colores asignados a cada conjunto de datos acaba de cambiar. +Después de convertir la columna de `dataset` al tipo `factor` y reorganizar los `levels` de los valores únicos en esta columna, las categorías de esta columna deberían de aparecer en el orden correcto en la leyenda de la gráfica y no en orden alfabético. También deberías de ver que los colores asignados a cada conjunto de datos acaba de cambiar. ```{r} ggplot(data = rfid_combined) + - # Anade una linea vertical para cada marca de tiempo + # Añade una linea vertical para cada marca de tiempo geom_segment( aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 @@ -357,12 +357,12 @@ ggplot(data = rfid_combined) + ``` -En la grafica que acabas de hacer, es muy dificil de discriminar entre las lineas para cada conjunto de datos. Puedes usar la funcion `facet_wrap()` para dividir los conjuntos de datos en paneles diferentes: +En la gráfica que acabas de hacer, es muy difícil de discriminar entre las lineas para cada conjunto de datos. Puedes usar la función `facet_wrap()` para dividir los conjuntos de datos en paneles diferentes: ```{r} ggplot(data = rfid_combined) + - # Anade una linea vertical para cada marca de tiempo + # Añade una linea vertical para cada marca de tiempo geom_segment( aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 @@ -370,25 +370,25 @@ ggplot(data = rfid_combined) + scale_color_manual(values = c("orange", "darkgreen")) + - # El simbolo de ~ significa "por", asi que estas creando un panel por cada valor unico (o categoria) en la columna dataset + # El símbolo de ~ significa "por", así que estas 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 grafica y cada panel contiene datos un solo conjunto de datos. Con este cambio estructural tambien alineaste los panels en el mismo eje x para que sea mas facil comparar patrones temporales. +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 mas fácil comparar patrones temporales. -Desde este punto de vista es dificil ver como los dos conjuntos de datos (originales y procesados) son diferentes. Puedes filtrar el `dataframe` con functiones del `tidyverse` para visualizar solo las primeras dos detecciones para cada conjunto de datos: +Desde este punto de vista es difícil ver como los dos conjuntos de datos (originales y procesados) son diferentes. Puedes filtrar el `dataframe` con funciones del `tidyverse` para visualizar solo las primeras dos detecciones para cada conjunto de datos: ```{r} ggplot(data = rfid_combined %>% - # Crea grupos por las categorias o levels en la columna dataset + # Crea grupos por las categorías o levels en la columna dataset group_by(dataset) %>% # Selecciona las primas dos filas para cada grupo slice(1:2) %>% ungroup() ) + - # Anade una linea vertical para cada marca de tiempo + # Añade una linea vertical para cada marca de tiempo geom_segment( aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 @@ -396,19 +396,19 @@ ggplot(data = rfid_combined %>% scale_color_manual(values = c("orange", "darkgreen")) + - # El simbolo de ~ significa "por", asi que estas creando un panel por cada valor unico (o categoria) en la columna dataset + # El símbolo de ~ significa "por", así que estas creando un panel por cada valor único (o categoría) en la columna dataset facet_wrap(~ dataset, nrow = 2, strip.position = "left") ``` -Ahora deberias de ver que la segunda marca de tiempo en los datos originales se elimino del conjunto de datos procesados (fue filtrado usando el umbral temporal en `preprocess_detections`). +Ahora deberías de ver que la segunda marca de tiempo en los datos originales se elimino del conjunto de datos procesados (fue filtrado usando el umbral temporal en `preprocess_detections`). -Luego puedes anadir 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 informacion sobre el inicio y el fin de cada evento de posar. Puedes anadir este conjunto de datos a la grafica con otra llamada de la funcion `geom_segment()`. Usaras esta capa de `geom_segment()` para anadir lineas que contienen informacion temporal sobre cuando los eventos de posar empezaron y terminaron. +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()`. Usaras esta capa de `geom_segment()` para añadir lineas que contienen información temporal sobre cuando los eventos de posar empezaron y terminaron. ```{r} ggplot(data = rfid_combined) + - # Anade una linea vertical para cada marca de tiempo + # Añade una linea vertical para cada marca de tiempo geom_segment( aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 @@ -416,10 +416,10 @@ ggplot(data = rfid_combined) + scale_color_manual(values = c("orange", "darkgreen")) + - # El simbolo de ~ significa "por", asi que estas creando un panel por cada valor unico (o categoria) en la columna dataset + # El símbolo de ~ significa "por", así que estas creando un panel por cada valor único (o categoría) en la columna dataset facet_wrap(~ dataset, nrow = 2, strip.position = "left") + - # Anade los eventos de posar + # Añade los eventos de posar geom_segment( data = rfid_perch, aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5), @@ -429,14 +429,14 @@ ggplot(data = rfid_combined) + ``` -En el codigo arriba para `geom_segment()`, especificaste que querias anadir otro conjunto de datos a la grafica cuando usaste el argumento `data`. Los argumentos `x` y `y` determinan donde va a empezar cada linea en ambos ejes de la grafica, respectivamente. Tambien vas a tener que especificar donde quieres que cada linea termina en cada eje. En el eje x, indicaste que quieres que la linea 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 numeros que usaste para los argumentos `y` y `yend` determinan donde las lineas para los eventos de posar se dibujaran, que en este caso es justamente arriba de las lineas para los otros conjuntos de datos. Las lineas para los eventos de posar se dibujaron como otra capa de informacion encima de cada panel de la grafica por defecto. +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 linea en ambos ejes de la gráfica, respectivamente. También vas a tener que especificar donde quieres que cada linea termina en cada eje. En el eje x, indicaste que quieres que la linea 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 lineas para los eventos de posar se dibujaran, que en este caso es justamente arriba de las lineas para los otros conjuntos de datos. Las lineas 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 grafica para que sea mas facil de interpretar. Puedes cambiar la posicion de la leyenda usando el argumento `legend.position` adentro de la funcion general de `theme()`. Abajo puedes guardar la grafica adentro de un objeto, para que no tengas que escribir todo el codigo de la grafica de nuevo cuando quieres anadirle mas informacion. +Puedes hacer unos cambios a la gráfica para que sea mas 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 mas información. ```{r} gg <- ggplot(data = rfid_combined) + - # Anade una linea vertical para cada marca de tiempo + # Añade una linea vertical para cada marca de tiempo geom_segment( aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 @@ -444,10 +444,10 @@ gg <- ggplot(data = rfid_combined) + scale_color_manual(values = c("orange", "darkgreen")) + - # El simbolo de ~ significa "por", asi que estas creando un panel por cada valor unico (o categoria) en la columna dataset + # El símbolo de ~ significa "por", así que estas creando un panel por cada valor único (o categoría) en la columna dataset facet_wrap(~ dataset, nrow = 2, strip.position = "left") + - # Anade los eventos de posar + # Añade los eventos de posar geom_segment( data = rfid_perch, aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5), @@ -463,21 +463,21 @@ gg ``` -Ahora puedes hacer unos ajustes menores para seguir mejorando a la grafica, incluyendo cambiar los titulos de los ejes para que sean mas informativos, cambiar el color de fondo a blanco, y eliminar el text en el eje y tnato como las rayas en el eje y, porque este eje no contiene informacion para interpretacion de los datos (o sea, la altura de cada linea no contiene informacion para interpretacion). +Ahora puedes hacer unos ajustes menores para seguir mejorando a la gráfica, incluyendo cambiar los títulos de los ejes para que sean mas 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 linea no contiene información para interpretación). ```{r} gg <- gg + - # Cambia los titulos de ambos ejes + # Cambia los títulos de ambos ejes xlab("Date and time") + - # El eje y no contiene informacion y por ende puedes eliminar este titulo + # El eje y no contiene información y por ende puedes eliminar este titulo ylab("") + - # Usa esta funcion para cambiar el color de fondo a blanco y negro + # Usa esta función para cambiar el color de fondo a blanco y negro theme_bw() + - # Usa funciones de estetica para eliminar el texto en el eje y y tambien los rayos en este eje + # 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(), @@ -488,16 +488,16 @@ gg ``` -Puedes guardar las graficas que creas en R como archivos fisicos. Abajo usaras la funcion `ggsave()` para escribir la grafica que hiciste arriba como un archico en tu computadora. +Puedes guardar las gráficas que creas en R como archivos físicos. Abajo usaras la función `ggsave()` para escribir la gráfica que hiciste arriba como un archivo en tu computadora. ```{r} gg -# Guarda la grafica como un archivo en tu computadora +# 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 publicacion. Por ejemplo, puedes cambiar el tamano final del archivo (`width` o "lo ancho", `height` o "la altura"), tanto como la resolucion en pixeles (`dpi`). Tambien puedes cambiar el tamano de texto en cada eje o del titulo de cada eje, o la posicion de la layenda mientras determinas el tamano final del imagen. +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 "lo 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 titulo de cada eje, o la posición de la leyenda mientras determinas el tamaño final del imagen. -En el siguiente tutorial vas a continuar analizando datos con ABISSMAL y vas a crear una grafica de codigo de barras mas compleja y refinada. \ No newline at end of file +En el siguiente tutorial vas a continuar analizando datos con ABISSMAL y vas a crear una gráfica de código de barras mas compleja y refinada. \ No newline at end of file diff --git a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd index d8319fc..3bb1150 100644 --- a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd +++ b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd @@ -22,7 +22,7 @@ Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las

Resumen del tutorial y objetivos de aprendizaje

-En este sexto y ultimo 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` ("cumulos") de detecciones que representan eventos de movimientos distintos y luego vas a anotar inferencias de comportamiento de estos eventos de movimiento. Tambien vas a crear graficas para visualizar las inferencias de comportamiento. Vas a continuar a usar habilidades que aprendistes en los tutoriales anteriores, y tambien vas a aprender como crear visualizaciones mas complejas con `ggplot()`. +En este sexto y ultimo 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 como crear visualizaciones mas complejas con `ggplot()`.

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

@@ -30,10 +30,10 @@ En este sexto y ultimo tutorial, vas a terminar de usar las detecciones simulada rm(list = ls()) # Limpia tu ambiente global -library(tidyverse) # Carga la coleccion de paquetes del tidyverse -library(data.table) # Carga otros paquetes requiridos por las funciones de ABISSMAL +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" # Inicializar un objeto con el path de tu directorio de trabajo +path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Inicializa un objeto con el path de tu directorio de trabajo ``` @@ -41,28 +41,28 @@ path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Inicializar un objeto co ```{r} -# Carga la funcion que detecta clusters en los datos procesados +# Carga la función que detecta clusters en los datos procesados source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_clusters.R") -# Carga la funcion que anota inferencias de comportamiento sobre los clusters +# 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 funcion arriba require +# Carga un archivo con funciones de apoyo que cada función arriba requiere source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") ```

Termina el `pipeline` de ABISSMAL

-Aqui usaras la funcion de ABISSMAL que se llama `detect_clusters()` para identificar `clusters` de detecciones a traves de los tipos de sensores (detecciones de diferentes sensores que se grabaran juntos en el tiempo). +Aquí usaras 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 se grabaran juntos en el tiempo). ```{r} -# El argumento run length, o lo largo de cada serie de detecciones que ocurrieron juntos en el tiempo (cluster) tiene que ser 1 para poder detectar clusters con un largo de 2 detecciones +# 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 funcion `score_clusters()`. +Luego vas a anotar inferencias de comportamiento de estos `clusters` de detecciones con la función `score_clusters()`. ```{r} score_clusters(file_nm = "detection_clusters.csv", rfid_label = "RFID", camera_label = NULL, outer_irbb_label = "Outer Beam Breakers", inner_irbb_label = "Inner Beam Breakers", video_metadata_col_nms = NULL, integrate_perching = TRUE, perching_dataset = "RFID", perching_prefix = "perching_events_", sensor_id_col_nm = "sensor_id", PIT_tag_col_nm = "PIT_tag", pixel_col_nm = NULL, video_width = NULL, video_height = NULL, integrate_preproc_video = FALSE, video_file_nm = NULL, timestamps_col_nm = NULL, path = path, data_dir = "Data/processed", out_dir = "Data/processed", out_file_nm = "scored_detectionClusters.csv", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") @@ -75,7 +75,7 @@ Puedes revisar los resultados finales ahora que terminaste de ejecutar el `pipel ```{r} scored_clusters <- read.csv(file.path(path, "Data/processed", "scored_detectionClusters.csv")) %>% - # Tienes que convertir las marcas de tiempo al formato POSIX cada vez que los datos se leen a R para hacer graficas + # 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")) @@ -87,11 +87,11 @@ glimpse(scored_clusters) ``` -Cuantos eventos de entrada y salida se anotaron por dia? +Cuantos eventos de entrada y salida se anotaron por día? -

Datos ausentos en R

+

Datos ausentes en R

-Para poder contra la cantidad de cada uno de estos eventos que ABISSMAL anoto por dia, necesitas saber como 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 especifico en R. Puedes determinar si un vector (o una columna en un `dataframe`) contiene datos ausentes si usas la funcion `is.na()`, que develovera `TRUE` cuando encuentra un valor de `NA` (un valor ausente) en el vector actual. +Para poder contra la cantidad de cada uno de estos eventos que ABISSMAL anoto por día, necesitas saber como 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 especifico en R. Puedes determinar si un vector (o una columna en un `dataframe`) contiene datos ausentes si usas la función `is.na()`, que devolverá `TRUE` cuando encuentra un valor de `NA` (un valor ausente) en el vector actual. ```{r} ?is.na() @@ -102,67 +102,65 @@ is.na(x) ``` -En el codigo arriba, creaste un vector que se llama `x` que tiene dos valores de `NA`. La funcion `is.na()` revisa si cada elemento de `x` es equivalente a `NA`, y devuelve `TRUE` cuando se cumple esa condicion (o sea, cuando encuentra un dato ausente). +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, tambien puedes usar otros simbolos especiales que son relevantes a frases condicionales, como el simbolo de `!`, que sirve para invertir una frase condicional. Por ejemplo, en el codigo abajo, cuando anades `!` antes de `is.na()`, estas preguntando si cada elemento de `x` *no* es equivalente a `NA`: +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()`, estas preguntando si cada elemento de `x` *no* es equivalente a `NA`: ```{r} !is.na(x) ``` -Como puedes ver, anadir 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 convirtio 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 utiles para encontrar y filtrar las filas de un `dataframe`. +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 funcion `dplyr::filter()` eliminara una fila cada vez que encuentra un valor de `FALSE` en la columna actual y en cambio no eliminara 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, anadirias `!is.na(name_of_column)` adentro de `dplyr::filter()`, que deberia de devolver `FALSE` cada vez que encuentra una fila con `NA`, y eliminara esa fila como parte de la operacion de filtrar. +Por ejemplo, la función `dplyr::filter()` eliminara una fila cada vez que encuentra un valor de `FALSE` en la columna actual y en cambio no eliminara 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 eliminara esa fila como parte de la operación de filtrar. -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. +

Cuenta eventos por día

-

Cuenta eventos por dia

- -Ahora puedes escribir codigo para contar el numero de eventos de entrada y salida que ABISSMAL anoto por dia. Vas a necesitar 1) crear una columna nueva con la informacion sobre el dia para cada marca de tiempo, 2) eliminar filas con datos ausentes para la informacion anotada de la direccion del movimiento (la columna `direction_scored`, porque esta informacion no se puede anotar para algunos movimientos), 3) agrupar el `dataframe` por dia y por la direccion anotada, y luego 4) contar el numero de filas por grupo. +Ahora puedes escribir código para contar el numero de eventos de entrada y salida que ABISSMAL anoto 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 numero de filas por grupo. ```{r} scored_clusters %>% - # Extrae el dia de cada marca de tiempo y crea una columna nueva con esta informacion + # Extrae el día de cada marca de tiempo y crea una columna nueva con esta información dplyr::mutate( day = lubridate::day(start) ) %>% - # Aqui estas usando la funcion is.na() que devolvera TRUE cuando encuentra un valo ausente (NA) en la columna actual. Con colocar el simbolo de ! antes de is.na(), estas invirtiendo la frase condicional y tambien el resultado, asi que todos los valores TRUE se convertiran a FALSE. Por ende, dplyr::filter eliminara todas las filas que devuelven el valor de FALSE en esta expresion (o sea, todas las filas con valores ausentes en la columna direction_scored con informacion sobre la direccion anotada de movimiento) + # 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(), estas invirtiendo la frase condicional y también el resultado, así que todos los valores TRUE se convertirán a FALSE. Por ende, dplyr::filter eliminara 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 contrar filas (eventos). Aqui quieres saber el numero de entradas y salidas (categorias en la columna direction_scored) por dia (categorias en la columna day) + # Agrupa el dataframe por ambas columnas para las cuales quieres contar filas (eventos). Aquí quieres saber el numero 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 numero de filas aqui es el numero de entradas o salidas anotadas por dia + # Luego puedes resumir los datos: el numero de filas aquí es el numero de entradas o salidas anotadas por día dplyr::summarise( n = n() ) ``` -Cuatro entradas y salidas se anotaron por dia. Como se compara este resultado con el numero de entradas y salidas que esperabas por dia? Si regresas al codigo en los tutoriales anteriores (el tercer y el cuarto tutorial) donde creaste los datos originales simulados, deberias de poder ver que empezaste por simular dos entradas y dos salidas por dia en los datos para el sistema de RFID y los sensores de infrarrojo. Luego anadiste dos entradas y dos salidas mas por dia cuando simulaste fallas de deteccion del sistema de RFID (o sea, estos fueron movimientos capturados solamente por los sensores de infrarrojo). `score_clusters` detecto el numero correcto de entradas y salidas por dia. +Cuatro entradas y salidas se anotaron por día. Como se compara este resultado con el numero de entradas y salidas que esperabas por día? Si regresas al código en los tutoriales anteriores (el tercer y el cuarto tutorial) donde creaste los datos originales simulados, deberías de poder ver que empezaste por simular dos entradas y dos salidas por día en los datos para el sistema de RFID y los sensores de infrarrojo. Luego añadiste dos entradas y dos salidas mas por día cuando simulaste fallas de detección del sistema de RFID (o sea, estos fueron movimientos capturados solamente por los sensores de infrarrojo). `score_clusters` detecto el numero 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 informacion sobre las etiquetas de PIT). Luego puedes seleccionar solo las columnas que contienen informacion que es util revisar, como las identidades de las etiquetas PIT, y tambien las marcas de tiempo para el inicio y fin de cada evento de posar. +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. ```{r} scored_clusters %>% - # Usa una frase condicional con is.na() para retener solo las filas que tienen codigos de etiquetas PIT que fueron asociados con eventos de posar + # 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 colo las columnas que quieres revisar visualmente dplyr::select(start, end, perching_PIT_tag) ``` -Como aprendiste en el tercer y el cuarto tutorial, el primer evento de posar por dia se realizo por el primer individuo (con la etiqueta de PIT "1357aabbcc"), y el segundo evento de posar por dia fue realizado por el segundo individuo (con la etiqueta de PIT "2468zzyyxx"). +Como aprendiste en el tercer y el cuarto tutorial, el primer evento de posar por día se realizo 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 movimento que no fueron eventos de posar fueron asignados a cada individuo? +Cuantos eventos de movimiento que no fueron eventos de posar fueron asignados a cada individuo? ```{r} scored_clusters %>% - # Extrae el dia de cada marca de tiempo y crea una columna nueva con esta informacion + # 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 codigos de las etiquetas de PIT que no fueron asociados con eventos de posar - # Aqui estas combinando dos frases condicionales para poder buscar filas en la columna individual_initiated (o el individuo que inicio el movimiento) que tienen codigos de etiquetas PIT, pero que tambien no tienen una etiqueta de sensor el la columna perching_sensor (o sea, eventos de movimiento que no fueron eventos de posar) + # 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 inicio el movimiento) que tienen códigos de etiquetas PIT, pero que también no tienen una etiqueta de sensor el 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( @@ -171,17 +169,17 @@ scored_clusters %>% ``` -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 dia, que es exactamente lo que simulaste en el tercer y el cuarto tutorial. Mas movimientos que no fueron eventos de posar se detectaron a traves de estos dias tambien, 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 solo los sensores de infrarrojo que no pueden grabar informacion sobre la identidad de individuos. +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. Mas 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 solo los sensores de infrarrojo que no pueden grabar información sobre la identidad de individuos. -

Construye una grafica compleja de codigo de barras

+

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

-Ahora puedes visualizar los resultados finales. En el codigo abajo vas a aprender como crear una grafica de codigo de barras que es mas compleja de lo que viste en el tutorial anterior, pero que es mas facil que interpretar que esa grafica anterior. Para esta grafica seria util poder visualizar tres tipos de inferencias de comportamiento o tipos de informacion a traves del tiempo: la direccion de movimiento (cuando esta disponsible), la identidad del individuo (cuando esta disponible), y los eventos de posar. +Ahora puedes visualizar los resultados finales. En el código abajo vas a aprender como crear una gráfica de código de barras que es mas compleja de lo que viste en el tutorial anterior, pero que es mas fácil que interpretar que esa gráfica anterior. Para esta gráfica seria ú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 esta disponible), la identidad del individuo (cuando esta disponible), y los eventos de posar. -Puedes empezar con construir la grafica con anadir lineas verticles para los eventos que no fueron eventos de posar. El color de cada linea indicara la identidad del individuo, incluyendo cuando esta informacion no estaba disponible. El tipo de linea va a contener informacion sobre la direccion de movimiento, y tambien cuando esta informacion no se pudo anotar. +Puedes empezar con construir la gráfica con añadir lineas verticales para los eventos que no fueron eventos de posar. El color de cada linea indicara la identidad del individuo, incluyendo cuando esta información no estaba disponible. El tipo de linea 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 lineas de la forma que quieres en la grafica, vas a necesitar modificar el `dataframe` de los resultados finales para convertir los `NAs` en las dos columnas asociadas con estos detalles de estetica para convertir los datos ausentes en informacion util. Por ejemplo, cuando no hay informacion sobre la identidad del individuo, seria muy util convertir los valores asociados de `NA` a un valor como "unassigned" ("no asignado"). En el codigo abajo vas a usar la funcion `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 informacion mas util parala grafica que vas a hacer mas adelante. +Para poder asignar colores y tipos de lineas 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, seria 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 mas útil para la gráfica que vas a hacer mas adelante. -Primero puedes practicar usar `is.na()` adentro de `ifelse()` para crear un vector nuevo. En el codigo abajo, vas a proveer la frase condicional que quieres probar (aqui vas a probar si la columna que contiene las etiquetas PIT del individuo que inicio el movimiento tiene valores de `NA`), el valor que quieres anadir al vetcor si la condicion se cumple (el valor "unassigned" cuando no hay informacion sobre la etiqueta PIT), y luego el valor que quieres anadirn si la condicion no se cumple (en este caso es devolver la etiqueta PIT en la columna individual_initiated si el valor actual no es `NA`). +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 inicio el movimiento tiene valores de `NA`), el valor que quieres añadir al vector si la condición se cumple (el valor "unassigned" cuando no hay información sobre la etiqueta PIT), y luego el valor que quieres añadir si la condición no se cumple (en este caso es devolver la etiqueta PIT en la columna individual_initiated si el valor actual no es `NA`). ```{r} # is.na() devuelve TRUE cuando encuentra valores de NA adentro de un vector (o columna) y FALSE cuando el valor actual no es NA @@ -200,44 +198,44 @@ scored_clusters_gg <- scored_clusters %>% # 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 (aqui "not_scored" significa que la direccion no sen pudo anotar) - # En la frase condicional abajo anadiste is.na(perching_sensor) (despues del simbolo de &) para solo convertir los valores de NA en la columna de direction_scored si tambien no fueron anotados como eventos de posar en la columna perching_sensor + # Repita este proceso para la columna direction_scored pero con un valor diferente (aquí "not_scored" significa que la dirección no sen pudo anotar) + # En la frase condicional abajo añadiste is.na(perching_sensor) (después del símbolo de &) para solo 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 (categorias) para crear la grafica de la forma que queremos (por ejemplo, los valores de "unassigned" y "not scored" deberian de ser los ultimos valores en la leyenda) + # 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 funcion distinct() para ver que todos los valores unicos de cada columna si fueron modificados +# Revisa los cambios que hiciste arriba usando la función distinct() para ver que todos los valores únicos de cada columna si 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) ``` -Este resultado se ve bien. Deberias de ver valores de `NA` en el `dataframe` pero estan en la columna "direction_scored" y asociados con los eventos de posar, que vas a anadir a la grafica en otra capa diferente de codigo mas adelante. +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 mas adelante. -Puedes usar este `dataframe` modificado para crear la grafica. En el codigo abajo, vas a anadir lineas con colores asignados por la identidad de individuos, y los tipos de linea van a representar la direccion de movimiento. Primero vas a especificar los detalles esteticos de la grafica: +Puedes usar este `dataframe` modificado para crear la gráfica. En el código abajo, vas a añadir lineas con colores asignados por la identidad de individuos, y los tipos de linea van a representar la dirección de movimiento. Primero vas a especificar los detalles estéticos de la gráfica: ```{r} -# Los colores estan el el mismo order que los levels (categorias) de la columna individual_initiated, asi que el color naranja va a representar la etiqueta PIT "1357aabbcc" +# Los colores están el el mismo orden que los levels (categorías) de la columna individual_initiated, así que el color naranja va a representar la etiqueta PIT "1357aabbcc" levels(scored_clusters_gg$individual_initiated) cols <- c("orange", "darkgreen", "black") -# Los tipos de linea estan el el mismo order que los levels de la columna direction_scored, asi que el valor "dotted" ("puntos) va a representar "not scored" (cuando la direccion no se pudo anotar) +# Los tipos de linea están el 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) ltys <- c("solid", "longdash", "dotted") ``` -Luego puedes anadir lineas a la grafica por identidad de individuo. Aqui estas dividiendo las llamadas de `geom_segment()` por los levels (categorias o valores unicos) en la columna individual_initiated. Solo anadiste lineas para el primer individuo y todos los movimientos que no fueron eventos de posar que no fueron asignados a un individuo porque despues de revisar los resultados finales, sabes que ningun movimiento (que no fue evento de posar) fue asignado al segundo individuo. +Luego puedes añadir lineas a la gráfica por identidad de 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 lineas 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. ```{r} ggplot() + - # Anade una linea vertical para cada evento que no fue un evento de posar y que fue asignado al primer individuo + # Añade una linea 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"), @@ -246,7 +244,7 @@ ggplot() + linewidth = 0.5 ) + - # Anade una linea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos de los dos individuos + # Añade una linea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos de los dos individuos geom_segment( data = scored_clusters_gg %>% dplyr::filter(individual_initiated == "unassigned"), @@ -255,17 +253,17 @@ ggplot() + linewidth = 0.5 ) + - # Anade los tipos de linea customizadas a la grafica + # Añade los tipos de linea customizadas a la gráfica scale_linetype_manual(values = ltys) + # Elimina el titulo del eje y ylab("") + - # Usa esta funcion para convertir el fondo de la grafica a blanco y negro + # Usa esta función para convertir el fondo de la gráfica a blanco y negro theme_bw() + - # Usa estas funciones de estetica para elimninar el text y los rayos del eje y - # Anade un argumento para cambiar la posicion de la leyenda adentro de la grafica + # 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(), @@ -274,22 +272,22 @@ ggplot() + ``` -Para hacer esta grafica organizaste las lineas verticales para separar los movimientos asignados al primer individuo y los movimientos que no fueron asignados. Esta separacion vertical entre estos dos conjuntos de datos facilita las comparaciones visuales de patrones de variacion en los movimientos a traves del tiempo. Creaste esta separacion vertical con cambiar los valores que usaste para `y` y `yend` en la segunda capa de `geom_segment()` para que esas lineas empezarian mas arriba que las lineas de la primera capa de `geom_segment()`. +Para hacer esta gráfica organizaste las lineas 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 patrones de variación en los movimientos a través del tiempo. 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 lineas empezarían mas arriba que las lineas de la primera capa de `geom_segment()`. -Puedes hacer mas modificaciones que ayudarian con interpretar esta grafica. En primerm lugar, las etiquetas de los paneles que estan al lado izquierdo se pueden cambiar para demostrar el dia general de coleccion de datos (como "Day 1" para el primer dia) en vez de la fecha. El texto en en eje x tambien se puede cambiar para demostrar solo el tiempo (o sea eliminar la informacion sobre el mes y el dia), y tambien puedes anadir mas etiquetas (por ejemplo, una etiqueta cada media hora). +Puedes hacer mas 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 solo el tiempo (o sea eliminar la información sobre el mes y el día), y también puedes añadir mas etiquetas (por ejemplo, una etiqueta cada media hora). -Puedes emmpezar con modificar el `dataframe` para anadir la informacion sobre el dia de coleccion de datos. Vas a crear esta columna nueva con frases condicionales a traves de la funcion `ifelse` porque solo hay tres dias de coleccion de datos con etiquetas que tienes que cambiar. +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 solo hay tres días de colección de datos con etiquetas que tienes que cambiar. ```{r} -# Crea una columna nueva en los datos originales para la fecha de coleccion de datos +# 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 informacion sobre el dia + # Primero necesitas crear una columna con información sobre el día dplyr::mutate( day = lubridate::day(start) ) %>% dplyr::mutate( - # Luego deberias de reemplazar la etiqueta para cada dia y guardar estos resultados en una columna nueva - day_label = ifelse(day == 1, "Day 1", day), # Aqui el ultimo argumento es la columna day porque la columna day_label no se ha creado todavia + # 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 ultimo 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) ) @@ -302,13 +300,13 @@ scored_clusters_gg2 %>% ``` -Ahora puedes actualizar el codigo para incluir las etiquetas nuevas de las fechas: +Ahora puedes actualizar el código para incluir las etiquetas nuevas de las fechas: ```{r} -# Anade el dataframe como el conjunto de datos por defecto para la capa fundamental de la grafica para que la funcion facet_wrap() tenga datos +# 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) + - # Anade una linea vertical para cada evento que no fue un evento de posar y que fue asignado al primer individuo + # Añade una linea 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"), @@ -317,7 +315,7 @@ ggplot(data = scored_clusters_gg2) + linewidth = 0.5 ) + - # Anade una linea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos de los dos individuos + # Añade una linea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos de los dos individuos geom_segment( data = scored_clusters_gg2 %>% dplyr::filter(individual_initiated == "unassigned"), @@ -326,31 +324,31 @@ ggplot(data = scored_clusters_gg2) + linewidth = 0.5 ) + - # Anade los tipos de linea customizadas a la grafica + # Añade los tipos de linea customizadas a la gráfica scale_linetype_manual(values = ltys) + # Elimina el titulo del eje y ylab("") + - # Usa esta funcion para convertir el fondo de la grafica a blanco y negro + # Usa esta función para convertir el fondo de la gráfica a blanco y negro theme_bw() + - # Usa estas funciones de estetica para elimninar el text y los rayos del eje y - # Anade un argumento para cambiar la posicion de la leyenda adentro de la grafica + # 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 grafica por dia, aqui usaras las etiquetas nuevas de dia + # Crea paneles en la gráfica por día, aquí usaras las etiquetas nuevas de día facet_wrap(~ day_label, nrow = 3, strip.position = "left") ``` -Ahora que moviste la informacion sobre el dia de coleccion de datos a las etiquetas de los paneles de la grafica, necesitas componer el texto en el eje x. La grafica sera mas facil de interpretar si puedes alinear las marcas de tiempo por hora y minuto para una comparacion directa a traves de dias diferentes. +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 sera mas 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 traves de dias, necesitas actualizar el formato de las columnas que contienen las marcas de tiempo. El codigo para convertir las marcas de tiempo a un formato diferente es anidado y repetitivo pero la conversion se realizara correctamente. Cuando le comunicas a R que deberias de convertir las marcas de tiempo a un formato con horas, minutos, y segundos solamente, R va a anadir un año, un mes, y un dia por defecto antes de la marca de tiempo (lo mas comun es que usara la fecha actual). Este comportamiento es esperado y no es un error, mas bien facilita que las marcas de tiempo se alinean de la forma correcta a traves de dias (paneles) en la grafica (porque R considera todas las marcas de tiempo como si ocurrieron en un solo dia). +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 realizara 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 mas común es que usara la fecha actual). Este comportamiento es esperado y no es un error, mas 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). ```{r} scored_clusters_gg3 <- scored_clusters_gg2 %>% @@ -359,17 +357,17 @@ scored_clusters_gg3 <- scored_clusters_gg2 %>% end_gg = as.POSIXct(strptime(format(as.POSIXct(end), "%H:%M:%S"), format = "%H:%M:%S")) ) -# Deberias de ver que un año, mes, y dia nuevo fueron adjuntados a las marcas de tiempo modificadas, pero este resultado es esperado (ve arriba) +# Deberías de ver que un año, mes, y día nuevo fueron adjuntados a las marcas de tiempo modificadas, pero este resultado es esperado (ve arriba) glimpse(scored_clusters_gg3) ``` -Ahora puedes actualizar el codigo de crear la grafica para cambiar la estetica del eje x usando la funcion `scale_x_datetime()` para especificar que quieres etiquetas cada media hora en este eje. Tambien vas a anadir un titulo para el eje x y eliminar la cuadricula en el eje y adentro de cada panel: +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 titulo para el eje x y eliminar la cuadricula en el eje y adentro de cada panel: ```{r} gg <- ggplot(data = scored_clusters_gg3) + - # Anade una linea vertical para cada evento que no fue un evento de posar y que fue asignado al primer individuo + # Añade una linea 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"), @@ -378,7 +376,7 @@ gg <- ggplot(data = scored_clusters_gg3) + linewidth = 0.5 ) + - # Anade una linea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos de los dos individuos + # Añade una linea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos de los dos individuos geom_segment( data = scored_clusters_gg3 %>% dplyr::filter(individual_initiated == "unassigned"), @@ -387,33 +385,33 @@ gg <- ggplot(data = scored_clusters_gg3) + linewidth = 0.5 ) + - # Anade los tipos de linea customizadas a la grafica + # Añade los tipos de linea customizadas a la gráfica scale_linetype_manual(values = ltys) + # Elimina el titulo del eje y ylab("") + - # Usa esta funcion para convertir el fondo de la grafica a blanco y negro + # Usa esta función para convertir el fondo de la gráfica a blanco y negro theme_bw() + - # Usa estas funciones de estetica para elimninar el text y los rayos del eje y - # Anade un argumento para cambiar la posicion de la leyenda adentro de la grafica + # 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 grafica por dia, aqui usaras las etiquetas nuevas de dia + # Crea paneles en la gráfica por día, aquí usaras las etiquetas nuevas de día facet_wrap(~ day_label, nrow = 3, strip.position = "left") + - # Cambia la estetica de las etiquetas del eje x + # Cambia la estética de las etiquetas del eje x scale_x_datetime( date_breaks = "30 mins", date_labels = "%H:%M" ) + - # Anade un titulo para el eje x + # Añade un titulo para el eje x xlab("Time of day (HH:MM)") + # Puedes quitar la cuadricula en el eje (mayor y menor) adentro de cada panel @@ -426,12 +424,12 @@ gg ``` -Todavia falta un tipo de informacion importante que necesitas anadir a esta grafica: los eventos de posar. Vas a anadir esta informacion con otra capa de `geom_segment()` pero ahora vas a crear lineas cortas y anchas para que aparezan mas como puntos en la grafica: +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 lineas cortas y anchas para que aparezcan mas como puntos en la gráfica: ```{r} gg <- gg + - # Anade los eventos de posar como lineas con orillas redondeadas, y puedes anadir colores que indican la identidad del individuo a traves de la columna individual_initiated + # Añade los eventos de posar como lineas 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)), @@ -439,28 +437,28 @@ gg <- gg + linewidth = 2, lineend = "round" ) + - # Anade los colores customizados para estos eventos de posar. Los colores tambien aplican a las lineas de movimientos por individuo que no fueron eventos de posar que anadiste en capas anteriores de geom_segment() + # Añade los colores customizados para estos eventos de posar. Los colores también aplican a las lineas 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 deberias de ver que una leyenda de color aparece arriba de la grafica con la adicion de esta capa adicional de `geom_segment()`. Ambas de las leyendas se pueden mejorar. Puedes modificar la grafica con actualizar el titulo de cada leyenda, aumentar el tamano de texto de cada leyenda, y reducir el espacio blanco ente las leyendas y la grafica. Para modificar los titulos de cada leyenda, vas a usar las funciones `guides()` y `guide_legend()`. Para aumentar el tamano de texto en la leyenda, vas a usar el argumento `legend.text` adentro de la funcion `theme()`, y para reducir el espacio blanco entre la grafica y las leyendas, vas a usar el argumento `legend.margin` adentro de la funcion `theme()`. +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 titulo de cada leyenda, aumentar el tamaño de texto de cada leyenda, y reducir el espacio blanco ente las leyendas y la gráfica. Para modificar los títulos de cada leyenda, vas a usar las funciones `guides()` y `guide_legend()`. Para aumentar el tamaño de texto en la leyenda, vas a usar el argumento `legend.text` adentro de la función `theme()`, y para reducir el espacio blanco entre la gráfica y las leyendas, vas a usar el argumento `legend.margin` adentro de la función `theme()`. ```{r} -# Ve mas informacion sobre la funcion que controla los margenes (espacio blanco) alrededor de la leyenda +# Ve mas información sobre la función que controla los margenes (espacio blanco) alrededor de la leyenda ?margin gg <- gg + - # Aumenta el tamano de texto de cada leyenda y reduce el espacio blanco entre la grafica y las leyendas + # 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 titulos de cada leyenda + # Modifica los títulos de cada leyenda guides( linetype = guide_legend(title = "Direction"), color = guide_legend(title = "Individual") @@ -470,24 +468,24 @@ gg ``` -Finalmente puedes guardar esta grafica como un archivo: +Finalmente puedes guardar esta gráfica como un archivo: ```{r} gg -# Guarda el archivo con la grafica en tu computadora +# 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 esteticas menores a este archivo para crear una figura de alta calidad para una publicacion. Por ejemplo, puedes cambiar el tamano final del imagen (`width`, `height`), tanto como la resolution (`dpi`). Puedes tambien cambiar el tamano de texto en cada eje y los titulos de cada eje, o la posicion de la leyenda mientras determinas el tamano final del imagen en el archivo. +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 del 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 del imagen en el archivo. -Abajo esta todo el codigo que escribiste para la grafica final, en una forma mas condensada y reorganizada: +Abajo esta todo el código que escribiste para la gráfica final, en una forma mas condensada y reorganizada: ```{r eval = FALSE} ggplot(data = scored_clusters_gg3) + - # Anade una linea vertical para cada evento que no fue un evento de posar y que fue asignado al primer individuo + # Añade una linea 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"), @@ -496,7 +494,7 @@ ggplot(data = scored_clusters_gg3) + linewidth = 0.5 ) + - # Anade una linea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos de los dos individuos + # Añade una linea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos de los dos individuos geom_segment( data = scored_clusters_gg3 %>% dplyr::filter(individual_initiated == "unassigned"), @@ -505,7 +503,7 @@ ggplot(data = scored_clusters_gg3) + linewidth = 0.5 ) + - # Anade los eventos de posar como lineas con orillas redondeadas, y puedes anadir colores que indican la identidad del individuo a traves de la columna individual_initiated + # Añade los eventos de posar como lineas 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)), @@ -513,40 +511,40 @@ ggplot(data = scored_clusters_gg3) + linewidth = 2, lineend = "round" ) + - # Anade los tipos de linea customizadas a la grafica + # Añade los tipos de linea customizadas a la gráfica scale_linetype_manual(values = ltys) + - # Anade los colores customizados para estos eventos de posar. Los colores tambien aplican a las lineas de movimientos por individuo que no fueron eventos de posar que anadiste en capas anteriores de geom_segment() + # Añade los colores customizados para estos eventos de posar. Los colores también aplican a las lineas 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 titulos de cada leyenda + # Modifica los títulos de cada leyenda guides( linetype = guide_legend(title = "Direction"), color = guide_legend(title = "Individual") ) + - # Anade un titulo para el eje x + # Añade un titulo para el eje x xlab("Time of day (HH:MM)") + # Elimina el titulo del eje y ylab("") + - # Cambia la estetica de las etiquetas del eje x + # 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 grafica por dia, aqui usaras las etiquetas nuevas de dia + # Crea paneles en la gráfica por día, aquí usaras las etiquetas nuevas de día facet_wrap(~ day_label, nrow = 3, strip.position = "left") + - # Usa esta funcion para convertir el fondo de la grafica a blanco y negro + # Usa esta función para convertir el fondo de la gráfica a blanco y negro theme_bw() + - # Usa estas funciones de estetica para elimninar el text y los rayos del eje y - # Anade un argumento para cambiar la posicion de la leyenda adentro de la grafica + # 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 cuadricula en el eje (mayor y menor) adentro de cada panel - # Aumenta el tamano de texto de cada leyenda y reduce el espacio blanco entre la grafica y las leyendas + # 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(), @@ -559,4 +557,4 @@ ggplot(data = scored_clusters_gg3) + ``` -Acabas de completar el tutorial final del `pipeline` de procesar y analizar datos de ABISSMAL. Tambien practicaste tus habilidades de programar y tus habilidades de la ciencia de datos en un contexto biologico. Muy bien hecho! Nos ayudaria mucho si puedes completar la forma de Google para una evaluacion de estos tutoriales ya que los terminaste. Tus respuestas nos ayudaran mejorar estos tutoriales en el futuro. Un enlace a la forma de Google estara disponible en el archivo README para los tutoriales. \ No newline at end of file +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 terminaste. Tus respuestas nos ayudaran mejorar estos tutoriales en el futuro. Un enlace a la forma de Google estará disponible en el archivo README para los tutoriales. \ No newline at end of file From 50a5066eb5da4431a8588ceec8fcf1dc17aea8bc Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Wed, 3 Apr 2024 12:41:00 -0400 Subject: [PATCH 53/69] [PCT-472]: Started manual grammar revisions --- R/vignettes/Tutorial_01_Introduccion.Rmd | 32 ++++---- R/vignettes/Tutorial_02_Configuracion.Rmd | 82 +++++++++---------- R/vignettes/Tutorial_03_SimularDatos.Rmd | 8 +- R/vignettes/Tutorial_04_GuardarDatos.Rmd | 20 ++--- ...utorial_05_ProcesarDatos_CrearGraficas.Rmd | 44 +++++----- R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd | 58 ++++++------- 6 files changed, 122 insertions(+), 122 deletions(-) diff --git a/R/vignettes/Tutorial_01_Introduccion.Rmd b/R/vignettes/Tutorial_01_Introduccion.Rmd index 1d1a95b..651b5ff 100644 --- a/R/vignettes/Tutorial_01_Introduccion.Rmd +++ b/R/vignettes/Tutorial_01_Introduccion.Rmd @@ -12,31 +12,31 @@ output:

Información sobre esta traducción

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

Resumen del tutorial y objetivos de aprendizaje

-Sería una gran ayuda si antes de empezar este tutorial puedes completar una encuesta a través de Google Forms. La información que compartes nos ayudará saber más de tu experiencia en R y analizando datos biológicos antes de empezar los tutoriales mismos, tanto como las habilidades que buscas mejorar a través de completar esta colección de tutoriales, y también nos ayudara mejorar estos tutoriales en el futuro. Puedes encontrar el enlace para la encuesta en el archivo README de los tutoriales. +Sería una gran ayuda si antes de empezar este tutorial puedes completar una encuesta a través de Google Forms. La información que compartes nos ayudará saber más de tu experiencia en R y analizando datos biológicos antes de empezar los tutoriales mismos, tanto como las habilidades que buscas mejorar a través de completar esta colección de tutoriales. También nos ayudara mejorar los tutoriales en el futuro. Puedes encontrar el enlace para la encuesta en el archivo README de los tutoriales. -En este primer tutorial, vas a leer sobre el programa RStudio, aprender como crear una versión local de el repositorio de ABISSMAL en GitHub, y aprender más sobre el `pipeline` de analisis de datos en ABISSMAL. En este tutorial vas a aprender como: +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 analisis de datos en ABISSMAL. En este tutorial vas a aprender cómo: 1. Configurar tu sesión de RStudio 2. Crear una versión local de un repositorio de GitHub 3. Usar ABISSMAL para procesar y analizar datos -4. Solucionar problemas con tu código usando recursos en linea +4. Solucionar problemas con tu código usando recursos en línea 5. Reportar problemas de código a GitHub -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 como manejar ciertas tares o solucionar problemas especificas con código, pero estos ejemplos no son un resumen exhaustivo de como manejar cada tarea ni solucionar cada problema. Más bien recomiendo que uses estos tutoriales como una oportunidad para practicar como usar tus habilidades de escribir código en un contexto biológico. +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 especificas con código, pero estos ejemplos no son un resumen exhaustivo de cómo manejar cada tarea ni cómo solucionar cada problema. Más bien recomiendo que usas estos tutoriales como una oportunidad para practicar cómo usar tus habilidades de escribir código en un contexto biológico.

Configurar tu sesión de RStudio

Si nunca has usado R, un lenguaje de programación para análisis estadísticos, ni RStudio, una interfaz gráfica de usuario, recomiendo revisar el tutorial [R for Reproducible Scientific Analysis](https://swcarpentry.github.io/r-novice-gapminder-es/) de [Software Carpentry](https://software-carpentry.org/). -Cuando hayas instalado R y RStudio en tu computadora, puedes hacer clic en el icono 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: +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:
-![Un imagen de la configuracion predeterminada de los paneles de RStudio, con el panel de la consola debajo del panel de la fuente](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/RStudio_01.png) -Con esta configuración de paneles 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 este al lado izquierdo del panel de la consola, y así sera más fácil de ver los resultados de tu código inmediatamente. Puedes seguir los siguientes pasos para reconfigurar los paneles en RStudio: +![Una imagen de la configuración predeterminada de los paneles de RStudio, con el panel de la consola debajo del panel de la fuente](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/RStudio_01.png) +Con esta configuración de paneles 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ú @@ -51,20 +51,20 @@ Con esta configuración de paneles es difícil de ver el código que escribes y Tu configuración de paneles en RStudio ahora se debería de ver así:
-![Un imagen de la configuracion modificada de los paneles de RStudio, con el panel de la consola al lado derecho y el panel de la fuente al lado izquierdo](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/RStudio_02.png) +![Una imagen de la configuración modificada de los paneles de RStudio, con el panel de la consola al lado derecho y el panel de la fuente al lado izquierdo](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/RStudio_02.png) -Otro cambio que puedes implementar en tu configuración de RStudio es el retorno automático, para no tener que hacer scroll a tu derecha cada vez que quieres leer una linea 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". +Otro cambio que puedes implementar en tu configuración de RStudio es el retorno automático, para no tener que hacer scroll a tu derecha cada vez que quieres leer una línea completa de texto o código. Para implementar este cambio, puedes ir a "Tools", luego "Global Options", seleccionar "Code", y seleccionar la caja para la opción "Soft-wrap R source files", luego hacer clic en "Apply" y "Ok". También puedes cambiar el tamaño de la fuente del texto y código que escribes, tanto como el color de texto y código, y el color de fondo de tu ventana de RStudio. Después de seleccionar "Tools" y "Global Options", puedes seleccionar "Appearance" para ver diferentes opciones.

Crear una versión local de un repositorio de GitHub

-Si nunca has usado GitHub, recomiendo bajar el programa [GitHub Desktop](https://docs.github.com/es/desktop). Este programa es una interfaz gráfica de usuario para 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 esta en GitHub para que puedas acceder las funciones de R de ABISSMAL en tu propia computadora. Si ya sabes como usar GitHub y como usar Git en el terminal, puedes seguir las instrucciones que tenemos en el [README de ABISSMAL](https://github.com/lastralab/ABISSMAL). +Si nunca has usado GitHub, recomiendo bajar el programa [GitHub Desktop](https://docs.github.com/es/desktop). Este programa es una interfaz gráfica de usuario para 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 esta en GitHub para que puedas acceder las funciones de R de ABISSMAL en tu propia computadora. Si ya sabes cómo usar GitHub y cómo usar Git en el terminal, puedes seguir las instrucciones que tenemos en el [README de ABISSMAL](https://github.com/lastralab/ABISSMAL). Cuando hayas instalado GitHub Desktop, puedes: -* Hacer clic en el icono de GitHub Desktop para abrir el programa +* 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 @@ -85,7 +85,7 @@ Cuando hayas instalado GitHub Desktop, puedes: Cuando el repositorio se ha instalado en tu computadora, deberías de poder ver el siguiente directorio y lista de archivos adentro de una carpeta llamada "ABISSMAL":
-![Un imagen del directorio de la version local del repositorio de ABISSMAL](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/ABISSMAL_localrepo.png) +![Una imagen del directorio de la version local del repositorio de ABISSMAL](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/ABISSMAL_localrepo.png) Los archivos que vamos a usar en los siguientes tutoriales están adentro de la carpeta "R". Esta carpeta contiene 6 archivos de R (extensión ".R"), un archivo README que contiene más información sobre cada archivo de R, y carpetas con los tutoriales en formato RMarkdown (extensión ".Rmd"), y también código para pruebas unitarias automatizadas. @@ -107,11 +107,11 @@ ABISSMAL provee 5 funciones diferentes de R para procesar y analizar datos, y de ![Figure 4 del manuscrito de ABISSMAL con un `pipeline` con las 5 funciones primarias](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/Figure4_ComputationalAnalyses.png) * Esta figura es de Smith-Vidaurre, G., Molina, T., Jarvis, E.D., and E.A. Hobson. 2023. Automated tracking of avian parental care. [EcoEvoRxiv preprint]((https://ecoevorxiv.org/repository/view/6268/)). -

Solucionar errores en linea

+

Solucionar errores en línea

Mientras escribes y ejecutas código encontraras errores que a veces pueden ser frustrantes. Experimentar y solucionar errores 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 tipeo 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 usaras en los siguientes tutoriales, o por errores con el código en los tutoriales mismos. -Cuando experimentas un error con tu condigo, 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 linea 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 donde 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 tipeo 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 función que puede ser una dependencia de ABISSMAL. +Cuando experimentas un error con tu condigo, 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 donde 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 tipeo 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 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 tipeo 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). @@ -119,4 +119,4 @@ Ya cuando hayas investigado cuidadosamente un error, y estas segura que el error Es posible que encuentres errores mientras trabajas en cada tutorial, incluyendo errores con código que no ejecuta, y que devuelve resultados incorrectos. Estos errores pueden manifestar de las funciones de ABISSMAL o con el código de algún tutorial. Cuando encuentras un error en una función de ABISSMAL, puedes crear un "Issue" (asunto o problema) en el repositorio de GitHub. Para crear un asunto nuevo, puedes seleccionar "New Issue" en la pagina de [Issues](https://github.com/lastralab/ABISSMAL/issues) en el repositorio de ABISSMAL, y seguir las instrucciones en el esquema del "Issue" nuevo para añadir la información que las desarrolladoras necesitan para trabajar efectivamente en el error. También puedes añadir la etiqueta de "bug" (o error) a tu "Issue". Si encuentras un error con los tutoriales puedes crear un "Issue" usando los mismos pasos, y deberías de clarificar que el error esta 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 como usar las cinco funciones primarias de ABISSMAL. \ No newline at end of file +En el siguiente tutorial, vamos a crear datos simulados de movimiento de animales para aprender cómo usar las cinco funciones primarias de ABISSMAL. \ No newline at end of file diff --git a/R/vignettes/Tutorial_02_Configuracion.Rmd b/R/vignettes/Tutorial_02_Configuracion.Rmd index 53c257a..10ba464 100644 --- a/R/vignettes/Tutorial_02_Configuracion.Rmd +++ b/R/vignettes/Tutorial_02_Configuracion.Rmd @@ -18,65 +18,65 @@ knitr::opts_chunk$set(echo = TRUE, eval = FALSE)

Información sobre esta traducción

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

Resumen del tutorial y objetivos de aprendizaje

-Vamos a configurar tu espacio de trabajo virtual para sesiones de escribir codigo en R y usar las funciones de ABISSMAL en este segundo tutorial. Vas a aprender habilidades basicas de programacion en R y buenas costumbres de la ciencia abierta para escribir codigo, incluyendo como: +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 costumbres de la ciencia abierta para escribir código, incluyendo cómo: 1. Usar archivos de RMarkdown 2. Limpiar tu ambiente global -3. Ejecutar codigo adentro de un "trozo" de codigo de RMarkdown -4. Aprender sobre funciones en R y su documentacion +3. Ejecutar código adentro de un "trozo" de código de RMarkdown +4. Aprender sobre funciones en R y su documentación 5. Instalar y acceder paquetes -6. Comentar tu codigo -7. Atajos de RStudio para escribir codigo +6. Comentar tu código +7. Atajos de RStudio para escribir código 8. Crear y usar una carpeta o un directorio de trabajo

Usar archivos de RMarkdown

-Cada tutorial en esta serie de tutoriales esta disponsible como un archivo de RMarkdown (extension .Rmd) y tambien 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 codigo junto con los resultados del codigo en un reporte en formato HTML. Puedes leer [la documentación de RMarkdown](https://rmarkdown.rstudio.com/lesson-1.html) o este tutorial por [Teresa Boca en RPubs](https://rpubs.com/tereboca/informe_rmakrdown) para aprender mas sobre como usar RMarkdown para escribo codigo y generar reportes. +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](https://rmarkdown.rstudio.com/lesson-1.html) o este tutorial por [Teresa Boca en RPubs](https://rpubs.com/tereboca/informe_rmakrdown) para aprender mas sobre cómo usar RMarkdown para escribo código y generar reportes. -Los archivos de RMarkdown facilitan el proceso de compartir tu codigo y resultados. Si nunca has usado RMarkdown, la mejor forma de completar los tutoriales sera crear un archivo de RMarkdown nuevo para cada tutorial y escribir el codigo por ti mismo. Aprenderas mas si escribes el codigo y los comentarios (adentro y afuera de cada trozo de codigo) en tus propias palabras. Tambien puedes abrir el reporte HTML de cada tutorial en tu navegador para tener una guia mientras escribes el codigo 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 anades una tercera columna a la configuracion de tus paneles de RStudio. Puedes seleccionar las siguientes opciones: +Los archivos de RMarkdown facilitan el proceso de compartir tu código y 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 mismo. Aprenderás mas si escribes el código y los comentarios (adentro y afuera de cada trozo de código) en tus propias palabras. También puedes abrir el reporte HTML de cada tutorial en tu navegador para tener una guía mientras escribes el código de cada tutorial en tu propio archivo de RMarkdown. Como otra alternativa puedes abrir el archivo de RMarkdown del tutorial al lado de con tu propio archivo de RMarkdown si añades una tercera columna a la configuración de tus paneles de RStudio. Puedes seleccionar las siguientes opciones: * "Tools" * "Global Options" * "Pane Layout" -* "Add Column" para anadir otro panel de fuente en una tercera columna +* "Add Column" para añadir otro panel de fuente en una tercera columna * Luego puedes abrir el tutorial original en un panel de fuente, y tu propio archivo de RMarkdown en el otro panel de fuente -Si ya tienes experiencia con escribir codigo en R y usar archivos de RMarkdown, puedes abrir el archivo original de RMarkdown de un tutorial y ejecutar el codigo 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 codigo original en cada archivo de RMarkdown, puedes crear una copia de cada tutorial y modificar las copias mientras completas cada tutorial. +Si ya tienes experiencia con escribir código en R y usar archivos de RMarkdown, puedes abrir el archivo original de RMarkdown de un tutorial y ejecutar el código adentro de cada trozo para completar cada tutorial (con algunas modificaciones de los directorios para especificar los directorios y archivos en tu computadora). Si quieres preservar el código original en cada archivo de RMarkdown, puedes crear una copia de cada tutorial y modificar las copias mientras completas cada tutorial.

Limpiar tu ambiente global

-Tu ambiente global es tu espacio virtual en R, y puede contener paquetes y objectos diferentes que facilitaran tus objetivos de programacion y/o analizar datos. Puedes ver los paquetes y objetos que existen en tu ambiente con hacer clic en la pestana de ambiente o "Environment" en el mismo panel que incluye las pestanas de "History" y "Connections". +Tu ambiente global es tu espacio virtual en R, y puede contener paquetes y objectos diferentes que facilitaran 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 sesion nueva de RStudio puede que tu ambiente global este vacio. Pero si estas trabajando en una sesion 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 costumbre para asegurar que cada vez que empiezas una sesion de escribir codigo. Si no limpias tu ambiente global, incluso cuando usas el mismo codigo entre sesiones, corres el riesgo de usar versiones viejas de objetos que no reflejan los cambios mas recientes en tu codigo. +Si iniciaste una sesion nueva de RStudio puede que tu ambiente global esté vacío. Pero si estas trabajando en una sesion 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 costumbre para asegurar que cada vez que empiezas una sesion de escribir código. Si no limpias tu ambiente global, incluso cuando usas el mismo código entre sesiones, corres el riesgo de usar versiones viejas de objetos que no reflejan los cambios mas recientes en tu código. -Puedes limpiar tu ambiente global directamente de la interfaz de RStudio con hacer clic en el icono de la escoba debajo de la pestana de "Environment" (haz clic en "Yes" con "hidden objects" para tambien limpiar objectos escondidos). +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 objectos escondidos). -Tambien puedes limpiar tu ambiente global con ejecutar el codigo como se detalla abajo. Puedes ejecutar el codigo adentro de este trozo de formas diferentes: +También puedes limpiar tu ambiente global con ejecutar el código como se detalla abajo. Puedes ejecutar el código adentro de este trozo de formas diferentes: -* Hacer clic en el icono de la flecha verde en la parte superior derecha del trozo de codigo para ejecutar solamente el codigo en este trozo +* Hacer 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 posicion adentro de la linea de codigo, y luego navegar al icono de "Run" en la parte superior derecha del panel de fuente que tiene un cuadro blanco y una flecha verde. En el menu desplegable, selecciona "Run Selected Lines" or "Run Current Chunk" (o usar los atajos de cada comando, ver los siguientes dos vinetas). "Run Selected Lines" ejecutara la linea de codigo en donde esta tu cursor, y "Run Current Chunk" ejecutara todo el codigo adentro del trozo (independientemente de la posicion de tu cursor) +* 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 usar los atajos de cada comando, ver los dos trozos que siguen). "Run Selected Lines" ejecutara la línea de código en donde esta tu cursor, y "Run Current Chunk" ejecutara todo el código adentro del trozo (independientemente de la posición de tu cursor) -* Puedes ubicar tu cursor (y hacer clic) en cualquiera posicion adentro de la linea de codigo, y usar el atajo de "Ctrl" + "Enter" para ejecutar la linea de codigo corriente donde esta 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 esta tu cursor -* Para ejecutar todo el codigo en el trozo, puedes usar el atajo "Ctrl" + "Shift" + "Enter" +* Para ejecutar todo el código en el trozo, puedes usar el atajo "Ctrl" + "Shift" + "Enter" -El primer atajo arriba, para ejecutar una linea de codigo a la vez, es muy util para poder ver los resultados de cada linea de codigo y revisarlos por errores. +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. ```{r} rm(list = ls()) ``` -El codigo arriba para limpiar tu ambente global es una expresion anidada con 2 funciones: `rm()` and `ls()`. La notacion de `()` se usa para funciones en R. Las funciones son operaciones que puedes aplicar en tu propio codigo con usar el nombre de una funcion especifica. R tiene una coleccion de funciones base que puedes acceder sin necesitar un paquete especifico, incluyendo las dos funciones arriba (`rm()` and `ls()`). +El código arriba para limpiar tu ambiente global es una expresión anidada con 2 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 especifica. R tiene una colección de funciones base que puedes acceder sin necesitar un paquete especifico, incluyendo las dos funciones arriba (`rm()` y `ls()`). -

Acceder documentacion de funciones de R

+

Acceder documentación de funciones de R

-Puedes acceder la documentacion para las funciones que usaste arriba con hacer clic en la pestana de "Help" en el panel que incluye "Files" y "Plots", o con escribir el nombre de la funcion en la barra de busqueda. Tambien puedes acceder la documentacion de funciones con ejecutar este codigo: +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 con escribir el nombre de la función en la barra de búsqueda. También puedes acceder la documentación de funciones con ejecutar este código: ```{r} ?rm @@ -89,15 +89,15 @@ Puedes acceder la documentacion para las funciones que usaste arriba con hacer c ``` -La documentacion de cada funcion contiene secciones especificas que pueden ser utiles para entender el uso y proposito de la function, sobre todo los argumentos de la funcion. Estos argumentos son los valores que la funcion require de la usaria para poder guiar o modificar la operacion. Muchas funciones tienen valores por defecto para algunos de sus argumentos que se usaran cuando no provees valores especificos a los argumentos. +La documentación de cada función contiene secciones especificas que pueden ser útiles para entender el uso y 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 usaran cuando no provees valores específicos a los argumentos. -Por ejemplo, la funcion `rm()` tiene un argumento `list()` (seguido por un `=` o signo igual, que se usa para proveer un valor especifico a un argumento). Necesitas proveer informacion despues 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 funcion `ls()`, que imprime los nombres de todos los objetos en tu ambiente global. +Por ejemplo, la función `rm()` tiene un argumento `list()` (seguido por un `=` o signo igual, que se usa para proveer un valor especifico a un argumento). Necesitas proveer información después del argumento `list()` para que `rm()` limpie tu ambiente global de la forma que quieres. Para eliminar todo en tu ambiente global, estamos usando los resultados de la función `ls()`, que imprime los nombres de todos los objetos en tu ambiente global.

Instalar y cargar paquetes

-Despues de limpiar tu ambiente global, todavia necesitas configurar tu espacio virtual de trabajo para preparar para tu sesion de escribir codigo para analizar datos. Un paso important es asegurar que puedes acceder funciones que necesitas para analizar datos pero que no son disponibles a traves de la coleccion de funciones bases de R. Por ejemplo, [el `tidyverse`](https://www.tidyverse.org/) es una coleccion de paquetes de R que provee funciones y expresiones utiles para analizar datos. +Después de limpiar tu ambiente global, todavía necesitas configurar tu espacio virtual de trabajo para preparar para tu sesion 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`](https://www.tidyverse.org/) 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 coleccion de paquetes para acceder funciones utiles para analizar datos. El codigo abajo instala el `tidyverse` de [CRAN](https://cran.r-project.org/), el "Comprehensive R Archive Network" en linea que contiene miles de paquetes de R. +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](https://cran.r-project.org/), el "Comprehensive R Archive Network" en línea que contiene miles de paquetes de R. ```{r} # Instalar el tidyverse de CRAN @@ -105,20 +105,20 @@ install.packages("tidyverse") ``` -En el trozo arriba, anadi un comentario arriba del codigo usando el simbolo de `#`, un signo numeral o hashtag. Cualquier texto que escribes despues de un hashtag sera ignorado cuando ejecutas tu codigo. Es buena costumbre comentar tu codigo, sobre todo cuando estes aprendiendo como escribir codigo en R. Para biologas, comentar tu codigo incluso cuando eres experto tambien puede ser muy buena costumbre. Comentar tu codigo es una forma de documentar to trabajo, y sirve para hacer el codigo que publicas con manuscritos o herramientas mas acesible para otros en la comunidad. +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 costumbre 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 experto también puede ser muy buena costumbre. Comentar tu código es una forma de documentar to trabajo, y sirve para hacer el código que publicas con manuscritos o herramientas mas accesible 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 sesion de RStudio es cargar el paquete en tu ambiente global para poder acceder las funciones contenidos adentro de la coleccion de paquetes del `tidyverse`. +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 sesion de RStudio es cargar el paquete en tu ambiente global para poder acceder las funciones contenidos adentro de la colección de paquetes del `tidyverse`. ```{r} library(tidyverse) ``` -

Atajos para escribir codigo

+

Atajos para escribir código

-RStudio tiene varios atajo utiles para escribir codigo. Puedes encontrar estos atajos con ir a "Tools" en el menu de RStudio, luego seleccionar "Keyboard Shortcuts Help", que deberia de abrir una ventana nueva con todos los atajos por defecto en RStudio. Arriba aprendiste sobre unos atajos para ejecutar codigo adentro de un trozo de RStudio. Algunos atajos utiles son "Shift + Ctrl + C", que puedes usar para comentar o silenciar de una a multiple lineas de codigo a la vez (o sea, convertir codigo a comentarios), y "Ctrl + Alt + I", que automaticamente crea un trozo nuevo de RMarkdown. +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 tambien contiene un atajo de autocompletar con el tabulador. Por ejemplo, en el trozo abajo, despues de escribir `libr` y hacer clic en el tabulador, deberias de poder ver una ventana pequena que demuestra todas las funciones, paquetes, u objetos disponibles que empiezan en el patron "libr". Puedes usar las teclas con flechas para seleccionar la opcion que quieres y hacer clic en "Enter" para completar la linea (por ejemplo, para escribir `library()` para cargar un paquete). +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). ```{r eval = FALSE} libr @@ -127,20 +127,20 @@ libr

Resolver frases incompletas en la consola

-Es importante vigilar el panel de la consola mientras escribes y ejecutas codigo en RStudio. El simbolo de `>` (o "mayor que") en la consola significa que la consola termino de ejecutar codigo y esta lista para otra operacion. Cuando ves el simbolo de `+` (o "mas") en la consola, este simbolo indica que la frase de codigo que acabas de ejecutar no esta completa. Frases incompletas de codigo surgen de errores de tipeo, como cuando te falto o abrir o cerrar los parentesis en una function. Abajo hay un ejemplo de una frase incompleta de codigo: +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 termino de ejecutar código y esta lista para otra operación. Cuando ves el símbolo de `+` (o "mas") en la consola, este símbolo indica que la frase de código que acabas de ejecutar no esta completa. Frases incompletas de código surgen de errores de tipeo, como cuando te falto o abrir o cerrar los paréntesis en una función. Abajo hay un ejemplo de una frase incompleta de código: ```{r eval = FALSE} library(tidyverse ``` -Deberias de ver el simbolo de `+` aparecer en la consola cuando ejecutas el codigo arriba, porque te falto un parentesis para cerrar la funcion `library()`. Tienes dos opciones para resolver este problema. Primero, si sabes que simbolo te falta para completar la frase, puedes escribir este simbolo directamente en la consola y terminar de ejecutar el codigo con seleccionar "Enter". La segunda opcion que tienes es hacer clic en la consola y luego seleccionar "Esc", que va a borrar la frase incompleta de la consola y reinciar la consola para que puedas ejecutar mas codigo (despues de corregir tu error en el codigo en el panel de la fuente). Es buena costumbre vigilar la consola para revisar que el codigo que ejectuas produce resultados. Si ejecutas muchas frases de codigo a la vez y no observas los resultados que esperas en la consola, puede que tienes una frase incompleta por alli, y seria mejor limpiar la consola y revisar bien tu codigo antes de ejecutarlo otra vez. +Deberías de ver el símbolo de `+` aparecer en la consola cuando ejecutas el código arriba, porque te falto 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 mas código (después de corregir tu error en el código en el panel de la fuente). Es buena costumbre 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 tienes una frase incompleta por allí, y seria mejor limpiar la consola y revisar bien tu código antes de ejecutarlo otra vez.

Crear tu directorio de trabajo

-El siguiente paso en el proceso de configurar tu espacio virtual de trabajo es decidir donde sera tu directorio de trabajo para tu sesion de escribir codigo. Un directorio es una ubicacion fisica en tu computadora (o una carpeta) donde R va a buscar archivos para leer o cargar datos. Cuando escribes datas de R como archivos fisicos, estos archivos fisicos se crearan en tu directorio de trabajo. +El siguiente paso en el proceso de configurar tu espacio virtual de trabajo es decidir donde estará tu directorio de trabajo para tu sesion 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 crearan en tu directorio de trabajo. -Puedes usar la funcion `getwd()` para revisar tu directorio corriente de trabajo. +Puedes usar la función `getwd()` para revisar tu directorio corriente de trabajo. ```{r eval = TRUE} getwd() @@ -156,11 +156,11 @@ dir.create("/home/gsvidaurre/Desktop/ABISSMAL_tutoriales") ``` -En el codigo arriba, deberias de reemplazar el `path`, o la combinacion de directorios arriba (en este ejemplo, el `path` es "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales") para que represente la ubicacion en tu computadora donde quieres guardar la carpeta nueva que se llamara `ABISSMAL_tutoriales` (u otro nombre que prefieres). Si usas el sistema operativo de Windows, tienes que cambiar la direccion de los simbolos 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`. +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 llamara `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 comun configurar tu directorio de trabajo con la funcion `setwd()` en cursos preliminares de programacion en R. Es buena costumbre evitar usar `setwd()` en codigo que quieres compartir con colaboradores o codigo que quieres compartir con la comunidad en general siguiendo la filosofia de ciencia abierta, por ejemplo cuando publicas un articulo o compartes una nueva herramienta. Usar `setwd()` cuando compartes tu codigo es suponer que todos los que van a usar tu codigo tienen el mismo directorio de trabajo en su computadora. Hay otras formas en que puedes especificar tu directorio de trabajo a traves de tu codigo sin depender de `setwd()`. +Es común configurar tu directorio de trabajo con la función `setwd()` en cursos preliminares de programación en R. Es buena costumbre 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 coleccion 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 tambien tu directorio de trabajo: +Por ejemplo, digamos que quieres crear una copia del primer tutorial y luego guardar esa copia adentro del directorio que creamos arriba. Podemos usar funciones de la colección base de R para copiar y guardar este archivo al directorio correcto de trabajo. Para evitar errores, vas a necesitar actualizar los `paths` abajo para que representen la carpeta en tu computadora con los tutoriales, y también tu directorio de trabajo: ```{r} file.copy( @@ -170,16 +170,16 @@ file.copy( ``` -En el codigo arriba especificamos dos argumentos a la funcion `file.copy()`. El primer argumento especifica la ubicacion 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 ubicacion 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 estan entrecomillas en cada argumento, para indicar que la informacion entrecomillas se deberia de tratar como el tipo de datos `character` en R, que es un termino formal para informacion en formato de texto. +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 información en formato de texto. -El resultado en la consola deberia de ser "[1] TRUE" si el archivo se copio y se guardo correctamente despues de que ejecutas el codigo. Puedes revisar que la funcion ejecuto bien con navegar a la carpeta donde se debio de haber guardado el archivo copiado y ver si esta copia existe. Tambien puedes usar la funcion base de R `list.files()` para revisar desde RStudio si el archivo que copiaste existe en tu directorio de trabajo. El resultados de `list.files()` es una lista de todos los archivos adentro de tu directorio de trabajo y esta lista deberia de contener un solo archivo: "Tutorial_01_Introduccion_copy.Rmd". +El resultado en la consola debería de ser "[1] TRUE" si el archivo se copio y se guardo correctamente después de que ejecutas el código. Puedes revisar que la función ejecuto 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 resultados de `list.files()` es una lista de todos los archivos adentro de tu directorio de trabajo y esta lista debería de contener un solo archivo: "Tutorial_01_Introduccion_copy.Rmd". ```{r} list.files(path = "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales") ``` -El codigo arriba es un ejemplo sobre como puedes especificar tu directorio de trabajo en el codigo que escribes sin tener que depender de `setwd()`. En los siguientes tutoriales vas a ver otros ejemplos sobre como puedes leer archivos de o escribir archivos a tu directorio de trabajo sin la funcion `setwd()`. Antes de empezar el siguiente tutorial, puedes borrar el archivo que copiaste a tu directorio de trabajo: +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: ```{r} file.remove("/home/gsvidaurre/Desktop/ABISSMAL_tutoriales/Tutorial_01_Introduction_copy.Rmd") diff --git a/R/vignettes/Tutorial_03_SimularDatos.Rmd b/R/vignettes/Tutorial_03_SimularDatos.Rmd index 254b339..a6adcee 100644 --- a/R/vignettes/Tutorial_03_SimularDatos.Rmd +++ b/R/vignettes/Tutorial_03_SimularDatos.Rmd @@ -18,7 +18,7 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE)

Información sobre esta traducción

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

Resumen del tutorial y objetivos de aprendizaje

@@ -302,7 +302,7 @@ glimpse(sim_dats)

Crear `dataframes` usando el `tidyverse`

-En el código arriba, usaste código de R base para añadir una columna nueva y actualizar el nombre de esa columna. Te tomo varias lineas de código para completar estas operaciones. Puedes reducir la cantidad de código que necesitas para estos pasos si eliminas las lineas 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`: +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 tomo varias líneas de código para completar estas operaciones. Puedes reducir la cantidad de código que necesitas para estos pasos si eliminas las líneas de código que usaste para revisar las operaciones. Pero otra forma para reducir la cantidad de código que escribes para esta serie de operaciones es usar la notación y colección de funciones del `tidyverse`: ```{r} # Crear el `dataframe` de nuevo con dos columnas @@ -318,7 +318,7 @@ glimpse(sim_dats) ``` -Acabas de añadir una columna para el año con el nombre correcto en menos lineas 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`. +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 para 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 mas adelante que son difíciles de identificar. @@ -339,7 +339,7 @@ sim_dats %>% ``` -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 ejecutara con el objeto actual. En el ejemplo arriba, `.` se refiere al objeto `sim_dats` que se uso como entrada para la operación entera de `piping`. Como el símbolo `.` esta adentro de la función `nrow()`, la función debería de devolver el numero de filas de `sim_dat`. +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 ejecutara 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 `.` esta adentro de la función `nrow()`, la función debería de devolver el numero de filas de `sim_dat`. Puedes usar una operación parecida para añadir dos columnas al `dataframe` que contienen información del mes y día: ```{r} diff --git a/R/vignettes/Tutorial_04_GuardarDatos.Rmd b/R/vignettes/Tutorial_04_GuardarDatos.Rmd index 6cd3b0c..b024b8b 100644 --- a/R/vignettes/Tutorial_04_GuardarDatos.Rmd +++ b/R/vignettes/Tutorial_04_GuardarDatos.Rmd @@ -18,7 +18,7 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE)

Información sobre esta traducción

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

Resumen del tutorial y objetivos de aprendizaje

@@ -222,7 +222,7 @@ Los `dataframes` que creas y manipulas en R se pueden guardar como archivos fís ``` -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 como usar `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()`: ```{r} # Combina el path para tu directorio de trabajo con el nombre del archivo que quieres escribir @@ -288,7 +288,7 @@ En el código abajo, vas a trabajar con una serie de pasos para guardar los dato

Filtrar un `dataframe`

-Vas a practicar como 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()`: +Vas a practicar cómo usar la función `dplyr::filter()` para filtrar filas de un `dataframe` por día y luego guardar un `dataframe` filtrado con `write.csv()`. Para filtrar un `dataframe`, puedes usar una frase condicional adentro de la función `dplyr::filter()`: ```{r} # Provee el dataframe a la función filter() con un "pipe" @@ -362,7 +362,7 @@ 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 mas difícil mantener archivos organizados de código y también es mas 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 como escribir un bucle con la función `lapply()`. +Vas a practicar cómo escribir un bucle con la función `lapply()`. ```{r} ?lapply @@ -390,7 +390,7 @@ lapply(X = 1:length(files), FUN = function(x){ Como puedes ver, el resultado de este bucle es una lista con dos elementos. Cada elemento de la lista esta rodeado de dos pares de corchetes ([[1]] y [[2]]) y contiene un vector con un largo de uno que contiene el valor del variable de iteración (uno y dos, respectivamente). -El 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, veras los contenidos de este objeto cuando imprimes `x`. O sea, escribir un bucle que usa `x` como el variable de iteración no afectara otras lineas de código que usan un objeto que se llama `x` afuera de la función, y viceversa. +El 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, veras los contenidos de este objeto cuando imprimes `x`. O sea, escribir un bucle que usa `x` como el variable de iteración no afectara otras líneas de código que usan un objeto que se llama `x` afuera de la función, y viceversa. El 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. @@ -604,7 +604,7 @@ days <- list( days -# Ahora necesitas crear un list de los dataframes que quieres usar para crear los archivos. Como ya sabes como 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 usaras en las operaciones de filtrar para cada tipo de sensor +# 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 usaras en las operaciones de filtrar para cada tipo de sensor dats <- list( `RFID` = sim_dats_rfid, `IRBB` = sim_dats_irbb @@ -618,11 +618,11 @@ Cuando hayas establecido las estructuras de datos para informar la operación de 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" los 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 lineas 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. +Para lograr este tipo de chequeo, deberías de ejecutar el código para "congelar" los 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 los variables de iteración no existen afuera de un bucle. Esta forma de probar el código adentro del bucle, en que no estas ejecutando los bucles mismos, es equivalente a probar el código afuera del bucle y por ende, los valores de los variables de iteración que inicializes 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 lineas que abren y cierran los bucles mismos están comentados para guiar tu chequeo (o sea para guiar cuales lineas de código deberías de ejecutar): +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): ```{r eval = FALSE} # Congela las variables de iteración para el chequeo @@ -664,7 +664,7 @@ y <- 1 ``` -Ahora deberías de tener una mejor idea sobre como cada bucle opera a través de diferentes estructuras de datos para realizar la tarea que quieres (en este caso, escribir una hola de calculo por tipo de sensor y día). Luego puedes modificar la estructura entera de los bucles para reemplazar las lineas que escribiste para el chequeo con las operaciones finales que quieres realizar: +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 calculo 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: ```{r eval = FALSE} # El bucle exterior: empieza con iterar a través de los sensores @@ -700,4 +700,4 @@ list.files(file.path(path, "Data/IRBB")) ``` -En este tutorial aprendiste mas sobre como filtrar `dataframes` y guardar estos objetos como hojas de calculo, y como usar bucles de una capa y estructuras de bucles anidados. En el siguiente tutorial vas a usar las hojas de calculo que datos simulados de RFID y sensores infrarrojos para empezar a procesar y analizar datos con las funciones de ABISSMAL. \ No newline at end of file +En este tutorial aprendiste mas sobre cómo filtrar `dataframes` y guardar estos objetos como hojas de calculo, y cómo usar bucles de una capa y estructuras de bucles anidados. En el siguiente tutorial vas a usar las hojas de calculo que datos simulados de RFID y sensores infrarrojos para empezar a procesar y analizar datos con las funciones de ABISSMAL. \ No newline at end of file diff --git a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd index 8a6a2af..45d9fc7 100644 --- a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd +++ b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd @@ -18,11 +18,11 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE)

Información sobre esta traducción

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

Resumen del tutorial y objetivos de aprendizaje

-En este quinto tutorial, vas a empezar a usar las detecciones simuladas de movimientos de animales en el `pipeline` de análisis de datos de ABISSMAL, incluyendo combinar los datos originales a través de días y procesar o limpiar los datos originales. También vas a crear gráficas de los datos procesados. Vas a continuar a usar habilidades que aprendiste en los tutoriales anteriores, y vas a aprender nuevas habilidades que incluyen: +En este quinto tutorial, vas a empezar a usar las detecciones simuladas de movimientos de animales en el `pipeline` de análisis de datos de ABISSMAL, incluyendo combinar los datos originales a través de días y procesar o limpiar los datos originales. También vas a crear gráficas de los datos procesados. Vas a continuar a usar habilidades que aprendiste en los tutoriales anteriores, y vas a aprender nuevas habilidades cómo: 1. Acceder funciones customizadas 2. Usar funciones customizadas @@ -62,9 +62,9 @@ source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R")

Accede información sobre las funciones de ABISSMAL

-Después de ejecutar las lineas de código arriba, deberías de ver que una colección entera de funciones están disponibles en tu ambiente global (revisa la pestaña de `Environment`). Las funciones que empiezan con `check_` son funciones de apoyo. Si haces `scroll` para 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. +Después de ejecutar las líneas de código arriba, deberías de ver que una colección entera de funciones están disponibles en tu ambiente global (revisa la pestaña de `Environment`). Las funciones que empiezan con `check_` son funciones de apoyo. Si haces `scroll` para 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 mas información sobre cada una de las tres funciones primarias, puedes hacer clic en el icono 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 lineas 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 para abajo, podrás ver una sección con detalles sobre la función misma, incluyendo la información que devuelve. Esta documentación esta escrita en ingles por el momento. Después de las lineas de documentación veras el código de la función misma. +Para obtener mas 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 para abajo, podrás ver una sección con detalles sobre la función misma, incluyendo la información que devuelve. Esta documentación esta escrita en ingles por el momento. Después de las líneas de documentación veras el código de la función misma.

Combinar los datos originales

@@ -272,7 +272,7 @@ glimpse(rfid_combined) ``` -Vas a construir la gráfica con estos datos usando funciones de `ggplot2`, un paquete que es parte del `tidyverse` pero que también se puede instalar y usar afuera del `tidyverse`. Puedes revisar este [enlace](https://ggplot2.tidyverse.org/) que tiene mas recursos (en ingles) para aprender como 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 a diferentes niveles de experiencia, y también un curso en linea y un seminario en linea. Puedes encontrar otros recursos en español en linea, como esta [guia para `ggplot2`](https://raw.githubusercontent.com/rstudio/cheatsheets/main/translations/spanish/data-visualization_es.pdf). +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](https://ggplot2.tidyverse.org/) que tiene mas recursos (en ingles) para aprender como 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 a diferentes niveles de experiencia, 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`](https://raw.githubusercontent.com/rstudio/cheatsheets/main/translations/spanish/data-visualization_es.pdf). El paquete de `ggplot2` tiene una notación única para construir gráficas, en 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 `+`. @@ -290,14 +290,14 @@ ggplot(data = rfid_combined) ``` -Necesitaras añadir otras funciones estéticas a esta capa fundamental de la gráfica para poder ver tus datos. Las funciones que usaras 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 esta representado por una linea 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 mas importante se contiene en una dimensión (el tiempo en el eje x). Si fueras a resumir el numero de marcas de tiempo grabado cada día, seria mejor hacer una gráfica de lineas. +Necesitaras añadir otras funciones estéticas a esta capa fundamental de la gráfica para poder ver tus datos. Las funciones que usaras 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 esta representado 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 mas importante se contiene en una dimensión (el tiempo en el eje x). Si fueras a resumir el numero de marcas de tiempo grabado cada día, seria 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 linea a una gráfica, y las lineas pueden comunicar información en una o dos dimensiones (por su ancho en el eje x y su altura en el eje y). +Puedes añadir la función `geom_segment()` como la siguiente capa encima de la capa fundamental de la gráfica. `geom_segment()` facilita añadir línea a una gráfica, y las líneas pueden comunicar información en una o dos dimensiones (por su ancho en el eje x y su altura en el eje y). ```{r} ggplot(data = rfid_combined) + - # Añade una linea vertical para cada marca de tiempo + # 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 @@ -305,14 +305,14 @@ ggplot(data = rfid_combined) + ``` -En el código arriba, `geom_segment()` añade una linea 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 lineas 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. +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 lineas se asignaran automáticamente por `ggplot` usando los colores por defecto del paquete, pero puedes cambiar estos colores usando la función `scale_color_manual()`: +Los colores de las líneas se asignaran automáticamente por `ggplot` usando los colores por defecto del paquete, pero puedes cambiar estos colores usando la función `scale_color_manual()`: ```{r} ggplot(data = rfid_combined) + - # Añade una linea vertical para cada marca de tiempo + # 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 @@ -322,7 +322,7 @@ ggplot(data = rfid_combined) + ``` -Las lineas 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 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: ```{r} @@ -347,7 +347,7 @@ Después de convertir la columna de `dataset` al tipo `factor` y reorganizar los ggplot(data = rfid_combined) + - # Añade una linea vertical para cada marca de tiempo + # 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 @@ -357,12 +357,12 @@ ggplot(data = rfid_combined) + ``` -En la gráfica que acabas de hacer, es muy difícil de discriminar entre las lineas para cada conjunto de datos. Puedes usar la función `facet_wrap()` para dividir los conjuntos de datos en paneles diferentes: +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: ```{r} ggplot(data = rfid_combined) + - # Añade una linea vertical para cada marca de tiempo + # 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 @@ -388,7 +388,7 @@ ggplot(data = rfid_combined %>% ungroup() ) + - # Añade una linea vertical para cada marca de tiempo + # 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 @@ -403,12 +403,12 @@ ggplot(data = rfid_combined %>% Ahora deberías de ver que la segunda marca de tiempo en los datos originales se elimino 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()`. Usaras esta capa de `geom_segment()` para añadir lineas que contienen información temporal sobre cuando los eventos de posar empezaron y terminaron. +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()`. Usaras esta capa de `geom_segment()` para añadir líneas que contienen información temporal sobre cuando los eventos de posar empezaron y terminaron. ```{r} ggplot(data = rfid_combined) + - # Añade una linea vertical para cada marca de tiempo + # 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 @@ -429,14 +429,14 @@ ggplot(data = rfid_combined) + ``` -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 linea en ambos ejes de la gráfica, respectivamente. También vas a tener que especificar donde quieres que cada linea termina en cada eje. En el eje x, indicaste que quieres que la linea 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 lineas para los eventos de posar se dibujaran, que en este caso es justamente arriba de las lineas para los otros conjuntos de datos. Las lineas para los eventos de posar se dibujaron como otra capa de información encima de cada panel de la gráfica por defecto. +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 dibujaran, 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 mas 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 mas información. ```{r} gg <- ggplot(data = rfid_combined) + - # Añade una linea vertical para cada marca de tiempo + # 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 @@ -463,7 +463,7 @@ gg ``` -Ahora puedes hacer unos ajustes menores para seguir mejorando a la gráfica, incluyendo cambiar los títulos de los ejes para que sean mas 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 linea no contiene información para interpretación). +Ahora puedes hacer unos ajustes menores para seguir mejorando a la gráfica, incluyendo cambiar los títulos de los ejes para que sean mas 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). ```{r} gg <- gg + @@ -498,6 +498,6 @@ ggsave(file.path(path, "raw_processed_perching.tiff"), width = 8, height = 6, un ``` -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 "lo 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 titulo de cada eje, o la posición de la leyenda mientras determinas el tamaño final del imagen. +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 "lo 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 titulo 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 mas compleja y refinada. \ No newline at end of file diff --git a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd index 3bb1150..3328214 100644 --- a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd +++ b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd @@ -18,11 +18,11 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE)

Información sobre esta traducción

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

Resumen del tutorial y objetivos de aprendizaje

-En este sexto y ultimo 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 como crear visualizaciones mas complejas con `ggplot()`. +En este sexto y ultimo 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 mas complejas con `ggplot()`.

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

@@ -91,7 +91,7 @@ Cuantos eventos de entrada y salida se anotaron por día?

Datos ausentes en R

-Para poder contra la cantidad de cada uno de estos eventos que ABISSMAL anoto por día, necesitas saber como 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 especifico 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. +Para poder contra la cantidad de cada uno de estos eventos que ABISSMAL anoto 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 especifico en R. Puedes determinar si un vector (o una columna en un `dataframe`) contiene datos ausentes si usas la función `is.na()`, que devolverá `TRUE` cuando encuentra un valor de `NA` (un valor ausente) en el vector actual. ```{r} ?is.na() @@ -136,7 +136,7 @@ scored_clusters %>% ``` -Cuatro entradas y salidas se anotaron por día. Como se compara este resultado con el numero de entradas y salidas que esperabas por día? Si regresas al código en los tutoriales anteriores (el tercer y el cuarto tutorial) donde creaste los datos originales simulados, deberías de poder ver que empezaste por simular dos entradas y dos salidas por día en los datos para el sistema de RFID y los sensores de infrarrojo. Luego añadiste dos entradas y dos salidas mas por día cuando simulaste fallas de detección del sistema de RFID (o sea, estos fueron movimientos capturados solamente por los sensores de infrarrojo). `score_clusters` detecto el numero correcto de entradas y salidas por día. +Cuatro entradas y salidas se anotaron por día. Cómo se compara este resultado con el numero de entradas y salidas que esperabas por día? Si regresas al código en los tutoriales anteriores (el tercer y el cuarto tutorial) donde creaste los datos originales simulados, deberías de poder ver que empezaste por simular dos entradas y dos salidas por día en los datos para el sistema de RFID y los sensores de infrarrojo. Luego añadiste dos entradas y dos salidas mas por día cuando simulaste fallas de detección del sistema de RFID (o sea, estos fueron movimientos capturados solamente por los sensores de infrarrojo). `score_clusters` detecto el numero 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. ```{r} @@ -173,11 +173,11 @@ Como puedes ver, cuatro eventos de movimiento que no fueron eventos de posar se

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

-Ahora puedes visualizar los resultados finales. En el código abajo vas a aprender como crear una gráfica de código de barras que es mas compleja de lo que viste en el tutorial anterior, pero que es mas fácil que interpretar que esa gráfica anterior. Para esta gráfica seria ú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 esta disponible), la identidad del individuo (cuando esta disponible), y los eventos de posar. +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 mas compleja de lo que viste en el tutorial anterior, pero que es mas fácil que interpretar que esa gráfica anterior. Para esta gráfica seria ú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 esta disponible), la identidad del individuo (cuando esta disponible), y los eventos de posar. -Puedes empezar con construir la gráfica con añadir lineas verticales para los eventos que no fueron eventos de posar. El color de cada linea indicara la identidad del individuo, incluyendo cuando esta información no estaba disponible. El tipo de linea va a contener información sobre la dirección de movimiento, y también cuando esta información no se pudo anotar. +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 indicara 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 lineas 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, seria 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 mas útil para la gráfica que vas a hacer mas adelante. +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, seria 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 mas útil para la gráfica que vas a hacer mas 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 inicio el movimiento tiene valores de `NA`), el valor que quieres añadir al vector si la condición se cumple (el valor "unassigned" cuando no hay información sobre la etiqueta PIT), y luego el valor que quieres añadir si la condición no se cumple (en este caso es devolver la etiqueta PIT en la columna individual_initiated si el valor actual no es `NA`). ```{r} @@ -217,25 +217,25 @@ scored_clusters_gg %>% 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 mas adelante. -Puedes usar este `dataframe` modificado para crear la gráfica. En el código abajo, vas a añadir lineas con colores asignados por la identidad de individuos, y los tipos de linea van a representar la dirección de movimiento. Primero vas a especificar los detalles estéticos de la gráfica: +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: ```{r} # Los colores están el el mismo orden que los levels (categorías) de la columna individual_initiated, así que el color naranja va a representar la etiqueta PIT "1357aabbcc" levels(scored_clusters_gg$individual_initiated) cols <- c("orange", "darkgreen", "black") -# Los tipos de linea están el 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) +# Los tipos de línea están el 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) ltys <- c("solid", "longdash", "dotted") ``` -Luego puedes añadir lineas a la gráfica por identidad de 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 lineas 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. +Luego puedes añadir líneas a la gráfica por identidad de 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. ```{r} ggplot() + - # Añade una linea vertical para cada evento que no fue un evento de posar y que fue asignado al primer individuo + # 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"), @@ -244,7 +244,7 @@ ggplot() + linewidth = 0.5 ) + - # Añade una linea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos de los dos individuos + # Añade una línea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos de los dos individuos geom_segment( data = scored_clusters_gg %>% dplyr::filter(individual_initiated == "unassigned"), @@ -253,7 +253,7 @@ ggplot() + linewidth = 0.5 ) + - # Añade los tipos de linea customizadas a la gráfica + # Añade los tipos de línea customizadas a la gráfica scale_linetype_manual(values = ltys) + # Elimina el titulo del eje y @@ -272,7 +272,7 @@ ggplot() + ``` -Para hacer esta gráfica organizaste las lineas 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 patrones de variación en los movimientos a través del tiempo. 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 lineas empezarían mas arriba que las lineas de la primera capa de `geom_segment()`. +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 patrones de variación en los movimientos a través del tiempo. 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 mas arriba que las líneas de la primera capa de `geom_segment()`. Puedes hacer mas 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 solo el tiempo (o sea eliminar la información sobre el mes y el día), y también puedes añadir mas etiquetas (por ejemplo, una etiqueta cada media hora). @@ -306,7 +306,7 @@ Ahora puedes actualizar el código para incluir las etiquetas nuevas de las fech # 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 linea vertical para cada evento que no fue un evento de posar y que fue asignado al primer individuo + # 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"), @@ -315,7 +315,7 @@ ggplot(data = scored_clusters_gg2) + linewidth = 0.5 ) + - # Añade una linea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos de los dos individuos + # Añade una línea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos de los dos individuos geom_segment( data = scored_clusters_gg2 %>% dplyr::filter(individual_initiated == "unassigned"), @@ -324,7 +324,7 @@ ggplot(data = scored_clusters_gg2) + linewidth = 0.5 ) + - # Añade los tipos de linea customizadas a la gráfica + # Añade los tipos de línea customizadas a la gráfica scale_linetype_manual(values = ltys) + # Elimina el titulo del eje y @@ -367,7 +367,7 @@ Ahora puedes actualizar el código de crear la gráfica para cambiar la estétic gg <- ggplot(data = scored_clusters_gg3) + - # Añade una linea vertical para cada evento que no fue un evento de posar y que fue asignado al primer individuo + # 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"), @@ -376,7 +376,7 @@ gg <- ggplot(data = scored_clusters_gg3) + linewidth = 0.5 ) + - # Añade una linea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos de los dos individuos + # Añade una línea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos de los dos individuos geom_segment( data = scored_clusters_gg3 %>% dplyr::filter(individual_initiated == "unassigned"), @@ -385,7 +385,7 @@ gg <- ggplot(data = scored_clusters_gg3) + linewidth = 0.5 ) + - # Añade los tipos de linea customizadas a la gráfica + # Añade los tipos de línea customizadas a la gráfica scale_linetype_manual(values = ltys) + # Elimina el titulo del eje y @@ -424,12 +424,12 @@ 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 lineas cortas y anchas para que aparezcan mas como puntos en la gráfica: +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 mas como puntos en la gráfica: ```{r} gg <- gg + - # Añade los eventos de posar como lineas con orillas redondeadas, y puedes añadir colores que indican la identidad del individuo a través de la columna individual_initiated + # 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)), @@ -437,7 +437,7 @@ gg <- gg + linewidth = 2, lineend = "round" ) + - # Añade los colores customizados para estos eventos de posar. Los colores también aplican a las lineas de movimientos por individuo que no fueron eventos de posar que añadiste en capas anteriores de geom_segment() + # 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 @@ -478,14 +478,14 @@ ggsave(file.path(path, "behavioral_inferences.tiff"), width = 8, height = 6, uni ``` -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 del 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 del imagen en el archivo. +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 esta todo el código que escribiste para la gráfica final, en una forma mas condensada y reorganizada: ```{r eval = FALSE} ggplot(data = scored_clusters_gg3) + - # Añade una linea vertical para cada evento que no fue un evento de posar y que fue asignado al primer individuo + # 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"), @@ -494,7 +494,7 @@ ggplot(data = scored_clusters_gg3) + linewidth = 0.5 ) + - # Añade una linea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos de los dos individuos + # Añade una línea vertical para cada evento que no fue un evento de posar y que no fue asignado a unos de los dos individuos geom_segment( data = scored_clusters_gg3 %>% dplyr::filter(individual_initiated == "unassigned"), @@ -503,7 +503,7 @@ ggplot(data = scored_clusters_gg3) + linewidth = 0.5 ) + - # Añade los eventos de posar como lineas con orillas redondeadas, y puedes añadir colores que indican la identidad del individuo a través de la columna individual_initiated + # 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)), @@ -511,10 +511,10 @@ ggplot(data = scored_clusters_gg3) + linewidth = 2, lineend = "round" ) + - # Añade los tipos de linea customizadas a la gráfica + # Añade los tipos de línea customizadas 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 lineas de movimientos por individuo que no fueron eventos de posar que añadiste en capas anteriores de geom_segment() + # 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 From 1f61c4a7a2c6c67713a8d1e78ece447e4b14cde8 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Wed, 3 Apr 2024 13:10:15 -0400 Subject: [PATCH 54/69] [PCT-472]: Manual grammar checks (continue with Tutorial 02) --- R/vignettes/Tutorial_01_Introduccion.Rmd | 35 +++++++++---------- R/vignettes/Tutorial_02_Configuracion.Rmd | 26 +++++++------- R/vignettes/Tutorial_03_SimularDatos.Rmd | 22 ++++++------ R/vignettes/Tutorial_04_GuardarDatos.Rmd | 14 ++++---- ...utorial_05_ProcesarDatos_CrearGraficas.Rmd | 28 +++++++-------- R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd | 26 +++++++------- 6 files changed, 75 insertions(+), 76 deletions(-) diff --git a/R/vignettes/Tutorial_01_Introduccion.Rmd b/R/vignettes/Tutorial_01_Introduccion.Rmd index 651b5ff..8e26b8a 100644 --- a/R/vignettes/Tutorial_01_Introduccion.Rmd +++ b/R/vignettes/Tutorial_01_Introduccion.Rmd @@ -44,9 +44,9 @@ Con esta configuración de paneles es difícil de ver el código que escribes y * 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 ocupara la siguiente parte de la primera fila de paneles a mano derecha +* 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 +* 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í: @@ -55,12 +55,11 @@ 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.

Crear una versión local de un repositorio de GitHub

-Si nunca has usado GitHub, recomiendo bajar el programa [GitHub Desktop](https://docs.github.com/es/desktop). Este programa es una interfaz gráfica de usuario para 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 esta en GitHub para que puedas acceder las funciones de R de ABISSMAL en tu propia computadora. Si ya sabes cómo usar GitHub y cómo usar Git en el terminal, puedes seguir las instrucciones que tenemos en el [README de ABISSMAL](https://github.com/lastralab/ABISSMAL). +Si nunca has usado GitHub, recomiendo bajar el programa [GitHub Desktop](https://docs.github.com/es/desktop). Este programa es una interfaz gráfica de usuario para 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](https://github.com/lastralab/ABISSMAL). Cuando hayas instalado GitHub Desktop, puedes: @@ -68,7 +67,7 @@ Cuando hayas instalado GitHub Desktop, puedes: * 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 +* 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" @@ -76,47 +75,47 @@ Cuando hayas instalado GitHub Desktop, puedes: * Seleccionar la pestaña "URL" -* Pegar la URL de ABISSMAL que copiaste de GitHub en la caja que pide "Repository URL or GitHub username and repository" +* 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 instalara directamente en tu Desktop +* 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 ha instalado en tu computadora, deberías de poder ver el siguiente directorio y lista de archivos adentro de una carpeta llamada "ABISSMAL": +Cuando el repositorio se haya instalado en tu computadora, deberías de poder ver el siguiente directorio y una lista de archivos adentro de una carpeta llamada "ABISSMAL":
-![Una imagen del directorio de la version local del repositorio de ABISSMAL](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/ABISSMAL_localrepo.png) +![Una imagen del directorio de la versión local del repositorio de ABISSMAL](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/ABISSMAL_localrepo.png) Los archivos que vamos a usar en los siguientes tutoriales están adentro de la carpeta "R". Esta carpeta contiene 6 archivos de R (extensión ".R"), un archivo README que contiene más información sobre cada archivo de R, y carpetas con los tutoriales en formato RMarkdown (extensión ".Rmd"), y también código para pruebas unitarias automatizadas.

Procesar y analizar datos con ABISSMAL

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

Solucionar errores en línea

-Mientras escribes y ejecutas código encontraras errores que a veces pueden ser frustrantes. Experimentar y solucionar errores 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 tipeo 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 usaras en los siguientes tutoriales, o por errores con el código en los tutoriales mismos. +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 tipeo 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 condigo, 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 donde 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 tipeo 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 función que puede ser una dependencia de ABISSMAL. +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 tipeo 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 tipeo o problemas con la estructura de tus datos, luego puedes considerar si el error se debe a un problema con las funciones de ABISSMAL o los tutoriales mismos, y puedes reportar el error a las desarolladoras de ABISSMAL en GitHub (ver la siguiente sección).

Reportar errores en GitHub

-Es posible que encuentres errores mientras trabajas en cada tutorial, incluyendo errores con código que no ejecuta, y que devuelve resultados incorrectos. Estos errores pueden manifestar de las funciones de ABISSMAL o con el código de algún tutorial. Cuando encuentras un error en una función de ABISSMAL, puedes crear un "Issue" (asunto o problema) en el repositorio de GitHub. Para crear un asunto nuevo, puedes seleccionar "New Issue" en la pagina de [Issues](https://github.com/lastralab/ABISSMAL/issues) en el repositorio de ABISSMAL, y seguir las instrucciones en el esquema del "Issue" nuevo para añadir la información que las desarrolladoras necesitan para trabajar efectivamente en el error. También puedes añadir la etiqueta de "bug" (o error) a tu "Issue". Si encuentras un error con los tutoriales puedes crear un "Issue" usando los mismos pasos, y deberías de clarificar que el error esta relacionado al código de algún tutorial en particular. +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](https://github.com/lastralab/ABISSMAL/issues) en el repositorio de ABISSMAL, y seguir las instrucciones en el esquema del "Issue" nuevo para añadir la información que las desarrolladoras necesitan para trabajar efectivamente en el error. También puedes añadir la etiqueta de "bug" (o error) a tu "Issue". Si encuentras un error con los tutoriales puedes crear un "Issue" usando los mismos pasos, y deberías de clarificar que el error está relacionado al código de algún tutorial en particular. En el siguiente tutorial, vamos a crear datos simulados de movimiento de animales para aprender cómo usar las cinco funciones primarias de ABISSMAL. \ No newline at end of file diff --git a/R/vignettes/Tutorial_02_Configuracion.Rmd b/R/vignettes/Tutorial_02_Configuracion.Rmd index 10ba464..0b1f4de 100644 --- a/R/vignettes/Tutorial_02_Configuracion.Rmd +++ b/R/vignettes/Tutorial_02_Configuracion.Rmd @@ -35,9 +35,9 @@ Vamos a configurar tu espacio de trabajo virtual para sesiones de escribir códi

Usar archivos de RMarkdown

-Cada tutorial en esta serie de tutoriales esta disponible como un archivo de RMarkdown (extensión .Rmd) y también como un archivo de HTML que puedes abrir y ver en tu navegador por defecto. Cada archivo de HTML fue generado por "tejer" el archivo de RMarkdown, o convertir el texto y el código junto con los resultados del código en un reporte en formato HTML. Puedes leer [la documentación de RMarkdown](https://rmarkdown.rstudio.com/lesson-1.html) o este tutorial por [Teresa Boca en RPubs](https://rpubs.com/tereboca/informe_rmakrdown) para aprender mas sobre cómo usar RMarkdown para escribo código y generar reportes. +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](https://rmarkdown.rstudio.com/lesson-1.html) o este tutorial por [Teresa Boca en RPubs](https://rpubs.com/tereboca/informe_rmakrdown) para aprender más sobre cómo usar RMarkdown para escribo código y generar reportes. -Los archivos de RMarkdown facilitan el proceso de compartir tu código y 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 mismo. Aprenderás mas 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: +Los archivos de RMarkdown facilitan el proceso de compartir tu código y 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 mismo. Aprenderás más si escribes el código y los comentarios (adentro y afuera de cada trozo de código) en tus propias palabras. También puedes abrir el reporte HTML de cada tutorial en tu navegador para tener una guía mientras escribes el código de cada tutorial en tu propio archivo de RMarkdown. Como otra alternativa puedes abrir el archivo de RMarkdown del tutorial al lado de con tu propio archivo de RMarkdown si añades una tercera columna a la configuración de tus paneles de RStudio. Puedes seleccionar las siguientes opciones: * "Tools" * "Global Options" @@ -51,7 +51,7 @@ Si ya tienes experiencia con escribir código en R y usar archivos de RMarkdown, Tu ambiente global es tu espacio virtual en R, y puede contener paquetes y objectos diferentes que facilitaran 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 sesion nueva de RStudio puede que tu ambiente global esté vacío. Pero si estas trabajando en una sesion 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 costumbre para asegurar que cada vez que empiezas una sesion de escribir código. Si no limpias tu ambiente global, incluso cuando usas el mismo código entre sesiones, corres el riesgo de usar versiones viejas de objetos que no reflejan los cambios mas recientes en tu código. +Si iniciaste una sesion nueva de RStudio puede que tu ambiente global esté vacío. Pero si estas trabajando en una sesion 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 costumbre para asegurar que cada vez que empiezas una sesion de escribir código. Si no limpias tu ambiente global, incluso cuando usas 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 objectos escondidos). @@ -59,9 +59,9 @@ También puedes limpiar tu ambiente global con ejecutar el código como se detal * Hacer 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 usar los atajos de cada comando, ver los dos trozos que siguen). "Run Selected Lines" ejecutara la línea de código en donde esta tu cursor, y "Run Current Chunk" ejecutara 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 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 usar los atajos de cada comando, ver los dos trozos que siguen). "Run Selected Lines" ejecutara la línea de código en dónde esta tu cursor, y "Run Current Chunk" ejecutara 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 esta 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 dónde esta tu cursor * Para ejecutar todo el código en el trozo, puedes usar el atajo "Ctrl" + "Shift" + "Enter" @@ -105,7 +105,7 @@ 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 costumbre 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 experto también puede ser muy buena costumbre. Comentar tu código es una forma de documentar to trabajo, y sirve para hacer el código que publicas con manuscritos o herramientas mas accesible para otros en la comunidad. +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 costumbre 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 experto también puede ser muy buena costumbre. Comentar tu código es una forma de documentar to 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 sesion de RStudio es cargar el paquete en tu ambiente global para poder acceder las funciones contenidos adentro de la colección de paquetes del `tidyverse`. ```{r} @@ -127,18 +127,18 @@ libr

Resolver frases incompletas en la consola

-Es importante vigilar el panel de la consola mientras escribes y ejecutas código en RStudio. El símbolo de `>` (o "mayor que") en la consola significa que la consola termino de ejecutar código y esta lista para otra operación. Cuando ves el símbolo de `+` (o "mas") en la consola, este símbolo indica que la frase de código que acabas de ejecutar no esta completa. Frases incompletas de código surgen de errores de tipeo, como cuando te falto o abrir o cerrar los paréntesis en una función. Abajo hay un ejemplo de una frase incompleta de código: +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 termino de ejecutar código y esta 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 esta completa. Frases incompletas de código surgen de errores de tipeo, como cuando te falto o abrir o cerrar los paréntesis en una función. Abajo hay un ejemplo de una frase incompleta de código: ```{r eval = FALSE} library(tidyverse ``` -Deberías de ver el símbolo de `+` aparecer en la consola cuando ejecutas el código arriba, porque te falto 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 mas código (después de corregir tu error en el código en el panel de la fuente). Es buena costumbre 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 tienes una frase incompleta por allí, y seria mejor limpiar la consola y revisar bien tu código antes de ejecutarlo otra vez. +Deberías de ver el símbolo de `+` aparecer en la consola cuando ejecutas el código arriba, porque te falto 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 costumbre 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 tienes una frase incompleta por allí, y seria mejor limpiar la consola y revisar bien tu código antes de ejecutarlo otra vez.

Crear tu directorio de trabajo

-El siguiente paso en el proceso de configurar tu espacio virtual de trabajo es decidir donde estará tu directorio de trabajo para tu sesion 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 crearan en tu directorio de trabajo. +El siguiente paso en el proceso de configurar tu espacio virtual de trabajo es decidir dónde estará tu directorio de trabajo para tu sesion de escribir código. Un directorio es una ubicación física en tu computadora (o una carpeta) dónde R va a buscar archivos para leer o cargar datos. Cuando escribes datas de R como archivos físicos, estos archivos físicos se crearan en tu directorio de trabajo. Puedes usar la función `getwd()` para revisar tu directorio corriente de trabajo. ```{r eval = TRUE} @@ -147,7 +147,7 @@ getwd() ``` -Mi directorio de trabajo por defecto es la carpeta en mi computadora donde guarde este archivo de RMarkdown. Para trabajar en un directorio aparte que contiene solo los datos generados en estos tutoriales, puedes mejor crear un nuevo directorio o carpeta en tu computadora: +Mi directorio de trabajo por defecto es la carpeta en mi computadora dónde guarde este archivo de RMarkdown. Para trabajar en un directorio aparte que contiene solo los datos generados en estos tutoriales, puedes mejor crear un nuevo directorio o carpeta en tu computadora: ```{r} ?dir.create @@ -156,7 +156,7 @@ 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 llamara `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`. +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 dónde quieres guardar la carpeta nueva que se llamara `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 costumbre 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()`. @@ -170,9 +170,9 @@ file.copy( ``` -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 información en formato de texto. +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 dónde 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 información en formato de texto. -El resultado en la consola debería de ser "[1] TRUE" si el archivo se copio y se guardo correctamente después de que ejecutas el código. Puedes revisar que la función ejecuto 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 resultados 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". +El resultado en la consola debería de ser "[1] TRUE" si el archivo se copio y se guardo correctamente después de que ejecutas el código. Puedes revisar que la función ejecuto bien con navegar a la carpeta dónde 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 resultados de `list.files()` es una lista de todos los archivos adentro de tu directorio de trabajo y esta lista debería de contener un solo archivo: "Tutorial_01_Introduccion_copy.Rmd". ```{r} list.files(path = "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales") diff --git a/R/vignettes/Tutorial_03_SimularDatos.Rmd b/R/vignettes/Tutorial_03_SimularDatos.Rmd index a6adcee..2fd3870 100644 --- a/R/vignettes/Tutorial_03_SimularDatos.Rmd +++ b/R/vignettes/Tutorial_03_SimularDatos.Rmd @@ -22,7 +22,7 @@ Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las

Resumen del tutorial y objetivos de aprendizaje

-En este tercer tutorial, vamos a crear datos simulados de movimientos de animales que fueron detectados por diferentes sensores. El proceso de simular estos datos reemplaza el proceso de colección de datos que provee ABISSMAL para grabar datos de animales en vivo. Generar estos datos simulados te va a proveer mas oportunidades para practicar habilidades básicas de escribir código y tener control sobre la creación de estos datos te ayudara entender los pasos diferentes de análisis de datos que siguen. Si quieres ver datos recolectados de pájaros con el software de ABISSMAL, y el código que fue usado para analizar esos datos, puedes revisar el preprint del [manuscrito de metodos](https://ecoevorxiv.org/repository/view/6268/) que tiene enlaces a los datos y el código que son públicamente accesibles. +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 ayudara entender los pasos diferentes de análisis de datos que siguen. Si quieres ver datos recolectados de pájaros con el software de ABISSMAL, y el código que fue usado para analizar esos datos, puedes revisar el preprint del [manuscrito de metodos](https://ecoevorxiv.org/repository/view/6268/) 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 habilidades de programación que aprendiste en el segundo tutorial, y vas a aprender habilidades adicionales que incluyen: @@ -47,7 +47,7 @@ library(tidyverse) # Carga la colección de paquetes en el tidyverse

Crear un objeto de `path`

-El siguiente paso sera 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 mas 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`. +El siguiente paso sera 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 case, la información que quieres guardar adentro de este objeto es tu directorio de trabajo en el formato de un `string`, y esta información necesita estar entrecomillas. ```{r eval = TRUE} @@ -63,13 +63,13 @@ path ``` -También puedes ver el contenido de `path` con hacer clic en la pestaña de "Environment" y revisar la columna al lado derecho del nombre del objeto. Ahora puedes confirmar que `path` es un objeto nuevo que contiene información sobre tu directorio de trabajo y esta disponible en tu ambiente global para mas operaciones. +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 esta disponible en tu ambiente global para más operaciones. Puedes practicar eliminar solamente el objeto `path` de tu ambiente global con escribir y ejecutar el código `rm(list = "path")`. Después de ejecutar este código, deberías de inicializar `path` otra vez usando el código arriba.

Crear marcas de tiempo para detecciones simuladas de movimientos

-Los datos primarios que colecta ABISSMAL son marcas de tiempo que indican el momento en el tiempo cuando un sensor se activo y grabo movimiento. Muchas (pero no todas) de estas detecciones se pueden asignar a uno o mas 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. +Los datos primarios que colecta ABISSMAL son marcas de tiempo que indican el momento en el tiempo cuando un sensor se activo y grabo 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 2 pájaros adultos a través de sensores de ABISSMAL montados en un contenedor de nido. La antena de RFID esta montada en la entrada del contenedor, y un par de sensores de infrarrojo, el par "externo", esta 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", esta 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 infrarrojos. Cuando un pájaro sale del contenedor, el par interno de sensores infrarrojos debería de activar primero, luego la antena de RFID, y luego el par externo de sensores infrarrojos. @@ -112,7 +112,7 @@ rfid_ts <- c(rfid_ts, "08:00:00", "08:00:01", "08:00:02", "08:00:03", "11:30:00" ``` -En el código arriba, modificaste el objeto `rfid_ts` con la función `c()` para añadir diez mas 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()`: +En el código arriba, modificaste el objeto `rfid_ts` con la función `c()` para añadir diez más marcas de tiempo a este vector para tener un total de 14 elementos. Revisa la estructura del objeto modificado de `rfid_ts` usando la función `glimpse()`: ```{r} # Aquí puedes ver la estructura del objeto en un formato que incluye el tipo de dato en R ("chr", que es tipo `character` o `string`), el numero de elementos ([1:14]), y los valores de los primeros elementos del vector @@ -138,7 +138,7 @@ glimpse(o_irbb_ts) ``` -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 replica 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 mas adelante, como la fecha y los códigos de las etiquetas PIT. +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 replica experimental, la fecha, y para los datos de RFID, el código alfanumérico único de cada etiqueta PIT que fue detectada por la antena de RFID. Algunos de estos metadatos son críticos para los análisis más adelante, como la fecha y los códigos de las etiquetas PIT. Los vectores son estructuras útiles en R, pero una limitación de los vectores es que no puedes combinar diferentes tipos de datos en un solo objeto. Puedes intentar combinar diferentes tipos de datos en un vector: ```{r} @@ -160,7 +160,7 @@ Cuando intentas combinar los tipos de dato `character`, `numeric`, y `binary` en 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 replica experimental, la fecha, y la identidad de la etiqueta PIT para cada detección. -Para crear un vector de metadatos sobre la replica experimental, vas a usar la función `rep()` para repetir la información sobre la replica experimental automáticamente, en vez de copiar y pegar las misma información varias veces. Para configurar el numero de veces que la información sobre la replica 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` sera útil para combinar estos vectores en un solo objeto mas adelante. +Para crear un vector de metadatos sobre la replica experimental, vas a usar la función `rep()` para repetir la información sobre la replica experimental automáticamente, en vez de copiar y pegar las misma información varias veces. Para configurar el numero de veces que la información sobre la replica 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` sera útil para combinar estos vectores en un solo objeto más adelante. ```{r} # La documentación nos dice que rep() espera dos argumentos, `x` y `time` @@ -320,9 +320,9 @@ glimpse(sim_dats) Acabas de añadir una columna para el año con el nombre correcto en menos líneas de código. En la notación del `tidyverse`, el símbolo de `%>%` significa una operación de `pipe`, en que usas el objeto antes del símbolo de `%>%` como entrada para la 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 para 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 mas adelante que son difíciles de identificar. +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 para 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 practica 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: +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: ```{r} # Crear el data frame otra vez con solo dos columnas @@ -335,7 +335,7 @@ sim_dats %>% # La expresión nrow(.) significa "obtener el numero 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 mas reciente de sim_dats con la columna nueva de "year" + glimpse() # Ver la estructura de la versión más reciente de sim_dats con la columna nueva de "year" ``` @@ -360,6 +360,6 @@ sim_dats %>% ``` -En el código arriba, añadiste dos columnas numéricas mas 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 data frame para cada columna. Especificar un solo valor para una nueva columna puede ayudar reducir la cantidad de código que escribes, pero solo cuando de verdad quieres que el mismo valor se repite por todas las filas del `dataframe`. +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 data frame para cada columna. Especificar un solo valor para una nueva columna puede ayudar reducir la cantidad de código que escribes, pero solo 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 calculo como archivos físicos en tu computadora. \ No newline at end of file diff --git a/R/vignettes/Tutorial_04_GuardarDatos.Rmd b/R/vignettes/Tutorial_04_GuardarDatos.Rmd index b024b8b..c7f0e2c 100644 --- a/R/vignettes/Tutorial_04_GuardarDatos.Rmd +++ b/R/vignettes/Tutorial_04_GuardarDatos.Rmd @@ -114,9 +114,9 @@ glimpse(sim_dats_rfid) ``` -Ahora puedes usar este `dataframe` para simular el proceso de colección de datos a través de dos días mas. Para crear mas 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`. +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, estas usando una operación `pipe` para usar `sim_dats_rfid` como entrada en `bind_rows()`, y con esta notación estas especificando que `sim_dats_rfid` es el objeto original al cual quieres adjuntar mas 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` para representar un día adicional de colección de datos. Luego repites este proceso para añadir un tercer días de recolectar datos: +En el código abajo, estas usando una operación `pipe` para usar `sim_dats_rfid` como entrada en `bind_rows()`, y con esta notación estas 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` para representar un día adicional de colección de datos. Luego repites este proceso para añadir un tercer días de recolectar datos: ```{r} sim_dats_rfid <- sim_dats_rfid %>% @@ -222,14 +222,14 @@ Los `dataframes` que creas y manipulas en R se pueden guardar como archivos fís ``` -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()`: +Para escribir un archivo físico a tu directorio de trabajo, necesitas comunicarle a R 1) dónde guardar el archivo y 2) el nombre del archivo que quieres crear. Puedes pasarle ambas piezas de información a `write.csv()` con combinar tu directorio de trabajo y el nombre del archivo usando la función `file.path()`. Para este ejemplo, vas a crear un archivo de prueba mientras practicas cómo usar `write.csv()`: ```{r} # Combina el path 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 donde vas a guardar el archivo, y luego el nombre del archivo +# Este objeto contiene la ubicación dónde vas a guardar el archivo, y luego el nombre del archivo rfid_file ``` @@ -360,7 +360,7 @@ file.remove(rfid_file)

Practicar escribir un bucle

-Podrías repetir el código arriba seis veces (tres veces por sensor) para escribir un `dataframe` por cada día de colección de datos por sensor. Pero es mejor evitar repetir el mismo código varias veces, porque cuando escribes código de esta forma es mas difícil mantener archivos organizados de código y también es mas 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. +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()`. ```{r} @@ -506,7 +506,7 @@ file.remove(files) ``` -Si quieres mas practica escribiendo bucles, puedes escribir un bucle para guardar una hoja de calculo para cada día de colección de datos para los sensores infrarrojo. +Si quieres más práctica escribiendo bucles, puedes escribir un bucle para guardar una hoja de calculo para cada día de colección de datos para los sensores infrarrojo.

Escribe un bucle anidado para crear hojas de calculo

@@ -700,4 +700,4 @@ list.files(file.path(path, "Data/IRBB")) ``` -En este tutorial aprendiste mas sobre cómo filtrar `dataframes` y guardar estos objetos como hojas de calculo, y cómo usar bucles de una capa y estructuras de bucles anidados. En el siguiente tutorial vas a usar las hojas de calculo que datos simulados de RFID y sensores infrarrojos para empezar a procesar y analizar datos con las funciones de ABISSMAL. \ No newline at end of file +En este tutorial aprendiste más sobre cómo filtrar `dataframes` y guardar estos objetos como hojas de calculo, y cómo usar bucles de una capa y estructuras de bucles anidados. En el siguiente tutorial vas a usar las hojas de calculo que datos simulados de RFID y sensores infrarrojos para empezar a procesar y analizar datos con las funciones de ABISSMAL. \ No newline at end of file diff --git a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd index 45d9fc7..4d661d3 100644 --- a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd +++ b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd @@ -64,7 +64,7 @@ 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 están disponibles en tu ambiente global (revisa la pestaña de `Environment`). Las funciones que empiezan con `check_` son funciones de apoyo. Si haces `scroll` para 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 mas 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 para abajo, podrás ver una sección con detalles sobre la función misma, incluyendo la información que devuelve. Esta documentación esta escrita en ingles por el momento. Después de las líneas de documentación veras el código de la función misma. +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 para abajo, podrás ver una sección con detalles sobre la función misma, incluyendo la información que devuelve. Esta documentación esta escrita en ingles por el momento. Después de las líneas de documentación veras el código de la función misma.

Combinar los datos originales

@@ -78,9 +78,9 @@ Vas a proveerle información a la función `combine_raw_data()` a través de los * `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 calculo de los datos originales combinados. La función creara esta carpeta si no existe en tu computadora +* `out_dir` es la carpeta dónde quieres guardar la hoja de calculo de los datos originales combinados. La función creara 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 mas información (`?DateTimeClasses`) +* `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 numero 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, y la hora, el minuto, y el segundo (en decimales), todos con dos dígitos, estarán separados por dos puntos ```{r} @@ -126,7 +126,7 @@ list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$")

Detectar eventos de posar

-Puedes usar los datos originales combinados de sensores diferentes en las siguientes funciones de ABISSMAL para empezar a hacer inferencias de comportamiento de los datos de detección de movimiento. Por ejemplo, puedes detectar eventos de posar en los datos originales de RFID con la función `detect_perching_events()`. Puedes leer mas sobre cada argumento en el archivo de R que contiene esta función. +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. ```{r} detect_perching_events(file_nm = "combined_raw_data_RFID.csv", threshold = 2, run_length = 2, sensor_id_col_nm = "sensor_id", timestamps_col_nm = "timestamp_ms", PIT_tag_col_nm = "PIT_tag", rfid_label = "RFID", general_metadata_cols = c("chamber_id", "sensor_id"), path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", out_file_prefix = "perching_events", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") @@ -144,7 +144,7 @@ glimpse(perching) ``` -`detect_perching_events()` identifico un total de seis eventos de posar, que es el mismo numero 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 mas información sobre estos eventos de posar: +`detect_perching_events()` identifico un total de seis eventos de posar, que es el mismo numero 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: ```{r} # Las marcas de tiempo cuando cada evento de posar empezó @@ -224,7 +224,7 @@ rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFI 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 ordenaran de menos a mas recientes + # La expresión "-desc()" adentro de la función arrange() indica que las marcas de tiempo se ordenaran 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 esta en el formato "dttm", significando que la conversión a formato POSIX sea realizo bien @@ -272,7 +272,7 @@ glimpse(rfid_combined) ``` -Vas a construir la gráfica con estos datos usando funciones de `ggplot2`, un paquete que es parte del `tidyverse` pero que también se puede instalar y usar afuera del `tidyverse`. Puedes revisar este [enlace](https://ggplot2.tidyverse.org/) que tiene mas recursos (en ingles) para aprender como 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 a diferentes niveles de experiencia, 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`](https://raw.githubusercontent.com/rstudio/cheatsheets/main/translations/spanish/data-visualization_es.pdf). +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](https://ggplot2.tidyverse.org/) que tiene más recursos (en ingles) para aprender como 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 a diferentes niveles de experiencia, 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`](https://raw.githubusercontent.com/rstudio/cheatsheets/main/translations/spanish/data-visualization_es.pdf). El paquete de `ggplot2` tiene una notación única para construir gráficas, en 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 `+`. @@ -290,7 +290,7 @@ ggplot(data = rfid_combined) ``` -Necesitaras añadir otras funciones estéticas a esta capa fundamental de la gráfica para poder ver tus datos. Las funciones que usaras 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 esta representado 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 mas importante se contiene en una dimensión (el tiempo en el eje x). Si fueras a resumir el numero de marcas de tiempo grabado cada día, seria mejor hacer una gráfica de líneas. +Necesitaras añadir otras funciones estéticas a esta capa fundamental de la gráfica para poder ver tus datos. Las funciones que usaras 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 esta representado 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 numero de marcas de tiempo grabado cada día, seria mejor hacer una gráfica de líneas. Puedes añadir la función `geom_segment()` como la siguiente capa encima de la capa fundamental de la gráfica. `geom_segment()` facilita añadir línea a una gráfica, y las líneas pueden comunicar información en una o dos dimensiones (por su ancho en el eje x y su altura en el eje y). ```{r} @@ -375,7 +375,7 @@ ggplot(data = rfid_combined) + ``` -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 mas fácil comparar patrones temporales. +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 comparar patrones temporales. Desde este punto de vista es difícil ver como los dos conjuntos de datos (originales y procesados) son diferentes. Puedes filtrar el `dataframe` con funciones del `tidyverse` para visualizar solo las primeras dos detecciones para cada conjunto de datos: ```{r} @@ -383,7 +383,7 @@ Desde este punto de vista es difícil ver como los dos conjuntos de datos (origi ggplot(data = rfid_combined %>% # Crea grupos por las categorías o levels en la columna dataset group_by(dataset) %>% - # Selecciona las primas dos filas para cada grupo + # Selecciona las primeras dos filas para cada grupo slice(1:2) %>% ungroup() ) + @@ -429,9 +429,9 @@ ggplot(data = rfid_combined) + ``` -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 dibujaran, 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. +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 dónde va a empezar cada línea en ambos ejes de la gráfica, respectivamente. También vas a tener que especificar dónde 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 dónde las líneas para los eventos de posar se dibujaran, 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 mas 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 mas información. +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. ```{r} gg <- ggplot(data = rfid_combined) + @@ -463,7 +463,7 @@ gg ``` -Ahora puedes hacer unos ajustes menores para seguir mejorando a la gráfica, incluyendo cambiar los títulos de los ejes para que sean mas 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). +Ahora puedes hacer unos ajustes menores para seguir mejorando a 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). ```{r} gg <- gg + @@ -500,4 +500,4 @@ ggsave(file.path(path, "raw_processed_perching.tiff"), width = 8, height = 6, un 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 "lo 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 titulo 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 mas compleja y refinada. \ No newline at end of file +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. \ No newline at end of file diff --git a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd index 3328214..55c0c0c 100644 --- a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd +++ b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd @@ -22,7 +22,7 @@ Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las

Resumen del tutorial y objetivos de aprendizaje

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

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

@@ -136,7 +136,7 @@ scored_clusters %>% ``` -Cuatro entradas y salidas se anotaron por día. Cómo se compara este resultado con el numero de entradas y salidas que esperabas por día? Si regresas al código en los tutoriales anteriores (el tercer y el cuarto tutorial) donde creaste los datos originales simulados, deberías de poder ver que empezaste por simular dos entradas y dos salidas por día en los datos para el sistema de RFID y los sensores de infrarrojo. Luego añadiste dos entradas y dos salidas mas por día cuando simulaste fallas de detección del sistema de RFID (o sea, estos fueron movimientos capturados solamente por los sensores de infrarrojo). `score_clusters` detecto el numero correcto de entradas y salidas por día. +Cuatro entradas y salidas se anotaron por día. Cómo se compara este resultado con el numero de entradas y salidas que esperabas por día? Si regresas al código en los tutoriales anteriores (el tercer y el cuarto tutorial) dónde creaste los datos originales simulados, deberías de poder ver que empezaste por simular dos entradas y dos salidas por día en los datos para el sistema de RFID y los sensores de infrarrojo. Luego añadiste dos entradas y dos salidas más por día cuando simulaste fallas de detección del sistema de RFID (o sea, estos fueron movimientos capturados solamente por los sensores de infrarrojo). `score_clusters` detecto el numero 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. ```{r} @@ -169,15 +169,15 @@ scored_clusters %>% ``` -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. Mas 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 solo los sensores de infrarrojo que no pueden grabar información sobre la identidad de individuos. +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 solo los sensores de infrarrojo que no pueden grabar información sobre la identidad de individuos.

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

-Ahora puedes visualizar los resultados finales. En el código abajo vas a aprender cómo crear una gráfica de código de barras que es mas compleja de lo que viste en el tutorial anterior, pero que es mas fácil que interpretar que esa gráfica anterior. Para esta gráfica seria ú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 esta disponible), la identidad del individuo (cuando esta disponible), y los eventos de posar. +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 es más fácil que interpretar que esa gráfica anterior. Para esta gráfica seria ú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 esta disponible), la identidad del individuo (cuando esta 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 indicara 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, seria 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 mas útil para la gráfica que vas a hacer mas adelante. +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, seria 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 más ú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 inicio el movimiento tiene valores de `NA`), el valor que quieres añadir al vector si la condición se cumple (el valor "unassigned" cuando no hay información sobre la etiqueta PIT), y luego el valor que quieres añadir si la condición no se cumple (en este caso es devolver la etiqueta PIT en la columna individual_initiated si el valor actual no es `NA`). ```{r} @@ -215,7 +215,7 @@ scored_clusters_gg %>% ``` -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 mas adelante. +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: ```{r} @@ -272,9 +272,9 @@ ggplot() + ``` -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 patrones de variación en los movimientos a través del tiempo. 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 mas arriba que las líneas de la primera capa de `geom_segment()`. +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 patrones de variación en los movimientos a través del tiempo. 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 mas 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 solo el tiempo (o sea eliminar la información sobre el mes y el día), y también puedes añadir mas etiquetas (por ejemplo, una etiqueta cada media hora). +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 solo 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 solo hay tres días de colección de datos con etiquetas que tienes que cambiar. ```{r} @@ -346,9 +346,9 @@ ggplot(data = scored_clusters_gg2) + ``` -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 sera mas 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. +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 sera 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 realizara 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 mas común es que usara la fecha actual). Este comportamiento es esperado y no es un error, mas 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). +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 realizara 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 usara 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). ```{r} scored_clusters_gg3 <- scored_clusters_gg2 %>% @@ -424,7 +424,7 @@ 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 mas como puntos en la gráfica: +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: ```{r} gg <- gg + @@ -447,7 +447,7 @@ 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 titulo de cada leyenda, aumentar el tamaño de texto de cada leyenda, y reducir el espacio blanco ente las leyendas y la gráfica. Para modificar los títulos de cada leyenda, vas a usar las funciones `guides()` y `guide_legend()`. Para aumentar el tamaño de texto en la leyenda, vas a usar el argumento `legend.text` adentro de la función `theme()`, y para reducir el espacio blanco entre la gráfica y las leyendas, vas a usar el argumento `legend.margin` adentro de la función `theme()`. ```{r} -# Ve mas información sobre la función que controla los margenes (espacio blanco) alrededor de la leyenda +# Ve más información sobre la función que controla los margenes (espacio blanco) alrededor de la leyenda ?margin gg <- gg + @@ -480,7 +480,7 @@ ggsave(file.path(path, "behavioral_inferences.tiff"), width = 8, height = 6, uni 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 esta todo el código que escribiste para la gráfica final, en una forma mas condensada y reorganizada: +Abajo esta todo el código que escribiste para la gráfica final, en una forma más condensada y reorganizada: ```{r eval = FALSE} ggplot(data = scored_clusters_gg3) + From cb24b26c378fb777f52000e81d9cef1ac197ab7b Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Wed, 3 Apr 2024 13:46:06 -0400 Subject: [PATCH 55/69] [PCT-472]: Finished manual grammar check of Tutorial 02 --- R/vignettes/Tutorial_01_Introduccion.Rmd | 4 +- R/vignettes/Tutorial_02_Configuracion.Rmd | 48 +++++++++++------------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/R/vignettes/Tutorial_01_Introduccion.Rmd b/R/vignettes/Tutorial_01_Introduccion.Rmd index 8e26b8a..888d497 100644 --- a/R/vignettes/Tutorial_01_Introduccion.Rmd +++ b/R/vignettes/Tutorial_01_Introduccion.Rmd @@ -18,7 +18,7 @@ Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las Sería una gran ayuda si antes de empezar este tutorial puedes completar una encuesta a través de Google Forms. La información que compartes nos ayudará saber más de tu experiencia en R y analizando datos biológicos antes de empezar los tutoriales mismos, tanto como las habilidades que buscas mejorar a través de completar esta colección de tutoriales. También nos ayudara mejorar los tutoriales en el futuro. Puedes encontrar el enlace para la encuesta en el archivo README de los tutoriales. -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 analisis de datos en ABISSMAL. En este tutorial vas a aprender cómo: +En este primer tutorial, vas a leer sobre el programa RStudio, aprender cómo crear una versión local de el repositorio de ABISSMAL en GitHub, y aprender más sobre el `pipeline` de análisis de datos en ABISSMAL. En este tutorial vas a aprender cómo: 1. Configurar tu sesión de RStudio 2. Crear una versión local de un repositorio de GitHub @@ -26,7 +26,7 @@ En este primer tutorial, vas a leer sobre el programa RStudio, aprender cómo cr 4. Solucionar problemas con tu código usando recursos en línea 5. Reportar problemas de código a GitHub -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 especificas 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. +Hay muchas formas de hacer una sola tarea o solucionar un problema en R, y deberías de mantener esto en mente mientras completas estos tutoriales. En cada tutorial, vas a aprender diferentes ejemplos sobre cómo manejar ciertas tares o solucionar problemas específicas con código, pero estos ejemplos no son un resumen exhaustivo de cómo manejar cada tarea ni cómo solucionar cada problema. Más bien recomiendo que usas estos tutoriales como una oportunidad para practicar cómo usar tus habilidades de escribir código en un contexto biológico.

Configurar tu sesión de RStudio

diff --git a/R/vignettes/Tutorial_02_Configuracion.Rmd b/R/vignettes/Tutorial_02_Configuracion.Rmd index 0b1f4de..bea1253 100644 --- a/R/vignettes/Tutorial_02_Configuracion.Rmd +++ b/R/vignettes/Tutorial_02_Configuracion.Rmd @@ -26,7 +26,7 @@ Vamos a configurar tu espacio de trabajo virtual para sesiones de escribir códi 1. Usar archivos de RMarkdown 2. Limpiar tu ambiente global -3. Ejecutar código adentro de un "trozo" de código de RMarkdown +3. Ejecutar código adentro de un "trozo" de RMarkdown 4. Aprender sobre funciones en R y su documentación 5. Instalar y acceder paquetes 6. Comentar tu código @@ -37,7 +37,7 @@ Vamos a configurar tu espacio de trabajo virtual para sesiones de escribir códi 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](https://rmarkdown.rstudio.com/lesson-1.html) o este tutorial por [Teresa Boca en RPubs](https://rpubs.com/tereboca/informe_rmakrdown) para aprender más sobre cómo usar RMarkdown para escribo código y generar reportes. -Los archivos de RMarkdown facilitan el proceso de compartir tu código y 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 mismo. 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: +Los archivos de RMarkdown facilitan el proceso de compartir tu código y tus resultados. Si nunca has usado RMarkdown, la mejor forma de completar los tutoriales será crear un archivo de RMarkdown nuevo para cada tutorial y escribir el código por ti misma. Aprenderás más si escribes el código y los comentarios (adentro y afuera de cada trozo de código) en tus propias palabras. También puedes abrir el reporte HTML de cada tutorial en tu navegador para tener una guía mientras escribes el código de cada tutorial en tu propio archivo de RMarkdown. Como otra alternativa, puedes abrir el archivo de RMarkdown del tutorial al lado de con tu propio archivo de RMarkdown si añades una tercera columna a la configuración de tus paneles de RStudio. Puedes seleccionar las siguientes opciones: * "Tools" * "Global Options" @@ -49,19 +49,19 @@ Si ya tienes experiencia con escribir código en R y usar archivos de RMarkdown,

Limpiar tu ambiente global

-Tu ambiente global es tu espacio virtual en R, y puede contener paquetes y objectos diferentes que facilitaran 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". +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 sesion nueva de RStudio puede que tu ambiente global esté vacío. Pero si estas trabajando en una sesion 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 costumbre para asegurar que cada vez que empiezas una sesion de escribir código. Si no limpias tu ambiente global, incluso cuando usas 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. +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 costumbre 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 objectos escondidos). +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 el código como se detalla abajo. Puedes ejecutar el código adentro de este trozo de formas diferentes: +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: -* Hacer 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 +* 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 usar los atajos de cada comando, ver los dos trozos que siguen). "Run Selected Lines" ejecutara la línea de código en dónde esta tu cursor, y "Run Current Chunk" ejecutara 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 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 dónde esta 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" @@ -72,11 +72,11 @@ rm(list = ls()) ``` -El código arriba para limpiar tu ambiente global es una expresión anidada con 2 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 especifica. R tiene una colección de funciones base que puedes acceder sin necesitar un paquete especifico, incluyendo las dos funciones arriba (`rm()` y `ls()`). +El código arriba para limpiar tu ambiente global es una expresión anidada con dos funciones: `rm()` y `ls()`. La notación de `()` se usa para funciones en R. Las funciones son operaciones que puedes aplicar en tu propio código con usar el nombre de una función específica. R tiene una colección de funciones base que puedes acceder sin necesitar un paquete específico, incluyendo las dos funciones arriba (`rm()` y `ls()`).

Acceder documentación de funciones de R

-Puedes acceder la documentación para las funciones que usaste arriba con hacer clic en la pestaña de "Help" en el panel que incluye "Files" y "Plots", o con escribir el nombre de la función en la barra de búsqueda. También puedes acceder la documentación de funciones con ejecutar este código: +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: ```{r} ?rm @@ -89,25 +89,25 @@ Puedes acceder la documentación para las funciones que usaste arriba con hacer ``` -La documentación de cada función contiene secciones especificas que pueden ser útiles para entender el uso y 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 usaran cuando no provees valores específicos a los argumentos. +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 especifico 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. +Por ejemplo, la función `rm()` tiene un argumento `list()` (seguido por un `=` o signo igual, que se usa para proveer un valor específico a un argumento). Necesitas proveer información después del argumento `list()` para que `rm()` limpie tu ambiente global de la forma que quieres. Para eliminar todo en tu ambiente global, estamos usando los resultados de la función `ls()`, que imprime los nombres de todos los objetos en tu ambiente global.

Instalar y cargar paquetes

-Después de limpiar tu ambiente global, todavía necesitas configurar tu espacio virtual de trabajo para preparar para tu sesion 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`](https://www.tidyverse.org/) es una colección de paquetes de R que provee funciones y expresiones útiles para analizar datos. +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`](https://www.tidyverse.org/) 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](https://cran.r-project.org/), el "Comprehensive R Archive Network" en línea que contiene miles de paquetes de R. ```{r} -# Instalar el tidyverse de CRAN +# 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 costumbre 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 experto también puede ser muy buena costumbre. Comentar tu código es una forma de documentar to trabajo, y sirve para hacer el código que publicas con manuscritos o herramientas más accesibles para otros en la comunidad. +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 costumbre 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 costumbre. 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 sesion de RStudio es cargar el paquete en tu ambiente global para poder acceder las funciones contenidos adentro de la colección de paquetes del `tidyverse`. +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`. ```{r} library(tidyverse) @@ -127,18 +127,18 @@ libr

Resolver frases incompletas en la consola

-Es importante vigilar el panel de la consola mientras escribes y ejecutas código en RStudio. El símbolo de `>` (o "mayor que") en la consola significa que la consola termino de ejecutar código y esta 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 esta completa. Frases incompletas de código surgen de errores de tipeo, como cuando te falto o abrir o cerrar los paréntesis en una función. Abajo hay un ejemplo de una frase incompleta de código: +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 tipeo, como cuando te faltó abrir o cerrar los paréntesis en una función. Abajo hay un ejemplo de una frase incompleta de código: ```{r eval = FALSE} library(tidyverse ``` -Deberías de ver el símbolo de `+` aparecer en la consola cuando ejecutas el código arriba, porque te falto 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 costumbre 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 tienes una frase incompleta por allí, y seria mejor limpiar la consola y revisar bien tu código antes de ejecutarlo otra vez. +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 costumbre vigilar la consola para revisar que el código que ejecutas produce resultados. Si ejecutas muchas frases de código a la vez y no observas los resultados que esperas en la consola, puede que tengas una frase incompleta por allí, y sería mejor limpiar la consola y revisar bien tu código antes de ejecutarlo otra vez.

Crear tu directorio de trabajo

-El siguiente paso en el proceso de configurar tu espacio virtual de trabajo es decidir dónde estará tu directorio de trabajo para tu sesion de escribir código. Un directorio es una ubicación física en tu computadora (o una carpeta) dónde R va a buscar archivos para leer o cargar datos. Cuando escribes datas de R como archivos físicos, estos archivos físicos se crearan en tu directorio de trabajo. +El siguiente paso en el proceso de configurar tu espacio virtual de trabajo es decidir donde estará tu directorio de trabajo para tu sesión de escribir código. Un directorio es una ubicación física en tu computadora (o una carpeta) donde R va a buscar archivos para leer o cargar datos. Cuando escribes datas de R como archivos físicos, estos archivos físicos se crearán en tu directorio de trabajo. Puedes usar la función `getwd()` para revisar tu directorio corriente de trabajo. ```{r eval = TRUE} @@ -147,7 +147,7 @@ getwd() ``` -Mi directorio de trabajo por defecto es la carpeta en mi computadora dónde guarde este archivo de RMarkdown. Para trabajar en un directorio aparte que contiene solo los datos generados en estos tutoriales, puedes mejor crear un nuevo directorio o carpeta en tu computadora: +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: ```{r} ?dir.create @@ -156,7 +156,7 @@ 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 dónde quieres guardar la carpeta nueva que se llamara `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`. +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 costumbre 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()`. @@ -170,9 +170,9 @@ file.copy( ``` -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 dónde 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 información en formato de texto. +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 copio y se guardo correctamente después de que ejecutas el código. Puedes revisar que la función ejecuto bien con navegar a la carpeta dónde 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 resultados 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". +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". ```{r} list.files(path = "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales") From e7a19e27eeefef349d875d22f14b0b3935a1236c Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Wed, 3 Apr 2024 14:17:50 -0400 Subject: [PATCH 56/69] [PCT-472] Manual grammar check of Tutorial 03 --- R/vignettes/Tutorial_01_Introduccion.Rmd | 2 +- R/vignettes/Tutorial_03_SimularDatos.Rmd | 92 +++++++++---------- R/vignettes/Tutorial_04_GuardarDatos.Rmd | 48 +++++----- ...utorial_05_ProcesarDatos_CrearGraficas.Rmd | 26 +++--- R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd | 12 +-- 5 files changed, 90 insertions(+), 90 deletions(-) diff --git a/R/vignettes/Tutorial_01_Introduccion.Rmd b/R/vignettes/Tutorial_01_Introduccion.Rmd index 888d497..38a0c41 100644 --- a/R/vignettes/Tutorial_01_Introduccion.Rmd +++ b/R/vignettes/Tutorial_01_Introduccion.Rmd @@ -16,7 +16,7 @@ Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las

Resumen del tutorial y objetivos de aprendizaje

-Sería una gran ayuda si antes de empezar este tutorial puedes completar una encuesta a través de Google Forms. La información que compartes nos ayudará saber más de tu experiencia en R y analizando datos biológicos antes de empezar los tutoriales mismos, tanto como las habilidades que buscas mejorar a través de completar esta colección de tutoriales. También nos ayudara mejorar los tutoriales en el futuro. Puedes encontrar el enlace para la encuesta en el archivo README de los tutoriales. +Sería una gran ayuda si antes de empezar este tutorial puedes completar una encuesta a través de Google Forms. La información que compartes nos ayudará saber más de tu experiencia en R y analizando datos biológicos antes de empezar los tutoriales mismos, tanto como las habilidades que buscas mejorar a través de completar esta colección de tutoriales. También nos ayudará mejorar los tutoriales en el futuro. Puedes encontrar el enlace para la encuesta en el archivo README de los tutoriales. 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: diff --git a/R/vignettes/Tutorial_03_SimularDatos.Rmd b/R/vignettes/Tutorial_03_SimularDatos.Rmd index 2fd3870..bb64ed4 100644 --- a/R/vignettes/Tutorial_03_SimularDatos.Rmd +++ b/R/vignettes/Tutorial_03_SimularDatos.Rmd @@ -22,9 +22,9 @@ Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las

Resumen del tutorial y objetivos de aprendizaje

-En este tercer tutorial, vamos a crear datos simulados de movimientos de animales que fueron detectados por diferentes sensores. El proceso de simular estos datos reemplaza el proceso de colección de datos que provee ABISSMAL para grabar datos de animales en vivo. Generar estos datos simulados te va a proveer más oportunidades para practicar habilidades básicas de escribir código y tener control sobre la creación de estos datos te ayudara entender los pasos diferentes de análisis de datos que siguen. Si quieres ver datos recolectados de pájaros con el software de ABISSMAL, y el código que fue usado para analizar esos datos, puedes revisar el preprint del [manuscrito de metodos](https://ecoevorxiv.org/repository/view/6268/) que tiene enlaces a los datos y el código que son públicamente accesibles. +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](https://ecoevorxiv.org/repository/view/6268/) 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 habilidades de programación que aprendiste en el segundo tutorial, y vas a aprender habilidades adicionales que incluyen: +A través del proceso de simular datos en este tutorial, vas a continuar usando tus habilidades de programación que aprendiste en el segundo tutorial, y vas a aprender sobre: 1. Crear objetos como vectores y `dataframes` 2. Tipos de datos en R @@ -36,7 +36,7 @@ A través del proceso de simular datos en este tutorial, vas a continuar usando 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 esta combinado en un solo trozo. +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. ```{r message = FALSE, warning = FALSE} rm(list = ls()) # Limpia tu ambiente global @@ -47,9 +47,9 @@ library(tidyverse) # Carga la colección de paquetes en el tidyverse

Crear un objeto de `path`

-El siguiente paso sera 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`. +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 case, 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. +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. ```{r eval = TRUE} path <- "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales" @@ -63,22 +63,22 @@ path ``` -También puedes ver el contenido de `path` con hacer clic en la pestaña de "Environment" y revisar la columna al lado derecho del nombre del objeto. Ahora puedes confirmar que `path` es un objeto nuevo que contiene información sobre tu directorio de trabajo y esta disponible en tu ambiente global para más operaciones. +También puedes ver el contenido de `path` con hacer clic en la pestaña de "Environment" y revisar la columna al lado derecho del nombre del objeto. Ahora puedes confirmar que `path` es un objeto nuevo que contiene información sobre tu directorio de trabajo y está disponible en tu ambiente global para más operaciones. Puedes practicar eliminar solamente el objeto `path` de tu ambiente global con escribir y ejecutar el código `rm(list = "path")`. Después de ejecutar este código, deberías de inicializar `path` otra vez usando el código arriba.

Crear marcas de tiempo para detecciones simuladas de movimientos

-Los datos primarios que colecta ABISSMAL son marcas de tiempo que indican el momento en el tiempo cuando un sensor se activo y grabo 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. +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 2 pájaros adultos a través de sensores de ABISSMAL montados en un contenedor de nido. La antena de RFID esta montada en la entrada del contenedor, y un par de sensores de infrarrojo, el par "externo", esta 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", esta 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 infrarrojos. Cuando un pájaro sale del contenedor, el par interno de sensores infrarrojos debería de activar primero, luego la antena de RFID, y luego el par externo de sensores infrarrojos. +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 infrarrojos. Cuando un pájaro sale del contenedor, el par interno de sensores infrarrojos debería de activar primero, luego la antena de RFID, y luego el par externo de sensores infrarrojos. 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 tenia un solo valor o elemento. Usar `c()` facilita combinar múltiple valores en un objeto como un vector que puede tener múltiple elementos. +Vas a combinar estas marcas de tiempo adentro de un solo objeto usando la función `c()`. Esta función concatena valores separados por comas en un objecto de tipo vector o lista. Arriba creaste un objeto que se llama `path` sin usar `c()`, pero este objeto tenía un solo valor o elemento. Usar `c()` facilita combinar múltiples valores en un objeto como un vector que puede tener múltiples elementos. ```{r} -# Crea un vector de cuatro marcas de tiempo de RFID o cuatro elementos in formato HH:MM:SS +# 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") ``` @@ -115,7 +115,7 @@ rfid_ts <- c(rfid_ts, "08:00:00", "08:00:01", "08:00:02", "08:00:03", "11:30:00" En el código arriba, modificaste el objeto `rfid_ts` con la función `c()` para añadir diez más marcas de tiempo a este vector para tener un total de 14 elementos. Revisa la estructura del objeto modificado de `rfid_ts` usando la función `glimpse()`: ```{r} -# Aquí puedes ver la estructura del objeto en un formato que incluye el tipo de dato en R ("chr", que es tipo `character` o `string`), el numero de elementos ([1:14]), y los valores de los primeros elementos del vector +# 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) ``` @@ -124,7 +124,7 @@ Otro tipo de información importante es simular ruido en las detecciones de los ```{r} # Simula unas fallas de detección de la antena de RFID a través de ambos pares de sensores infrarrojos -# Estas fallas en detección del sensor de RFID surgió en cuatro eventos simulados adicionales (dos entradas y dos salidas) +# 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") @@ -138,18 +138,18 @@ glimpse(o_irbb_ts) ``` -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 replica 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. +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: ```{r} # Crea un vector con los tipos `character`, `numeric`, y `binary` (o sea, datos de texto, valores numéricos, y valores binarios) # El resultado se debería de imprimir directamente a la consola porque no estas guardando el resultado en un objeto -# Deberías de ver que todos los elementos se forzará al tipo `character` o `string` entrecomillas +# Deberías de ver que todos los elementos se forzarán al tipo `character` o `string` entrecomillas c("1", 1, TRUE, FALSE) # Ahora crea un vector con datos `numeric` y `binary` -# Deberías de poder ver que todos los elementos se forzará 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) +# 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) ``` @@ -158,15 +158,15 @@ Cuando intentas combinar los tipos de dato `character`, `numeric`, y `binary` en

Crear vectores de metadatos

-Para los análisis que siguen, necesitas poder crear metadatos con diferentes tipos de datos, y luego combinar los datos primarios para cada sensor (las marcas de tiempo por sensor) con los metadatos importantes. Puedes empezar con crear vectores de metadatos para las marcas de tiempo de RFID, incluyendo información sobre la replica experimental, la fecha, y la identidad de la etiqueta PIT para cada detección. +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 replica experimental, vas a usar la función `rep()` para repetir la información sobre la replica experimental automáticamente, en vez de copiar y pegar las misma información varias veces. Para configurar el numero de veces que la información sobre la replica 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` sera útil para combinar estos vectores en un solo objeto más adelante. +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. ```{r} # La documentación nos dice que rep() espera dos argumentos, `x` y `time` ?rep -# Crea un vector con información sobre la replica experimental +# 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)) @@ -180,7 +180,7 @@ glimpse(exp_rep) ``` -Usar `times = length(rfid_ts)` es mejor costumbre 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 dentro 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 que tantas modificaciones le hayas hecho a `rfid_ts` en el código arriba. +Usar `times = length(rfid_ts)` es mejor costumbre 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. ```{r} @@ -192,7 +192,7 @@ length(rfid_ts) == length(exp_rep) 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 (si *no* 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): ```{r} # El resultado de esta frase debería de ser FALSE, porque estos vectores tienen el mismo largo @@ -200,31 +200,31 @@ length(rfid_ts) != length(exp_rep) ``` -Como un ejemplo, puedes modificar `rfid_ts` para que tenga un numero 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 total. +Como un ejemplo, puedes modificar `rfid_ts` para que tenga un número de elementos diferente a `exp_rep`. Abajo puedes ver algunas formas diferentes de filtrar o eliminar cuatro elementos del vector `rfid_ts` para que tenga diez elementos en total. ```{r} -## Crear indices numéricos para filtrar un objeto +## Crea índices numéricos para filtrar un objeto -# Puedes usar el símbolo `:` para crear una secuencia de números de los indices 5 a lo largo de rfid_ts +# Puedes usar el símbolo `:` para crear una secuencia de números de los índices 5 a lo largo de rfid_ts 5:length(rfid_ts) -# También puedes usar la función `seq()` para crear la misma secuencia de indices numéricos que ves arriba +# También puedes usar la función `seq()` para crear la misma secuencia de índices numéricos que ves arriba seq(from = 5, to = length(rfid_ts), by = 1) -# Si quieres filtrar elementos no consecutivos, puedes crear un vector de indices con la función `c()` +# 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) -## Filtrar un vector por indices numéricos +## 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 de indice cinco a lo largo de rfid_ts, y eliminar los primeros cuatro elementos +# 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)] rfid_ts[seq(from = 5, to = length(rfid_ts), by = 1)] rfid_ts[c(1, 3, 5, 6, 8, 10, 11, 12, 13, 14)] -# Puedes usar cualquier de los métodos arriba para crear una secuencia de indices que quieres eliminar, y luego usar el símbolo `-` adentro de los corchetes para eliminar los elementos en esos indices particulares. Por ejemplo: -rfid_ts[-c(1:4)] # los números deberían de estar adentro de la función `c()` para que este tipo de filtrar de forma invertida funcione +# 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 ``` @@ -238,9 +238,9 @@ length(rfid_ts[-c(1:4)]) != length(exp_rep)

Crear `dataframes` con datos primarios y metadatos

-Es importante combinar los metadatos con los datos primarios para análisis futuros, y puedes combinarlos usando un tipo de objeto que se llama un `dataframe`. Los `dataframes` son parecidos a las hojas de calculo 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 calculo o archivos físicos en tu computadora. +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 numero de filas: +Para combinar dos vectores en un solo `dataframe`, los vectores tienen que tener el mismo largo. Cuando intentas combinar dos vectores de largo diferentes, deberías de recibir un mensaje de error en la consola especificando que los dos argumentos no tienen el mismo número de filas: ```{r eval = FALSE} sim_dats <- data.frame(exp_rep, rfid_ts[-c(1:4)]) @@ -259,9 +259,9 @@ glimpse(sim_dats) ``` -Cuando revisas la estructura del objeto `sim_dats`, el `dataframe` nuevo, puedes ver que tiene 14 filas y 2 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`. +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 replica experimental y el vector `rfid_ts` se convierte en la columna `timestamps` con las marcas de tiempo. +Puedes cambiar los nombres de cada columna con añadir un nombre nuevo y el símbolo `=` antes de cada vector. Abajo el vector `exp_rep` se convierte en la columna `replicate` con la réplica experimental y el vector `rfid_ts` se convierte en la columna `timestamps` con las marcas de tiempo. ```{r} sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) @@ -282,19 +282,19 @@ glimpse(sim_dats) La columna del año tiene un nombre extraño cuando añadimos una nueva columna usando `cbind()`, y si revisas el código de arriba, puedes ver que el nombre de esta columna se parece mucho al código que escribiste adentro de `cbind()`. Puedes usar la función `names()` e indexar con corchetes para cambiar el nombre extraño a un nombre mejor, como "year": ```{r} -# Esta función devuelve un vector de los nombres de las columnas del `dataframe` +# Esta función devuelve un vector de los nombres de las columnas del dataframe names(sim_dats) -# Usa indexar con corchetes y la función `ncol()` para encontrar el ultimo nombre entre los nombres de todas las columnas, porque esta ultima columna contiene la información sobre el año -ncol(sim_dats) # Hay 3 columnas en este `dataframe` +# Usa indexar con corchetes y la función ncol() para encontrar el último nombre entre los nombres de todas las columnas, porque esta última columna contiene la información sobre el año +ncol(sim_dats) # Hay 3 columnas en este dataframe -# Esta expresión devuelve el nombre de la ultima columna +# Esta expresión devuelve el nombre de la última columna names(sim_dats)[ncol(sim_dats)] -# Puedes sobrescribir el nombre de la ultima columna con un nombre nuevo +# 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 actualizo de la forma que esperas +# Confirma que el nombre de la columna de año se actualizó de la forma que esperas names(sim_dats) glimpse(sim_dats) @@ -302,10 +302,10 @@ glimpse(sim_dats)

Crear `dataframes` usando el `tidyverse`

-En el código arriba, usaste código de R base para añadir una columna nueva y actualizar el nombre de esa columna. Te tomo 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`: +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`: ```{r} -# Crear el `dataframe` de nuevo con dos columnas +# 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 @@ -320,7 +320,7 @@ glimpse(sim_dats) Acabas de añadir una columna para el año con el nombre correcto en menos líneas de código. En la notación del `tidyverse`, el símbolo de `%>%` significa una operación de `pipe`, en que usas el objeto antes del símbolo de `%>%` como entrada para la 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 para 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. +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: ```{r} @@ -332,14 +332,14 @@ sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) sim_dats %>% glimpse() %>% # Ver la estructura de la primera versión de sim_dats dplyr::mutate( - # La expresión nrow(.) significa "obtener el numero de filas para el objeto actual". El objeto en este caso es sim_dats + # La expresión nrow(.) significa "obtener el número de filas para el objeto actual". El objeto en este caso es sim_dats year = rep(2023, nrow(.)) ) %>% glimpse() # Ver la estructura de la versión más reciente de sim_dats con la columna nueva de "year" ``` -En el código 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 ejecutara 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 `.` esta adentro de la función `nrow()`, la función debería de devolver el numero de filas de `sim_dat`. +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: ```{r} @@ -360,6 +360,6 @@ sim_dats %>% ``` -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 data frame para cada columna. Especificar un solo valor para una nueva columna puede ayudar reducir la cantidad de código que escribes, pero solo cuando de verdad quieres que el mismo valor se repite por todas las filas del `dataframe`. +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 calculo como archivos físicos en tu computadora. \ No newline at end of file +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. \ No newline at end of file diff --git a/R/vignettes/Tutorial_04_GuardarDatos.Rmd b/R/vignettes/Tutorial_04_GuardarDatos.Rmd index c7f0e2c..6245521 100644 --- a/R/vignettes/Tutorial_04_GuardarDatos.Rmd +++ b/R/vignettes/Tutorial_04_GuardarDatos.Rmd @@ -22,7 +22,7 @@ Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las

Resumen del tutorial y objetivos de aprendizaje

-En este cuarto tutorial, vas a guardar hojas de calculo 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: +En este cuarto tutorial, vas a guardar hojas de cálculo de las detecciones simuladas de movimientos de animales a tu computadora. Vas a continuar usando habilidades que aprendiste en los tutoriales anteriores, y vas a aprender nuevas habilidades que incluyen: 1. Indexar y filtrar `dataframes` 2. Revisar la estructura de `dataframes` con R base y el `tidyverse` @@ -133,7 +133,7 @@ sim_dats_rfid <- sim_dats_rfid %>% ) ) -glimpse(sim_dats_rfid) # Tres veces el numero original de filas, se ve bien +glimpse(sim_dats_rfid) # Tres veces el número original de filas, se ve bien ``` @@ -204,7 +204,7 @@ sim_dats_irbb <- sim_dats_irbb %>% ) ) -glimpse(sim_dats_irbb) # Tres veces el numero de filas, se ve bien +glimpse(sim_dats_irbb) # Tres veces el número de filas, se ve bien # Tres días, se ve bien sim_dats_irbb %>% @@ -215,7 +215,7 @@ sim_dats_irbb %>%

Guarda un `dataframe` como un archivo físico

-Los `dataframes` que creas y manipulas en R se pueden guardar como archivos físicos en tu directorio de trabajo. Tienes muchas opciones diferentes para guardar `dataframes`, pero recomiendo que uses formato `.csv` porque este formato es compatible con R, Microsoft Word, y otros programas. Puedes usar la función `write.csv()` para guardar `dataframes` a hojas de calculo `.csv` en tu computadora: +Los `dataframes` que creas y manipulas en R se pueden guardar como archivos físicos en tu directorio de trabajo. Tienes muchas opciones diferentes para guardar `dataframes`, pero recomiendo que uses formato `.csv` porque este formato es compatible con R, Microsoft Word, y otros programas. Puedes usar la función `write.csv()` para guardar `dataframes` a hojas de cálculo `.csv` en tu computadora: ```{r eval = FALSE} ?write.csv @@ -238,13 +238,13 @@ Luego puedes proveer el `dataframe` a `write.csv()` usando un `pipe` y puedes es ```{r eval = FALSE} sim_dats_rfid %>% - # Escribe el dataframe como una hoja de calculo en formato .csv. No incluyes los nombres de las filas (row.names = FALSE) + # 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 calculo 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 calculo 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. +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()` función como esperabas con usar `list.files()` para ver los archivos en tu directorio de trabajo. ```{r eval = FALSE} @@ -262,7 +262,7 @@ list.files(path, pattern = ".csv$") ``` -

Leer una hoja de calculo

+

Leer una hoja de cálculo

Ahora puedes leer uno de estos archivos con R usando la función `read.csv()`. En el código abajo, vas a proveer el resultado de `read.csv()` a la función `glimpse()` para revisar la estructura del `dataframe` creado en R cuando leíste el archivo. El resultado de este código se imprime a la consola porque no esta guardado adentro de un objeto. ```{r eval = FALSE} @@ -284,7 +284,7 @@ file.remove(rfid_file)

Guardar datos simulados para análisis con ABISSMAL

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

Filtrar un `dataframe`

@@ -340,7 +340,7 @@ 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 calculo en formato .csv. No incluyes nombres para las filas + # Escribe el dataframe filtrado como una hoja de cálculo en formato .csv. No incluyes 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) @@ -375,7 +375,7 @@ length(files) ``` -Ahora puedes empezar a escribir la estructura de un bucle. En el código abajo, el argumento `X` es el numero 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. +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 ejecutara en cada iteración del bucle. El argumento `x` adentro de `funcion()` es el variable de iteración, o el variable que va a tomar un valor diferente del vector `X` en cada iteración. ```{r} @@ -410,7 +410,7 @@ También puedes modificar el código adentro de la función customizada para gua 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 calculo con el nombre de archivo de la iteración actual + # 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) @@ -425,7 +425,7 @@ list.files(file.path(path, "Data/RFID")) ``` -Acabas de escribir dos hojas de calculo usando un bucle, pero escribiste el mismo `dataframe` a cada hoja de calculo. Para poder escribir un `dataframe` diferente a cada hoja de calculo, 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 usaras el variable de iteración para filtrar `sim_dats_rfid` y escribir una hoja de calculo por cada día en `days`. +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 usaras el variable de iteración para filtrar `sim_dats_rfid` y escribir una hoja de cálculo por cada día en `days`. ```{r eval = FALSE} days <- c(1, 2, 3) @@ -435,7 +435,7 @@ 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 calculo para ese día + # 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) }) @@ -452,9 +452,9 @@ file.remove(rem_files) ``` -

Usa un bucle para guardar la hoja de calculo de RFID

+

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

-Ahora puedes juntar todo lo que aprendiste arriba y usar el bucle para escribir una hoja de calculo por día para el sensor de RFID. +Ahora puedes juntar todo lo que aprendiste arriba y usar el bucle para escribir una hoja de cálculo por día para el sensor de RFID. ```{r eval = FALSE} # Crea un vector de los nombres de los archivos que quieres guardar @@ -468,7 +468,7 @@ files <- c( files <- file.path(path, "Data/RFID", files) files -# Inicializa un vector de los días para poder escribir una hoja de calculo por día en cada iteración del bucle +# 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 @@ -478,7 +478,7 @@ 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 calculo correcta para el día actual + # Escribe el dataframe filtrado a la hoja de cálculo correcta para el día actual write.csv(file = files[x], row.names = FALSE) })) @@ -506,9 +506,9 @@ file.remove(files) ``` -Si quieres más práctica escribiendo bucles, puedes escribir un bucle para guardar una hoja de calculo para cada día de colección de datos para los sensores infrarrojo. +Si quieres más práctica escribiendo bucles, puedes escribir un bucle para guardar una hoja de cálculo para cada día de colección de datos para los sensores infrarrojo. -

Escribe un bucle anidado para crear hojas de calculo

+

Escribe un bucle anidado para crear hojas de cálculo

Si quieres minimizar la cantidad de código que escribes para guardar los datos por tipo de sensor y por día, puedes guardar archivos de ambos tipos de sensores (RFID y sensores infrarrojos) en el mismo bucle. Para continuar, deberías de crear otro directorio para los datos de los sensores infrarrojos: ```{r eval = FALSE} @@ -571,7 +571,7 @@ files[["RFID"]] ``` -Los `lists` con tipos de objetos muy útiles para operaciones con bucles anidados. Por ejemplo, si quieres escribir una hoja de calculo 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`): +Los `lists` con 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`): ```{r} # Crea un vector de los nombres de los sensores @@ -596,7 +596,7 @@ file_dirs <- list( file_dirs # Crea un list de los días de colección de datos para cada tipo de sensor -# Esto puede ser un solo vector en vez de una lista porque quieres guardar el mismo numero de días por sensor, pero una lista es útil por si quieres cambiar los días mismos o el numero de días que quieres guardar por 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 una lista 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) @@ -664,7 +664,7 @@ y <- 1 ``` -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 calculo 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: +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: ```{r eval = FALSE} # El bucle exterior: empieza con iterar a través de los sensores @@ -674,7 +674,7 @@ invisible(lapply(1:length(sensors), function(x){ # 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 calculo por tipo de sensor y día + # 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 el variable x para acceder el dataframe para el tipo de sensor actual y luego usar el variable y para filtrar este dataframe por el día actual @@ -700,4 +700,4 @@ 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 calculo, y cómo usar bucles de una capa y estructuras de bucles anidados. En el siguiente tutorial vas a usar las hojas de calculo que datos simulados de RFID y sensores infrarrojos para empezar a procesar y analizar datos con las funciones de ABISSMAL. \ No newline at end of file +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 que datos simulados de RFID y sensores infrarrojos para empezar a procesar y analizar datos con las funciones de ABISSMAL. \ No newline at end of file diff --git a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd index 4d661d3..b04cbf4 100644 --- a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd +++ b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd @@ -68,7 +68,7 @@ Para obtener más información sobre cada una de las tres funciones primarias, p

Combinar los datos originales

-Cuando hayas cargado las funciones de ABISSMAL, podrás usar la primera función, `combine_raw_data()`, para combinar los datos colectados a través de días y tipos de sensores en una sola hoja de calculo 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 calculo para este sensor. +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 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: @@ -78,11 +78,11 @@ Vas a proveerle información a la función `combine_raw_data()` a través de los * `data_dir` es la carpeta que contiene datos adentro de tu directorio de trabajo -* `out_dir` es la carpeta dónde quieres guardar la hoja de calculo de los datos originales combinados. La función creara esta carpeta si no existe en tu computadora +* `out_dir` es la carpeta dónde quieres guardar la hoja de cálculo de los datos originales combinados. La función creara 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 numero 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, y la hora, el minuto, y el segundo (en decimales), todos con dos dígitos, estarán separados por dos puntos +* `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, y la hora, el minuto, y el segundo (en decimales), todos con dos dígitos, estarán separados por dos puntos ```{r} # Combina los datos originales para los sensores de RFID e infrarrojo en procesos separados @@ -90,14 +90,14 @@ combine_raw_data(sensors = "RFID", path = path, data_dir = "Data", out_dir = "Da ``` -Puedes revisar que `combine_raw_data()` guardo una hoja de calculo con los datos originales combinados de RFID al directorio nuevo `raw_combined`: +Puedes revisar que `combine_raw_data()` guardo una hoja de cálculo con los datos originales combinados de RFID al directorio nuevo `raw_combined`: ```{r} list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") ``` -Puedes leer el archivo de datos combinados de RFID ("combined_raw_data_RFID.csv") a R para revisar la estructura de esta hoja de calculo: +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: ```{r} rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) @@ -106,18 +106,18 @@ glimpse(rfid_data) ``` -Leer estos datos a R creo 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 calculo, 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 infrarrojos tendrán números de identidad únicos en la columna de `senso_id`). +Leer estos datos a R creo 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 infrarrojos tendrán números de identidad únicos en la columna de `senso_id`). -La función `combine_raw_data()` también creo 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, veras que las hojas de calculo originales por día se preservaron y no fueron ni eliminados ni sobrescritos. +La función `combine_raw_data()` también creo 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, veras 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 guardaran en hojas de calculo separados, y evitas tener que escribir el mismo código varias veces para ejecutar `combine_raw_data()` para múltiples sensores: +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 guardaran en hojas de cálculo separados, y evitas tener que escribir el mismo código varias veces para ejecutar `combine_raw_data()` para múltiples sensores: ```{r} combine_raw_data(sensors = c("RFID", "IRBB"), path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") ``` -Los datos de RFID se sobrescribirán, y deberías de ver una hoja de calculo adicional con los datos originales de los sensores infrarrojos: +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 infrarrojos: ```{r} list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") @@ -133,7 +133,7 @@ detect_perching_events(file_nm = "combined_raw_data_RFID.csv", threshold = 2, ru ``` -`detect_perching_events()` puede operar en solo 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 guardara 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 numero de detecciones). +`detect_perching_events()` puede operar en solo 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 guardara 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()`? ```{r} @@ -144,7 +144,7 @@ glimpse(perching) ``` -`detect_perching_events()` identifico un total de seis eventos de posar, que es el mismo numero 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: +`detect_perching_events()` identifico 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: ```{r} # Las marcas de tiempo cuando cada evento de posar empezó @@ -187,7 +187,7 @@ list.files(file.path(path, "Data/processed")) ``` -Puedes leer este archivo a R para ver su estructura. Deberías de ver menos filas en este `dataframe` comparado con la hoja de calculo de los datos originales: +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: ```{r} rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) @@ -290,7 +290,7 @@ ggplot(data = rfid_combined) ``` -Necesitaras añadir otras funciones estéticas a esta capa fundamental de la gráfica para poder ver tus datos. Las funciones que usaras 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 esta representado 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 numero de marcas de tiempo grabado cada día, seria mejor hacer una gráfica de líneas. +Necesitaras añadir otras funciones estéticas a esta capa fundamental de la gráfica para poder ver tus datos. Las funciones que usaras 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 esta representado 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, seria mejor hacer una gráfica de líneas. Puedes añadir la función `geom_segment()` como la siguiente capa encima de la capa fundamental de la gráfica. `geom_segment()` facilita añadir línea a una gráfica, y las líneas pueden comunicar información en una o dos dimensiones (por su ancho en el eje x y su altura en el eje y). ```{r} diff --git a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd index 55c0c0c..5359916 100644 --- a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd +++ b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd @@ -22,7 +22,7 @@ Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las

Resumen del tutorial y objetivos de aprendizaje

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

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

@@ -117,7 +117,7 @@ Por ejemplo, la función `dplyr::filter()` eliminara una fila cada vez que encue

Cuenta eventos por día

-Ahora puedes escribir código para contar el numero de eventos de entrada y salida que ABISSMAL anoto 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 numero de filas por grupo. +Ahora puedes escribir código para contar el número de eventos de entrada y salida que ABISSMAL anoto por día. Vas a necesitar 1) crear una columna nueva con la información sobre el día para cada marca de tiempo, 2) eliminar filas con datos ausentes para la información anotada de la dirección del movimiento (la columna `direction_scored`, porque esta información no se puede anotar para algunos movimientos), 3) agrupar el `dataframe` por día y por la dirección anotada, y luego 4) contar el número de filas por grupo. ```{r} scored_clusters %>% @@ -127,16 +127,16 @@ scored_clusters %>% ) %>% # 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(), estas invirtiendo la frase condicional y también el resultado, así que todos los valores TRUE se convertirán a FALSE. Por ende, dplyr::filter eliminara 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 numero de entradas y salidas (categorías en la columna direction_scored) por día (categorías en la columna day) + # 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 numero de filas aquí es el numero de entradas o salidas anotadas por día + # Luego puedes resumir los datos: el número de filas aquí es el número de entradas o salidas anotadas por día dplyr::summarise( n = n() ) ``` -Cuatro entradas y salidas se anotaron por día. Cómo se compara este resultado con el numero de entradas y salidas que esperabas por día? Si regresas al código en los tutoriales anteriores (el tercer y el cuarto tutorial) dónde creaste los datos originales simulados, deberías de poder ver que empezaste por simular dos entradas y dos salidas por día en los datos para el sistema de RFID y los sensores de infrarrojo. Luego añadiste dos entradas y dos salidas más por día cuando simulaste fallas de detección del sistema de RFID (o sea, estos fueron movimientos capturados solamente por los sensores de infrarrojo). `score_clusters` detecto el numero correcto de entradas y salidas por día. +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 al código en los tutoriales anteriores (el tercer y el cuarto tutorial) dónde creaste los datos originales simulados, deberías de poder ver que empezaste por simular dos entradas y dos salidas por día en los datos para el sistema de RFID y los sensores de infrarrojo. Luego añadiste dos entradas y dos salidas más por día cuando simulaste fallas de detección del sistema de RFID (o sea, estos fueron movimientos capturados solamente por los sensores de infrarrojo). `score_clusters` detecto 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. ```{r} @@ -287,7 +287,7 @@ scored_clusters_gg2 <- scored_clusters_gg %>% ) %>% 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 ultimo argumento es la columna day porque la columna day_label no se ha creado todavía + 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) ) From 2ea532fed33730d9a41e21db779482c8e8599c98 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Wed, 3 Apr 2024 14:44:36 -0400 Subject: [PATCH 57/69] [PCT-472] Nearly done with manual grammar check for Tutorial 04 --- R/vignettes/Tutorial_01_Introduccion.Rmd | 2 +- R/vignettes/Tutorial_03_SimularDatos.Rmd | 8 +-- R/vignettes/Tutorial_04_GuardarDatos.Rmd | 63 ++++++++++--------- ...utorial_05_ProcesarDatos_CrearGraficas.Rmd | 8 +-- R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd | 2 +- 5 files changed, 43 insertions(+), 40 deletions(-) diff --git a/R/vignettes/Tutorial_01_Introduccion.Rmd b/R/vignettes/Tutorial_01_Introduccion.Rmd index 38a0c41..d15179a 100644 --- a/R/vignettes/Tutorial_01_Introduccion.Rmd +++ b/R/vignettes/Tutorial_01_Introduccion.Rmd @@ -92,7 +92,7 @@ Los archivos que vamos a usar en los siguientes tutoriales están adentro de la ABISSMAL provee 5 funciones diferentes de R para procesar y analizar datos, y describimos estas funciones en más detalle abajo, en el orden general en que se deberían de usar. Puedes encontrar más información detallada sobre estas funciones en el archivo README de la carpeta "R", y también en el manuscrito asociado con ABISSMAL: -1. `combine_raw_data` automáticamente combina hojas de cálculo de los datos originales que fueron colectados por día y los guarda en una sola hoja de cálculo a través de todos los días de colección de datos. Esta concatenación de datos se realiza por cada tipo de sensor, por ejemplo, los sensores de rayos infrarrojos 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 infrarrojos, la cámara, o el sensor de temperatura de ABISSMAL. Las siguientes funciones no aceptan datos de temperatura +1. `combine_raw_data` automáticamente combina hojas de cálculo de los datos originales que fueron colectados por día y los guarda en una sola hoja de cálculo a través de todos los días de colección de datos. Esta concatenación de datos se realiza por cada tipo de sensor, por ejemplo, los sensores de rayos infrarrojo o una cámara de infrarrojo, y no cambia ni reemplaza los datos originales. Esta función puede usar datos de los sensores de RFID ("radio frequency identification"), los sensores infrarrojo, la cámara, o el sensor de temperatura de ABISSMAL. Las siguientes funciones no aceptan datos de temperatura 2. `detect_perching_events` toma como entrada los resultados de `combine_raw_data` y detecta grupos de detecciones que ocurrieron cerca en el tiempo como eventos de posar. Estos eventos de 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 diff --git a/R/vignettes/Tutorial_03_SimularDatos.Rmd b/R/vignettes/Tutorial_03_SimularDatos.Rmd index bb64ed4..f7baf25 100644 --- a/R/vignettes/Tutorial_03_SimularDatos.Rmd +++ b/R/vignettes/Tutorial_03_SimularDatos.Rmd @@ -71,7 +71,7 @@ Puedes practicar eliminar solamente el objeto `path` de tu ambiente global con e 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 infrarrojos. Cuando un pájaro sale del contenedor, el par interno de sensores infrarrojos debería de activar primero, luego la antena de RFID, y luego el par externo de sensores infrarrojos. +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. @@ -96,7 +96,7 @@ length(rfid_ts) # Este vector tiene cuatro elementos ``` -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. +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. ```{r} # Simula marcas de tiempo para el par externo ("o_") e interno ("i_") de sensores infrarrojo para una entrada, una salida, y luego otra entrada y salida @@ -120,10 +120,10 @@ glimpse(rfid_ts) ``` -Otro tipo de información importante es simular ruido en las detecciones de los sensores. Por ejemplo, la antena de RFID puede fallar en detectar la etiqueta PIT ("passive integrated transponder") de un individuo, y los sensores de infrarrojo pueden activarse cuando los pájaros dejan material de nido colgando en la entrada del contenedor. En ambos casos, los sensores infrarrojos deberían de activar pero no la antena de RFID. +Otro tipo de información importante es simular ruido en las detecciones de los sensores. Por ejemplo, la antena de RFID puede fallar en detectar la etiqueta PIT ("passive integrated transponder") de un individuo, y los sensores de infrarrojo pueden activarse cuando los pájaros dejan material de nido colgando en la entrada del contenedor. En ambos casos, los sensores infrarrojo deberían de activar pero no la antena de RFID. ```{r} -# Simula unas fallas de detección de la antena de RFID a través de ambos pares de sensores infrarrojos +# 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") diff --git a/R/vignettes/Tutorial_04_GuardarDatos.Rmd b/R/vignettes/Tutorial_04_GuardarDatos.Rmd index 6245521..718eddd 100644 --- a/R/vignettes/Tutorial_04_GuardarDatos.Rmd +++ b/R/vignettes/Tutorial_04_GuardarDatos.Rmd @@ -44,7 +44,7 @@ path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Inicializa un objeto con

Crear los datos simulados

-En el código abajo, vas a recrear los datos simulados de RFID y sensores infrarrojos del tutorial anterior. Aquí estamos combinando el código en menos trozos comparado con el tercer tutorial: +En el código abajo, vas a recrear los datos simulados de RFID y sensores infrarrojo del tutorial anterior. Aquí estamos combinando el código en menos trozos comparado con el tercer tutorial: ```{r} # Crea un vector de cuatro marcas de tiempo de RFID en formato HH:MM:SS @@ -60,11 +60,11 @@ glimpse(rfid_ts) Aquí "IRBB" significa "infrared beam breakers" o sensores de infrarrojo. ```{r} -# Simula marcas de tiempo para los pares externos ("o_") e internos ("i_") de sensores infrarrojos para una entrada y una salida, y luego otra entrada y salida +# 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 infrarrojos +# 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") @@ -82,15 +82,15 @@ glimpse(i_irbb_ts) 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: ```{r} -# Crea un vector para la replica experimental +# 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 (4 detecciones) al primer individuo, y el segundo evento de posar (6 detecciones) al segundo individuo +# 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 replica experimental y las marcas de tiempo +# 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 @@ -116,7 +116,7 @@ glimpse(sim_dats_rfid) Ahora puedes usar este `dataframe` para simular el proceso de colección de datos a través de dos días más. Para crear 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, estas usando una operación `pipe` para usar `sim_dats_rfid` como entrada en `bind_rows()`, y con esta notación estas 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` para representar un día adicional de colección de datos. Luego repites este proceso para añadir un tercer días de recolectar datos: +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: ```{r} sim_dats_rfid <- sim_dats_rfid %>% @@ -139,13 +139,13 @@ glimpse(sim_dats_rfid) # Tres veces el número original de filas, se ve bien

Revisar columnas de `dataframe` con R base

-Acabas de revisar la estructura del `dataframe` para confirmar que los datos simulados tiene 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`: +Acabas de revisar la estructura del `dataframe` para confirmar que los datos simulados tienen datos recolectados a través de tres días. También puedes revisar los valores únicos que están presentes en la columna de `day`. Abajo puedes ver una forma de revisar los valores únicos adentro de una columna de un `dataframe`, usando dos ejemplos diferentes de notación de R base para acceder columnas adentro de un `dataframe`: ```{r} # Escribir una expresión con el nombre de un objeto dataframe, un símbolo $, y el nombre de una columna te ayuda 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 -# 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 de del par interno de corchetes +# 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"]] # Puedes usar la función unique() para ver los valores únicos adentro de un vector, incluyendo una columna en un dataframe @@ -157,7 +157,7 @@ unique(sim_dats_rfid[["day"]]) # Tres días, se ve bien

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

-También puedes revisar valores únicos en una columna usando funciones del `tidyverse`. En la expresión abajo, vas a usar una expresión de `pipe` para proveer el `dataframe` `sim_dats_rfid` a la función `pull()`, que va a facilitar acceder la columna `day` del dataframe como un vector. Luego este vector de la columna `day` se va a usar como entrada a la función `unique()` para revisar los valores únicos del vector mismo. La función `unique()` no requiere un argumento adentro de los paréntesis porque ya recibió el valor de entrada que necesita a través de la operación de `piping`. +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`. ```{r} # Tres días, se ve bien @@ -222,7 +222,7 @@ Los `dataframes` que creas y manipulas en R se pueden guardar como archivos fís ``` -Para escribir un archivo físico a tu directorio de trabajo, necesitas comunicarle a R 1) dónde 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()`: +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()`: ```{r} # Combina el path para tu directorio de trabajo con el nombre del archivo que quieres escribir @@ -246,7 +246,7 @@ sim_dats_rfid %>% 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()` función como esperabas con usar `list.files()` para ver los archivos en tu directorio de trabajo. +Puedes revisar que `write.csv()` funcionó como esperabas con usar `list.files()` para ver los archivos en tu directorio de trabajo. ```{r eval = FALSE} # Ve una lista de todos los archivos en este path @@ -254,17 +254,17 @@ 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 especifica 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". +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". ```{r eval = FALSE} -# Devuelve solo archivos que terminan en el patrón ".csv" en este path particular +# Devuelve sólo archivos que terminan en el patrón ".csv" en este path particular list.files(path, pattern = ".csv$") ```

Leer una hoja de cálculo

-Ahora puedes leer uno de estos archivos con R usando la función `read.csv()`. En el código abajo, vas a proveer el resultado de `read.csv()` a la función `glimpse()` para revisar la estructura del `dataframe` creado en R cuando leíste el archivo. El resultado de este código se imprime a la consola porque no esta guardado adentro de un objeto. +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. ```{r eval = FALSE} read.csv(file.path(path, "test_file.csv")) %>% @@ -340,13 +340,13 @@ 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 nombres para las filas + # 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 creo adentro de la carpeta nueva de RFID, y luego puedes eliminar este archivo. +Revisa que el archivo de prueba se creó adentro de la carpeta nueva de RFID, y luego puedes eliminar este archivo. ```{r eval = FALSE} list.files(file.path(path, "Data/RFID"), pattern = ".csv$") @@ -376,10 +376,11 @@ length(files) ``` 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 ejecutara en cada iteración del bucle. El argumento `x` adentro de `funcion()` es el variable de iteración, o el variable que va a tomar un valor diferente del vector `X` en cada iteración. + +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 el variable de iteración, o el variable que va a tomar un valor diferente del vector `X` en cada iteración. ```{r} -# En este bucle el 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 va a imprimir en cada iteración en la consola +# En este bucle el 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 @@ -388,9 +389,9 @@ lapply(X = 1:length(files), FUN = function(x){ ``` -Como puedes ver, el resultado de este bucle es una lista con dos elementos. Cada elemento de la lista esta rodeado de dos pares de corchetes ([[1]] y [[2]]) y contiene un vector con un largo de uno que contiene el valor del variable de iteración (uno y dos, respectivamente). +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 del variable de iteración (uno y dos, respectivamente). -El 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, veras los contenidos de este objeto cuando imprimes `x`. O sea, escribir un bucle que usa `x` como el variable de iteración no afectara otras líneas de código que usan un objeto que se llama `x` afuera de la función, y viceversa. +El 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 el 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. El 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. @@ -425,7 +426,7 @@ list.files(file.path(path, "Data/RFID")) ``` -Acabas de escribir dos hojas de cálculo usando un bucle, pero escribiste el mismo `dataframe` a cada hoja de cálculo. Para poder escribir un `dataframe` diferente a cada hoja de cálculo, puedes añadir el paso de filtrar el `dataframe`que aprendiste arriba. En el código abajo, también vas a crear otro objecto de vector que se llama `days` (días) y luego usaras el variable de iteración para filtrar `sim_dats_rfid` y escribir una hoja de cálculo por cada día en `days`. +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`. ```{r eval = FALSE} days <- c(1, 2, 3) @@ -442,7 +443,7 @@ lapply(X = 1:length(files), FUN = function(x){ ``` -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 esta 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. +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. ```{r eval = FALSE} rem_files <- list.files(file.path(path, "Data/RFID"), pattern = "^test", full.names = TRUE) @@ -472,7 +473,7 @@ files days <- c(1, 2, 3) days -# Puedes eliminar los nombres de los argumentos de lapply() porque estas especificando los valores de los argumentos en el orden que la función espera por defecto +# 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 %>% @@ -485,7 +486,7 @@ invisible(lapply(1:length(files), function(x){ ``` -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 al 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()` esta rodeado de dos pares de corchetes, y luego un solo par de corchetes. `lapply()` es una función que devuelve una lista, 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 importantes a la consola. Ya que puedes usar `list.files()` para revisar que `lapply()` se ejecuto bien, usar `invisible()` te ayudara minimizar la cantidad de texto que tienes que revisar en tu consola. +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. ```{r eval = FALSE} # Los archivos nuevos de .csv para cada día de datos de RFID están presentes en el directorio esperado, se ve bien @@ -510,7 +511,7 @@ Si quieres más práctica escribiendo bucles, puedes escribir un bucle para guar

Escribe un bucle anidado para crear hojas de cálculo

-Si quieres minimizar la cantidad de código que escribes para guardar los datos por tipo de sensor y por día, puedes guardar archivos de ambos tipos de sensores (RFID y sensores infrarrojos) en el mismo bucle. Para continuar, deberías de crear otro directorio para los datos de los sensores infrarrojos: +Si quieres minimizar la cantidad de código que escribes para guardar los datos por tipo de sensor y por día, puedes guardar archivos de ambos tipos de sensores (RFID y sensores infrarrojo) en el mismo bucle. Para continuar, deberías de crear otro directorio para los datos de los sensores infrarrojo: ```{r eval = FALSE} dir.create(file.path(path, "Data", "IRBB")) @@ -520,7 +521,7 @@ dir.create(file.path(path, "Data", "IRBB")) Para lograr filtrar y escribir datos para ambos tipos de sensores a través de los días de colección de datos, vas a usar un tipo de objeto en R que se llama un `list`. Vas a usar estos `lists` adentro del bucle anidado: ```{r} -# Crea una lista de los nombres de archivos customizados para guardar datos para cada tipo de sensor y día +# 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") @@ -539,7 +540,7 @@ Puedes indexar un `list` de una forma parecida a indexar vectores y `dataframes` files[1] glimpse(files[1]) -# Usar dos pares de corchetes devuelvo solo el elemento actual, o sea elimina el estructura de list para demostrar el estructura original de ese elemento (aquí este elemento es un vector) +# 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]] glimpse(files[[1]]) @@ -571,7 +572,9 @@ files[["RFID"]] ``` -Los `lists` con 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`): +TKTK continue with manual grammar check + +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`): ```{r} # Crea un vector de los nombres de los sensores @@ -700,4 +703,4 @@ 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 que datos simulados de RFID y sensores infrarrojos para empezar a procesar y analizar datos con las funciones de ABISSMAL. \ No newline at end of file +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 que datos simulados de RFID y sensores creó para empezar a procesar y analizar datos con las funciones de ABISSMAL. \ No newline at end of file diff --git a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd index b04cbf4..573e74b 100644 --- a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd +++ b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd @@ -106,9 +106,9 @@ glimpse(rfid_data) ``` -Leer estos datos a R creo 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 infrarrojos tendrán números de identidad únicos en la columna de `senso_id`). +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 `senso_id`). -La función `combine_raw_data()` también creo 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, veras que las hojas de cálculo originales por día se preservaron y no fueron ni eliminados ni sobrescritos. +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, veras 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 guardaran en hojas de cálculo separados, y evitas tener que escribir el mismo código varias veces para ejecutar `combine_raw_data()` para múltiples sensores: ```{r} @@ -117,7 +117,7 @@ combine_raw_data(sensors = c("RFID", "IRBB"), path = path, data_dir = "Data", ou ``` -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 infrarrojos: +Los datos de RFID se sobrescribirán, y deberías de ver una hoja de cálculo adicional con los datos originales de los sensores infrarrojo: ```{r} list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") @@ -196,7 +196,7 @@ glimpse(rfid_pp) ``` -Luego puedes procesar los datos originales de los sensores infrarrojos y revisar el archivo de `.csv`: +Luego puedes procesar los datos originales de los sensores infrarrojo y revisar el archivo de `.csv`: ```{r} preprocess_detections(sensor = "IRBB", timestamps_col_nm = "timestamp_ms", group_col_nm = "sensor_id", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") diff --git a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd index 5359916..ef315f8 100644 --- a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd +++ b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd @@ -169,7 +169,7 @@ scored_clusters %>% ``` -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 solo los sensores de infrarrojo que no pueden grabar información sobre la identidad de individuos. +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 solo los sensores infrarrojo que no pueden grabar información sobre la identidad de individuos.

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

From 6eb2b818bba190b7fee588a952ca8e6f1673d789 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Wed, 3 Apr 2024 14:50:16 -0400 Subject: [PATCH 58/69] [PCT-472]: Finished manual grammar check of Tutorial 04 --- R/vignettes/Tutorial_04_GuardarDatos.Rmd | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/R/vignettes/Tutorial_04_GuardarDatos.Rmd b/R/vignettes/Tutorial_04_GuardarDatos.Rmd index 718eddd..848c5d1 100644 --- a/R/vignettes/Tutorial_04_GuardarDatos.Rmd +++ b/R/vignettes/Tutorial_04_GuardarDatos.Rmd @@ -572,8 +572,6 @@ files[["RFID"]] ``` -TKTK continue with manual grammar check - 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`): ```{r} @@ -590,7 +588,7 @@ files <- list( files -# Crea un list de los paths para los archivos de cada sensor. Usaras estos paths adentro de los bucles +# 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") @@ -599,7 +597,7 @@ file_dirs <- list( file_dirs # Crea un list de los días de colección de datos para cada tipo de sensor -# Esto puede ser un solo vector en vez de una lista porque quieres guardar el mismo número de días por sensor, pero una lista es útil por si quieres cambiar los días mismos o el número de días que quieres guardar por 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) @@ -607,7 +605,7 @@ days <- list( days -# Ahora necesitas crear un list de los dataframes que quieres usar para crear los archivos. Como ya sabes cómo filtrar dataframes por día, ese código puede ir adentro de los bucles para minimizar la cantidad de código que escribes. Aquí vas a especificar el dataframe que usaras en las operaciones de filtrar para cada tipo de sensor +# 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 @@ -617,13 +615,13 @@ glimpse(dats) ``` -Cuando hayas establecido las estructuras de 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). +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" los 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. +Para lograr este tipo de chequeo, deberías de ejecutar el código para "congelar" los 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 los variables de iteración no existen afuera de un bucle. Esta forma de probar el código adentro del bucle, en que no estas ejecutando los bucles mismos, es equivalente a probar el código afuera del bucle y por ende, los valores de los variables de iteración que inicializes afuera del bucle se van a respetar. +*Nota importante*: Arriba aprendiste que los 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 los 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): ```{r eval = FALSE} @@ -653,7 +651,7 @@ y <- 1 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 esta adentro de la lista. Luego usa el variable y para indexar este vector con un par de corchetes para acceder el nombre de archivo correcto para esta iteración + # 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 el 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] @@ -703,4 +701,4 @@ 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 que datos simulados de RFID y sensores creó para empezar a procesar y analizar datos con las funciones de ABISSMAL. \ No newline at end of file +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. \ No newline at end of file From 4227dd69499c99ccc371b9c41601ed90c54b66b0 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Thu, 4 Apr 2024 09:28:51 -0400 Subject: [PATCH 59/69] [PCT-472]: Finished manual grammer check of Tutorial 04 --- ...utorial_05_ProcesarDatos_CrearGraficas.Rmd | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd index 573e74b..8d919f9 100644 --- a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd +++ b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd @@ -1,5 +1,5 @@ --- -title: "Tutorial 04 05: Procesar Datos y Crear Graficas" +title: "Tutorial 05: Procesar Datos y Crear Gráficas" author: "Grace Smith-Vidaurre" date: "2023-12-27" output: @@ -43,7 +43,7 @@ path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Inicializa un objeto con

Cargar las funciones de ABISSMAL

-Las funciones customizadas de R en ABISSMAL están guardados 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: +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: ```{r} # Carga la función que combina los datos originales @@ -62,13 +62,13 @@ source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R")

Accede información sobre las funciones de ABISSMAL

-Después de ejecutar las líneas de código arriba, deberías de ver que una colección entera de funciones están disponibles en tu ambiente global (revisa la pestaña de `Environment`). Las funciones que empiezan con `check_` son funciones de apoyo. Si haces `scroll` para 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. +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 para abajo, podrás ver una sección con detalles sobre la función misma, incluyendo la información que devuelve. Esta documentación esta escrita en ingles por el momento. Después de las líneas de documentación veras el código de la función misma. +Para obtener más información sobre cada una de las tres funciones primarias, puedes hacer clic en el ícono blanco cuadrado a la mera derecha de cada función en la pestaña de `Environment`, o ejecutar el código `View(nombre_de_la_funcion)`. Este comando debería de abrir el archivo de la función actual en una pestaña nueva adentro de tu panel de fuente. En el archivo de cada función, vas ver líneas de documentación que empiezan con los símbolos "`#@", luego el nombre de la función y una descripción, y luego una descripción de cada argumento (parámetro) para la función. Si haces scroll hacia abajo, podrás ver una sección con detalles sobre la función misma, incluyendo la información que devuelve. Esta documentación está escrita en inglés por el momento. Después de las líneas de documentación verás el código de la función misma.

Combinar los datos originales

-Cuando hayas cargado las funciones de ABISSMAL, podrás usar la primera función, `combine_raw_data()`, para combinar los datos colectados a través de días y 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. +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: @@ -78,11 +78,11 @@ Vas a proveerle información a la función `combine_raw_data()` a través de los * `data_dir` es la carpeta que contiene datos adentro de tu directorio de trabajo -* `out_dir` es la carpeta dónde quieres guardar la hoja de cálculo de los datos originales combinados. La función creara esta carpeta si no existe en tu computadora +* `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, y la hora, el minuto, y el segundo (en decimales), todos con dos dígitos, estarán separados por dos puntos +* `POSIXct_format` es una secuencia de caracteres que contiene la información del formato `POSIXct` para combinar fechas y marcas de tiempo en una sola columna. Por defecto la función devolverá el año como un número con cuatro dígitos y el mes y el día como números con dos dígitos, separados por guiones. La fecha y el tiempo estarán separados por un espacio. La hora, el minuto, y el segundo (en decimales), todos con dos dígitos, estarán separados por dos puntos. ```{r} # Combina los datos originales para los sensores de RFID e infrarrojo en procesos separados @@ -90,7 +90,7 @@ combine_raw_data(sensors = "RFID", path = path, data_dir = "Data", out_dir = "Da ``` -Puedes revisar que `combine_raw_data()` guardo una hoja de cálculo con los datos originales combinados de RFID al directorio nuevo `raw_combined`: +Puedes revisar que `combine_raw_data()` guardó una hoja de cálculo con los datos originales combinados de RFID al directorio nuevo `raw_combined`: ```{r} list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") @@ -106,11 +106,11 @@ glimpse(rfid_data) ``` -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 `senso_id`). +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, veras que las hojas de cálculo originales por día se preservaron y no fueron ni eliminados ni sobrescritos. +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 guardaran en hojas de cálculo separados, y evitas tener que escribir el mismo código varias veces para ejecutar `combine_raw_data()` para múltiples sensores: +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: ```{r} combine_raw_data(sensors = c("RFID", "IRBB"), path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") @@ -133,7 +133,7 @@ detect_perching_events(file_nm = "combined_raw_data_RFID.csv", threshold = 2, ru ``` -`detect_perching_events()` puede operar en solo 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 guardara 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). +`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()`? ```{r} @@ -144,13 +144,13 @@ glimpse(perching) ``` -`detect_perching_events()` identifico 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: +`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: ```{r} # Las marcas de tiempo cuando cada evento de posar empezó perching$perching_start -# Las marcas de tiempo cuando cada evento de posar termino +# Las marcas de tiempo cuando cada evento de posar terminó perching$perching_end # La etiqueta única de PIT que contiene información sobre la identidad del individuo que estuvo posando en la antena de RFID @@ -165,13 +165,13 @@ 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 detecto una vez por día, por ende, cada individuo realizo un evento de posar cada día. +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 detecciones en el siguiente paso de procesar o limpiar los datos originales. +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. -

Procesa los datos originales

+

Procesar los datos originales

-Cuando hayas detectado los eventos de posar en los datos originales, puedes seguir con procesar o limpiar los datos originales con la función `preprocess_detections()`. Los datos originales a veces contienen múltiples detecciones separadas por poco tiempo (como las detecciones de RFID cuando un individuo esta posando en la antena), y estos 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. +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: ```{r} @@ -213,7 +213,7 @@ glimpse(irbb_pp) 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 como 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. +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`. ```{r} @@ -224,10 +224,10 @@ rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFI 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 ordenaran de menos a más recientes + # 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 esta en el formato "dttm", significando que la conversión a formato POSIX sea realizo bien +# 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) rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) %>% @@ -272,11 +272,11 @@ glimpse(rfid_combined) ``` -Vas a construir la gráfica con estos datos usando funciones de `ggplot2`, un paquete que es parte del `tidyverse` pero que también se puede instalar y usar afuera del `tidyverse`. Puedes revisar este [enlace](https://ggplot2.tidyverse.org/) que tiene más recursos (en ingles) para aprender como 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 a diferentes niveles de experiencia, 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`](https://raw.githubusercontent.com/rstudio/cheatsheets/main/translations/spanish/data-visualization_es.pdf). +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](https://ggplot2.tidyverse.org/) que tiene más recursos (en inglés) para aprender cómo usar la notación de `ggplot` para hacer diferentes tipos de gráficas. Estos recursos incluyen secciones de tres libros diferentes con ejercicios para practicar hacer gráficas sencillas o complejas, y también un curso en línea y un seminario en línea. Puedes encontrar otros recursos en español en línea, como esta [guia para `ggplot2`](https://raw.githubusercontent.com/rstudio/cheatsheets/main/translations/spanish/data-visualization_es.pdf). El paquete de `ggplot2` tiene una notación única para construir gráficas, en 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()`, veras que la función inmediatamente dibuja una gráfica vacía en tu panel de `Plots` (gráficas) en RStudio. +Si llamas `ggplot()`, verás que la función inmediatamente dibuja una gráfica vacía en tu panel de `Plots` (gráficas) en RStudio. ```{r} ggplot() @@ -290,7 +290,7 @@ ggplot(data = rfid_combined) ``` -Necesitaras añadir otras funciones estéticas a esta capa fundamental de la gráfica para poder ver tus datos. Las funciones que usaras 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 esta representado 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, seria mejor hacer una gráfica de líneas. +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). ```{r} @@ -307,7 +307,7 @@ ggplot(data = rfid_combined) + 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 asignaran automáticamente por `ggplot` usando los colores por defecto del paquete, pero puedes cambiar estos colores usando la función `scale_color_manual()`: +Los colores de las líneas se asignarán automáticamente por `ggplot` usando los colores por defecto del paquete, pero puedes cambiar estos colores usando la función `scale_color_manual()`: ```{r} ggplot(data = rfid_combined) + @@ -324,11 +324,11 @@ ggplot(data = rfid_combined) + 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: +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: ```{r} # Cambia la columna dataset al tipo de datos "factor" -# Cuando especificas que el valor de "raw" ("original") venga primero en el argumento de levels, estas reorganizando los levels (niveles) de la columna para que este valor salga primero en la leyenda +# 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")) @@ -370,14 +370,14 @@ ggplot(data = rfid_combined) + scale_color_manual(values = c("orange", "darkgreen")) + - # El símbolo de ~ significa "por", así que estas creando un panel por cada valor único (o categoría) en la columna dataset + # 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 comparar patrones temporales. +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 como 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: +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: ```{r} ggplot(data = rfid_combined %>% @@ -396,14 +396,14 @@ ggplot(data = rfid_combined %>% scale_color_manual(values = c("orange", "darkgreen")) + - # El símbolo de ~ significa "por", así que estas creando un panel por cada valor único (o categoría) en la columna dataset + # 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 elimino del conjunto de datos procesados (fue filtrado usando el umbral temporal en `preprocess_detections`). +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()`. Usaras esta capa de `geom_segment()` para añadir líneas que contienen información temporal sobre cuando los eventos de posar empezaron y terminaron. +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. ```{r} ggplot(data = rfid_combined) + @@ -416,7 +416,7 @@ ggplot(data = rfid_combined) + scale_color_manual(values = c("orange", "darkgreen")) + - # El símbolo de ~ significa "por", así que estas creando un panel por cada valor único (o categoría) en la columna dataset + # 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 @@ -429,7 +429,7 @@ ggplot(data = rfid_combined) + ``` -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 dónde va a empezar cada línea en ambos ejes de la gráfica, respectivamente. También vas a tener que especificar dónde 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 dónde las líneas para los eventos de posar se dibujaran, 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. +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. ```{r} @@ -444,7 +444,7 @@ gg <- ggplot(data = rfid_combined) + scale_color_manual(values = c("orange", "darkgreen")) + - # El símbolo de ~ significa "por", así que estas creando un panel por cada valor único (o categoría) en la columna dataset + # 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 @@ -463,7 +463,7 @@ gg ``` -Ahora puedes hacer unos ajustes menores para seguir mejorando a 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). +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). ```{r} gg <- gg + @@ -471,7 +471,7 @@ 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 titulo + # 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 @@ -488,7 +488,7 @@ gg ``` -Puedes guardar las gráficas que creas en R como archivos físicos. Abajo usaras la función `ggsave()` para escribir la gráfica que hiciste arriba como un archivo en tu computadora. +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. ```{r} gg @@ -498,6 +498,6 @@ ggsave(file.path(path, "raw_processed_perching.tiff"), width = 8, height = 6, un ``` -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 "lo 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 titulo de cada eje, o la posición de la leyenda mientras determinas el tamaño final de la imagen. +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. \ No newline at end of file From 73e6067755a1a89434b4e0425dca91f4829a5a67 Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Thu, 4 Apr 2024 09:55:41 -0400 Subject: [PATCH 60/69] [PCT-472]: Finished manual grammar check of Tutorial 06 --- R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd | 102 +++++++++--------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd index ef315f8..b6eb6fb 100644 --- a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd +++ b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd @@ -54,7 +54,7 @@ source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R")

Termina el `pipeline` de ABISSMAL

-Aquí usaras 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 se grabaran juntos en el tiempo). +Aquí usarís la función de ABISSMAL que se llama `detect_clusters()` para identificar `clusters` de detecciones a través de los tipos de sensores (detecciones de diferentes sensores que fueron grabados juntos en el tiempo). ```{r} # El argumento run_length, o lo largo de cada serie de detecciones que ocurrieron juntos en el tiempo (cluster) tiene que ser 1 para poder detectar clusters con un largo de 2 detecciones @@ -91,7 +91,7 @@ Cuantos eventos de entrada y salida se anotaron por día?

Datos ausentes en R

-Para poder contra la cantidad de cada uno de estos eventos que ABISSMAL anoto 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 especifico 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. +Para poder contar la cantidad de cada uno de estos eventos que ABISSMAL anotó por día, necesitas saber cómo manejar datos ausentes en R. Los datos ausentes se suelen representar usando el valor `NA` ("not available" o "no disponible"), que es un tipo de dato específico en R. Puedes determinar si un vector (o una columna en un `dataframe`) contiene datos ausentes si usas la función `is.na()`, que devolverá `TRUE` cuando encuentra un valor de `NA` (un valor ausente) en el vector actual. ```{r} ?is.na() @@ -104,7 +104,7 @@ is.na(x) 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()`, estas preguntando si cada elemento de `x` *no* es equivalente a `NA`: +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`: ```{r} !is.na(x) @@ -113,11 +113,11 @@ Como `is.na()` es una frase condicional, también puedes usar otros símbolos es 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()` eliminara una fila cada vez que encuentra un valor de `FALSE` en la columna actual y en cambio no eliminara 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 eliminara esa fila como parte de la operación de filtrar. +Por ejemplo, la función `dplyr::filter()` eliminará una fila cada vez que encuentra un valor de `FALSE` en la columna actual y en cambio, no eliminará una fila cada vez que encuentra un valor de `TRUE`. O sea, si quieres eliminar files que contienen valores de `NA` para una columna actual, añadirías `!is.na(name_of_column)` adentro de `dplyr::filter()`, que debería de devolver `FALSE` cada vez que encuentra una fila con `NA`, y eliminará esa fila como parte de la operación de filtrar.

Cuenta eventos por día

-Ahora puedes escribir código para contar el número de eventos de entrada y salida que ABISSMAL anoto 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. +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. ```{r} scored_clusters %>% @@ -125,18 +125,18 @@ scored_clusters %>% 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(), estas invirtiendo la frase condicional y también el resultado, así que todos los valores TRUE se convertirán a FALSE. Por ende, dplyr::filter eliminara 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) + # 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 + # Luego puedes resumir los datos. El número de filas aquí es el número de entradas o salidas anotadas por día dplyr::summarise( n = n() ) ``` -Cuatro entradas y salidas se anotaron por día. Cómo se compara este resultado con el número de entradas y salidas que esperabas por día? Si regresas al código en los tutoriales anteriores (el tercer y el cuarto tutorial) dónde creaste los datos originales simulados, deberías de poder ver que empezaste por simular dos entradas y dos salidas por día en los datos para el sistema de RFID y los sensores de infrarrojo. Luego añadiste dos entradas y dos salidas más por día cuando simulaste fallas de detección del sistema de RFID (o sea, estos fueron movimientos capturados solamente por los sensores de infrarrojo). `score_clusters` detecto el número correcto de entradas y salidas por día. +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. ```{r} @@ -144,12 +144,12 @@ Ahora puedes revisar los eventos de posar, empezando con filtrar todas las filas 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 colo las columnas que quieres revisar visualmente + # Luego selecciona solo las columnas que quieres revisar visualmente dplyr::select(start, end, perching_PIT_tag) ``` -Como aprendiste en el tercer y el cuarto tutorial, el primer evento de posar por día se realizo 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"). +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? ```{r} @@ -160,7 +160,7 @@ scored_clusters %>% 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 inicio el movimiento) que tienen códigos de etiquetas PIT, pero que también no tienen una etiqueta de sensor el la columna perching_sensor (o sea, eventos de movimiento que no fueron 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( @@ -169,17 +169,17 @@ scored_clusters %>% ``` -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 solo los sensores infrarrojo que no pueden grabar información sobre la identidad de individuos. +Como puedes ver, cuatro eventos de movimiento que no fueron eventos de posar se asignaron al primer individuo (con la etiqueta PIT "1357aabbcc") en cada día, que es exactamente lo que simulaste en el tercer y el cuarto tutorial. Más movimientos que no fueron eventos de posar se detectaron a través de estos días también, pero como esos eventos no fueron capturados por la antena de RFID (o sea fueron fallas simuladas de la antena de RFID), estos movimientos fueron capturados por sólo los sensores infrarrojo que no pueden grabar información sobre la identidad de los individuos.

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

-Ahora puedes visualizar los resultados finales. En el código abajo vas a aprender cómo crear una gráfica de código de barras que es más compleja de lo que viste en el tutorial anterior, pero que es más fácil que interpretar que esa gráfica anterior. Para esta gráfica seria ú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 esta disponible), la identidad del individuo (cuando esta disponible), y los eventos de posar. +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 indicara 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. +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, seria 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 más útil para la gráfica que vas a hacer más adelante. +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 inicio 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`). +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`). ```{r} # is.na() devuelve TRUE cuando encuentra valores de NA adentro de un vector (o columna) y FALSE cuando el valor actual no es NA @@ -198,8 +198,8 @@ scored_clusters_gg <- scored_clusters %>% # 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 sen pudo anotar) - # En la frase condicional abajo añadiste is.na(perching_sensor) (después del símbolo de &) para solo 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 + # 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) @@ -208,7 +208,7 @@ scored_clusters_gg <- scored_clusters %>% 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 si fueron modificados +# 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) @@ -220,17 +220,17 @@ Este resultado se ve bien. Deberías de ver valores de `NA` en el `dataframe` pe 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: ```{r} -# Los colores están el el mismo orden que los levels (categorías) de la columna individual_initiated, así que el color naranja va a representar la etiqueta PIT "1357aabbcc" +# 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) cols <- c("orange", "darkgreen", "black") -# Los tipos de línea están el 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) +# 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) ltys <- c("solid", "longdash", "dotted") ``` -Luego puedes añadir líneas a la gráfica por identidad de 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. +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. ```{r} ggplot() + @@ -244,7 +244,7 @@ ggplot() + 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 unos de los dos individuos + # 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"), @@ -253,10 +253,10 @@ ggplot() + linewidth = 0.5 ) + - # Añade los tipos de línea customizadas a la gráfica + # Añade los tipos de línea customizados a la gráfica scale_linetype_manual(values = ltys) + - # Elimina el titulo del eje y + # Elimina el título del eje y ylab("") + # Usa esta función para convertir el fondo de la gráfica a blanco y negro @@ -272,11 +272,11 @@ ggplot() + ``` -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 patrones de variación en los movimientos a través del tiempo. 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()`. +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 solo 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 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 solo hay tres días de colección de datos con etiquetas que tienes que cambiar. +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. ```{r} # Crea una columna nueva en los datos originales para la fecha de colección de datos @@ -315,7 +315,7 @@ ggplot(data = scored_clusters_gg2) + 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 unos de los dos individuos + # 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"), @@ -324,10 +324,10 @@ ggplot(data = scored_clusters_gg2) + linewidth = 0.5 ) + - # Añade los tipos de línea customizadas a la gráfica + # Añade los tipos de línea customizados a la gráfica scale_linetype_manual(values = ltys) + - # Elimina el titulo del eje y + # Elimina el título del eje y ylab("") + # Usa esta función para convertir el fondo de la gráfica a blanco y negro @@ -341,14 +341,14 @@ ggplot(data = scored_clusters_gg2) + legend.position = "top" ) + - # Crea paneles en la gráfica por día, aquí usaras las etiquetas nuevas de día + # 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 sera 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. +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 realizara 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 usara 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). +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). ```{r} scored_clusters_gg3 <- scored_clusters_gg2 %>% @@ -357,12 +357,12 @@ scored_clusters_gg3 <- scored_clusters_gg2 %>% 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, pero este resultado es esperado (ve arriba) +# 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) ``` -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 titulo para el eje x y eliminar la cuadricula en el eje y adentro de cada panel: +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: ```{r} gg <- ggplot(data = scored_clusters_gg3) + @@ -376,7 +376,7 @@ gg <- ggplot(data = scored_clusters_gg3) + 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 unos de los dos individuos + # 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"), @@ -385,10 +385,10 @@ gg <- ggplot(data = scored_clusters_gg3) + linewidth = 0.5 ) + - # Añade los tipos de línea customizadas a la gráfica + # Añade los tipos de línea customizados a la gráfica scale_linetype_manual(values = ltys) + - # Elimina el titulo del eje y + # Elimina el título del eje y ylab("") + # Usa esta función para convertir el fondo de la gráfica a blanco y negro @@ -402,7 +402,7 @@ gg <- ggplot(data = scored_clusters_gg3) + legend.position = "top" ) + - # Crea paneles en la gráfica por día, aquí usaras las etiquetas nuevas de día + # 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 @@ -411,10 +411,10 @@ gg <- ggplot(data = scored_clusters_gg3) + date_labels = "%H:%M" ) + - # Añade un titulo para el eje x + # Añade un título para el eje x xlab("Time of day (HH:MM)") + - # Puedes quitar la cuadricula en el eje (mayor y menor) adentro de cada panel + # 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() @@ -444,7 +444,7 @@ 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 titulo de cada leyenda, aumentar el tamaño de texto de cada leyenda, y reducir el espacio blanco ente 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()`. +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()`. ```{r} # Ve más información sobre la función que controla los margenes (espacio blanco) alrededor de la leyenda @@ -480,7 +480,7 @@ ggsave(file.path(path, "behavioral_inferences.tiff"), width = 8, height = 6, uni 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 esta todo el código que escribiste para la gráfica final, en una forma más condensada y reorganizada: +Abajo está todo el código que escribiste para la gráfica final, en una forma más condensada y reorganizada: ```{r eval = FALSE} ggplot(data = scored_clusters_gg3) + @@ -494,7 +494,7 @@ ggplot(data = scored_clusters_gg3) + 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 unos de los dos individuos + # 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"), @@ -511,7 +511,7 @@ ggplot(data = scored_clusters_gg3) + linewidth = 2, lineend = "round" ) + - # Añade los tipos de línea customizadas a la gráfica + # 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() @@ -523,10 +523,10 @@ ggplot(data = scored_clusters_gg3) + color = guide_legend(title = "Individual") ) + - # Añade un titulo para el eje x + # Añade un título para el eje x xlab("Time of day (HH:MM)") + - # Elimina el titulo del eje y + # Elimina el título del eje y ylab("") + # Cambia la estética de las etiquetas del eje x @@ -535,7 +535,7 @@ ggplot(data = scored_clusters_gg3) + date_labels = "%H:%M" ) + - # Crea paneles en la gráfica por día, aquí usaras las etiquetas nuevas de día + # 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 @@ -543,7 +543,7 @@ ggplot(data = scored_clusters_gg3) + # 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 cuadricula en el eje (mayor y menor) adentro de cada panel + # 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(), @@ -557,4 +557,4 @@ ggplot(data = scored_clusters_gg3) + ``` -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 terminaste. Tus respuestas nos ayudaran mejorar estos tutoriales en el futuro. Un enlace a la forma de Google estará disponible en el archivo README para los tutoriales. \ No newline at end of file +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. \ No newline at end of file From 379c699ec2f80a368a4cff5bfa0acd044646592d Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Sun, 7 Apr 2024 19:14:19 -0400 Subject: [PATCH 61/69] [PCT-472]: Updated bilingual vignette info and added Google form links --- R/vignettes/README.html | 484 +++++++++++++++++++++++ R/vignettes/README.md | 43 +- R/vignettes/Tutorial_01_Introduccion.Rmd | 2 - R/vignettes/Vignette_01_Introduction.Rmd | 2 - 4 files changed, 512 insertions(+), 19 deletions(-) create mode 100644 R/vignettes/README.html diff --git a/R/vignettes/README.html b/R/vignettes/README.html new file mode 100644 index 0000000..7576a67 --- /dev/null +++ b/R/vignettes/README.html @@ -0,0 +1,484 @@ + + + + + + + + + + + + + +README + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +

+ABISSMAL R vignettes +

+

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

+

+Overview +

+

The goal of these vignettes is to disseminate R coding skills in a +biological context through the ABISSMAL behavioral tracking system. +There are six vignettes that cover the following topics:

+
    +
  1. An introduction to RStudio that includes downloading the ABISSMAL +GitHub repository, an introduction to the ABISSMAL data processing and +analysis pipeline, and information about troubleshooting errors and +reporting bugs

  2. +
  3. Learn how to set up a virtual workspace, including cleaning your +global environment, running code inside of RMarkdown files, package +installation, keyboard shortcuts, creating and specifying a working +directory

  4. +
  5. Create simulated datasets of animal movements while learning how +to create objects in R, different data types in R, and how to manipulate +objects with conditional statements and piping expressions

  6. +
  7. Save the simulated datasets and learn how to write data as files +on your computer, how to manipulate data frames with base R and the +tidyverse, and how to write simple and complex loops

  8. +
  9. Work through the ABISSMAL data processing pipeline while learning +how to access and use custom functions, as well as how to plot +timestamps in a barcode figure

  10. +
  11. Finish the ABISSMAL data analysis pipeline while using the R +coding skills that you practiced in the earlier vignettes, and end by +making a more complex and refined barcode figure

  12. +
+

Each vignette is available as a RMarkdown (extension +.Rmd) file that can be opened in RStudio. The knitted +output per vignette is available in HTML format, and you can open these +files in your default Internet browser. The HTML files contain text, +code, and the output of code chunks.

+

After completing the vignettes, it would be very helpful if you could +complete the brief Google +form for a post-vignette evaluation that will help us continue to +improve these vignettes over time.

+

+Resumen +

+

Nuestro objetivo es diseminar habilidades de programación en R en un +contexto biológico a través del sistema de ABISSMAL. Tenemos seis +tutoriales que cubren los siguientes temas:

+
    +
  1. Una introducción a RStudio que incluye descargar el repositorio +de GitHub de ABISSMAL, una introducción al pipeline de +procesar y analizar datos de ABISSMAL, e información sobre cómo manejar +y reportar errores

  2. +
  3. Aprende cómo configurar un espacio virtual, incluyendo limpiar tu +ambiente global, ejecutar código adentro de trozos de código de archivos +de RMarkdown, instalar paquetes, atajos para escribir código, y crear y +especificar un directorio de trabajo

  4. +
  5. Crea datos simulados de movimientos de animales mientras aprendes +cómo crear objetos en R, qué son los diferentes tipos de datos en R, y +cómo manipular objetos con frases condicionales y expresiones de +piping

  6. +
  7. Guarda los datos simulados y aprende cómo escribir datos a +archivos físicos en tu computadora, manipular dataframes +con R base y el tidyverse, y escribir bucles sencillos y +complejos

  8. +
  9. Trabaja en el pipeline de procesar datos con +ABISSMAL mientras aprendes cómo acceder y usas funciones customizadas, y +también cómo visualizar marcas de tiempo en una figura de código de +barras

  10. +
  11. Termina el pipeline de análisis de datos de ABISSMAL +mientras usas las habilidades de programación que practicaste en los +tutoriales anteriores, y terminarás con crear una figura de código de +barras más compleja y refinada

  12. +
+

Cada tutorial está disponible como un archivo de RMarkdown (extension +.Rmd) que puedes abrir en RStudio. El reporte “tejido” de +cada tutorial está disponible como un archivo en formato HTML, y puedes +abrir esos archivos en tu navegador por defecto. Los archivos de HTML +contienen texto, código, y los resultados de los trozos de código.

+

Sería una gran ayuda si después de completar los tutoriales también +completarías una encuesta a través de Google Forms. La +información que compartes nos ayudará mejorar los tutoriales en el +futuro.

+ + + + +
+ + + + + + + + + + + + + + + diff --git a/R/vignettes/README.md b/R/vignettes/README.md index 12d6a46..d0a3182 100644 --- a/R/vignettes/README.md +++ b/R/vignettes/README.md @@ -1,32 +1,45 @@ -

ABISSMAL R vignettes -

-Developers:
+

ABISSMAL R vignettes

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

Overview

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

Resumen

-4. Save data and practice writing loops +Nuestro objetivo es diseminar habilidades de programación en R en un contexto biológico a través del sistema de ABISSMAL. Tenemos seis tutoriales que cubren los siguientes temas: -5. Combine the raw data, detect perching events and pre-process the raw data, make simple barcode plots of the raw and per-processed data, and highlight how to encode color in these plots +1. Una introducción a RStudio que incluye descargar el repositorio de GitHub de ABISSMAL, una introducción al `pipeline` de procesar y analizar datos de ABISSMAL, e información sobre cómo manejar y reportar errores -6. For vignette 06, I want to detect clusters, score clusters, generate summary statistics, and make a complex barcode style visuals +2. Aprende cómo configurar un espacio virtual, incluyendo limpiar tu ambiente global, ejecutar código adentro de trozos de código de archivos de RMarkdown, instalar paquetes, atajos para escribir código, y crear y especificar un directorio de trabajo +3. Crea datos simulados de movimientos de animales mientras aprendes cómo crear objetos en R, qué son los diferentes tipos de datos en R, y cómo manipular objetos con frases condicionales y expresiones de `piping` -To do: +4. Guarda los datos simulados y aprende cómo escribir datos a archivos físicos en tu computadora, manipular `dataframes` con R base y el `tidyverse`, y escribir bucles sencillos y complejos -- Update file descriptions in README +5. Trabaja en el `pipeline` de procesar datos con ABISSMAL mientras aprendes cómo acceder y usas funciones customizadas, y también cómo visualizar marcas de tiempo en una figura de código de barras -- Make a pre- and post-vignette Google form to asses the content and style of the vignettes for disseminating basic R coding skills in a biological context +6. Termina el `pipeline` de análisis de datos de ABISSMAL mientras usas las habilidades de programación que practicaste en los tutoriales anteriores, y terminarás con crear una figura de código de barras más compleja y refinada -- Translate text/comments in each vignette to Spanish +Cada tutorial está disponible como un archivo de RMarkdown (extension `.Rmd`) que puedes abrir en RStudio. El reporte "tejido" de cada tutorial está disponible como un archivo en formato HTML, y puedes abrir esos archivos en tu navegador por defecto. Los archivos de HTML contienen texto, código, y los resultados de los trozos de código. +Sería una gran ayuda si después de completar los tutoriales también completarías una encuesta a través de [Google Forms](https://forms.gle/CaQXVWDrY5oHg8mCA). La información que compartes nos ayudará mejorar los tutoriales en el futuro. diff --git a/R/vignettes/Tutorial_01_Introduccion.Rmd b/R/vignettes/Tutorial_01_Introduccion.Rmd index d15179a..1dfa4c0 100644 --- a/R/vignettes/Tutorial_01_Introduccion.Rmd +++ b/R/vignettes/Tutorial_01_Introduccion.Rmd @@ -16,8 +16,6 @@ Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las

Resumen del tutorial y objetivos de aprendizaje

-Sería una gran ayuda si antes de empezar este tutorial puedes completar una encuesta a través de Google Forms. La información que compartes nos ayudará saber más de tu experiencia en R y analizando datos biológicos antes de empezar los tutoriales mismos, tanto como las habilidades que buscas mejorar a través de completar esta colección de tutoriales. También nos ayudará mejorar los tutoriales en el futuro. Puedes encontrar el enlace para la encuesta en el archivo README de los tutoriales. - En este primer tutorial, vas a leer sobre el programa RStudio, aprender cómo crear una versión local de el repositorio de ABISSMAL en GitHub, y aprender más sobre el `pipeline` de análisis de datos en ABISSMAL. En este tutorial vas a aprender cómo: 1. Configurar tu sesión de RStudio diff --git a/R/vignettes/Vignette_01_Introduction.Rmd b/R/vignettes/Vignette_01_Introduction.Rmd index dae6b3c..b5af88b 100644 --- a/R/vignettes/Vignette_01_Introduction.Rmd +++ b/R/vignettes/Vignette_01_Introduction.Rmd @@ -12,8 +12,6 @@ output:

Vignette overview and learning objectives

-Prior to starting this vignette, it would be very helpful if you could complete the brief Google form for a pre-vignette evaluation that will help us learn more about your experience in R and data science with biological datasets prior to completing these vignettes, and what types of skills you're hoping to improve. Your responses in this pre-vignette form 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. - This first vignette will take you through a brief introduction to RStudio, downloading a local version of the ABISSMAL GitHub repository, and an introduction to the data analysis workflow provided by ABISSMAL. Through this tutorial, you will learn: 1. How to configure your RStudio session From 71fef152a9cb9e00fbabca18a623d4ba5f52cfeb Mon Sep 17 00:00:00 2001 From: Grace Smith Vidaurre Date: Sun, 7 Apr 2024 19:21:46 -0400 Subject: [PATCH 62/69] [PCT-472]: Knitting Rmd files --- R/vignettes/Tutorial_01_Introduccion.html | 1854 ++++++++++++ R/vignettes/Tutorial_02_Configuracion.html | 1937 +++++++++++++ R/vignettes/Tutorial_03_SimularDatos.html | 2161 ++++++++++++++ R/vignettes/Tutorial_04_GuardarDatos.html | 2526 +++++++++++++++++ ...torial_05_ProcesarDatos_CrearGraficas.html | 2330 +++++++++++++++ .../Tutorial_06_FinalizarAnalisis.html | 2379 ++++++++++++++++ R/vignettes/Vignette_01_Introduction.html | 1828 ++++++++++++ .../{README.html => Vignette_02_Setup.html} | 1566 +++++++++- R/vignettes/Vignette_03_SimulateData.html | 2102 ++++++++++++++ R/vignettes/Vignette_04_SaveData.html | 2472 ++++++++++++++++ .../Vignette_05_ProcessData_BuildPlots.html | 2282 +++++++++++++++ .../Vignette_06_FinishAnalysisPipeline.html | 2335 +++++++++++++++ 12 files changed, 25695 insertions(+), 77 deletions(-) create mode 100644 R/vignettes/Tutorial_01_Introduccion.html create mode 100644 R/vignettes/Tutorial_02_Configuracion.html create mode 100644 R/vignettes/Tutorial_03_SimularDatos.html create mode 100644 R/vignettes/Tutorial_04_GuardarDatos.html create mode 100644 R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.html create mode 100644 R/vignettes/Tutorial_06_FinalizarAnalisis.html create mode 100644 R/vignettes/Vignette_01_Introduction.html rename R/vignettes/{README.html => Vignette_02_Setup.html} (67%) create mode 100644 R/vignettes/Vignette_03_SimulateData.html create mode 100644 R/vignettes/Vignette_04_SaveData.html create mode 100644 R/vignettes/Vignette_05_ProcessData_BuildPlots.html create mode 100644 R/vignettes/Vignette_06_FinishAnalysisPipeline.html diff --git a/R/vignettes/Tutorial_01_Introduccion.html b/R/vignettes/Tutorial_01_Introduccion.html new file mode 100644 index 0000000..75d6f83 --- /dev/null +++ b/R/vignettes/Tutorial_01_Introduccion.html @@ -0,0 +1,1854 @@ + + + + + + + + + + + + + + + +Tutorial 01: Introducción + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Información sobre esta traducción +

+

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

+

+Resumen del tutorial y objetivos de aprendizaje +

+

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

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

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

+

+Configurar tu sesión de RStudio +

+

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

+

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

+


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

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

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

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

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

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

  • +
+

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

+


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

+

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

+

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

+

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

+

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

+

Cuando hayas instalado GitHub Desktop, puedes:

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

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

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

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

  • +
  • Seleccionar “Clone repository”

  • +
  • Seleccionar la pestaña “URL”

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

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

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

  • +
+

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

+


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

+

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

+

+Procesar y analizar datos con ABISSMAL +

+

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

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

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

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

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

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

  10. +
+


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

+

+Solucionar errores en línea +

+

Mientras escribes y ejecutas código encontrarás errores que a veces +pueden ser frustrantes. Experimentar y solucionar los errores que te +salen es una parte muy importante de tu proceso de aprendizaje en R (u +otros lenguajes de programación). Los errores surgen por diferentes +razones, incluyendo errores de tipeo 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 tipeo +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 tipeo o problemas con la +estructura de tus datos, luego puedes considerar si el error se debe a +un problema con las funciones de ABISSMAL o los tutoriales mismos, y +puedes reportar el error a las desarolladoras de ABISSMAL en GitHub (ver +la siguiente sección).

+

+Reportar errores en GitHub +

+

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

+

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

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Tutorial_02_Configuracion.html b/R/vignettes/Tutorial_02_Configuracion.html new file mode 100644 index 0000000..02f2df3 --- /dev/null +++ b/R/vignettes/Tutorial_02_Configuracion.html @@ -0,0 +1,1937 @@ + + + + + + + + + + + + + + + +Tutorial 02: Configuración + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Información sobre esta traducción +

+

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

+

+Resumen del tutorial y objetivos de aprendizaje +

+

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

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

+Usar archivos de RMarkdown +

+

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

+

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

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

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

+

+Limpiar tu ambiente global +

+

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

+

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

+

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

+

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

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

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

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

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

  • +
+

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

+
rm(list = ls())
+

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

+

+Acceder documentación de funciones de R +

+

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

+
?rm
+
?ls
+

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

+

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

+

+Instalar y cargar paquetes +

+

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

+

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

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

En el trozo arriba, añadí un comentario arriba del código usando el +símbolo de #, un signo numeral o hashtag. +Cualquier texto que escribes después de un hashtag será +ignorado cuando ejecutas tu código. Es buena costumbre 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 costumbre. Comentar tu código es una forma de documentar +tu trabajo y sirve para hacer el código que publicas con manuscritos o +herramientas más accesibles para otros en la comunidad.

+

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

+
library(tidyverse)
+

+Atajos para escribir código +

+

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

+

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

+
libr
+

+Resolver frases incompletas en la consola +

+

Es importante vigilar el panel de la consola mientras escribes y +ejecutas código en RStudio. El símbolo de > (o “mayor +que”) en la consola significa que la consola terminó de ejecutar código +y está lista para otra operación. Cuando ves el símbolo de ++ (o “más”) en la consola, este símbolo indica que la frase +de código que acabas de ejecutar no está completa. Las frases +incompletas de código surgen de errores de tipeo, 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 +costumbre vigilar la consola para revisar que el código que ejecutas +produce resultados. Si ejecutas muchas frases de código a la vez y no +observas los resultados que esperas en la consola, puede que tengas una +frase incompleta por allí, y sería mejor limpiar la consola y revisar +bien tu código antes de ejecutarlo otra vez.

+

+Crear tu directorio de trabajo +

+

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

+

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

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

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

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

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

+

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

+

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

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

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

+

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

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

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

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

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

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Tutorial_03_SimularDatos.html b/R/vignettes/Tutorial_03_SimularDatos.html new file mode 100644 index 0000000..b7593cd --- /dev/null +++ b/R/vignettes/Tutorial_03_SimularDatos.html @@ -0,0 +1,2161 @@ + + + + + + + + + + + + + + + +Tutorial 03: Simular Datos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Información sobre esta traducción +

+

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

+

+Resumen del tutorial y objetivos de aprendizaje +

+

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

+

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

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

+Cargar paquetes +

+

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

+

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

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

+Crear un objeto de path +

+

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

+

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

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

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

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

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

+

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

+

+Crear marcas de tiempo para detecciones simuladas de movimientos +

+

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

+

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

+

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

+

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

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

Puedes ver las propiedades diferentes del objeto +rfid_ts:

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

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

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

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

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

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

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

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

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

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

+

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

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

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

+

+Crear vectores de metadatos +

+

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

+

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

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

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

+

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

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

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

+

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

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

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

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

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

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

+Crear dataframes con datos primarios y metadatos +

+

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

+

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

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

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

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

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

+

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

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

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

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

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

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

+Crear dataframes usando el tidyverse +

+

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

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

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

+

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

+

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

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

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

+

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

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

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

+

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

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Tutorial_04_GuardarDatos.html b/R/vignettes/Tutorial_04_GuardarDatos.html new file mode 100644 index 0000000..b2386e9 --- /dev/null +++ b/R/vignettes/Tutorial_04_GuardarDatos.html @@ -0,0 +1,2526 @@ + + + + + + + + + + + + + + + +Tutorial 04: Guardar Datos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Información sobre esta traducción +

+

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

+

+Resumen del tutorial y objetivos de aprendizaje +

+

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

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

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

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

+Crear los datos simulados +

+

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

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

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

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

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

+

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

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

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

+

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

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

+Revisar columnas de dataframe con R base +

+

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

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

+Revisar columnas de un dataframe con el +tidyverse +

+

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

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

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

+

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

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

+Guarda un dataframe como un archivo físico +

+

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

+
?write.csv
+

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

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

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

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

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

+

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

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

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

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

+Leer una hoja de cálculo +

+

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

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

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

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

+Guardar datos simulados para análisis con ABISSMAL +

+

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

+

+Filtrar un dataframe +

+

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

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

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

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

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

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

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

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

+Practicar escribir un bucle +

+

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

+

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

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

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

+

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

+
# En este bucle el 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 del variable de iteración (uno y dos, +respectivamente).

+

El 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 el 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.

+

El 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 del variable de iteración es que puedes usarlo +para indexar vectores, dataframes, u otros objetos que +creaste afuera del bucle. Por ejemplo, puedes usar x e +indexar con corchetes para imprimir el nombre de cada archivo que +quieres crear:

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

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

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

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

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

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

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

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

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

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

+

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

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

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

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

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

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

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

+

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

+

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

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

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

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

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

+

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

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

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

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

Accede los elementos de este list por nombre:

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

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

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

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

+

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

+

Para lograr este tipo de chequeo, deberías de ejecutar el código para +“congelar” los 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 los 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 los 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 el 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 el variable x para acceder el dataframe para el tipo de sensor actual y luego usar el variable y para filtrar este dataframe por el día actual
+    dats[[x]] %>%
+      # Filtra el dataframe por el día actual
+      dplyr::filter(day == days_tmp[y]) %>%
+      # Usa una operación de indexar con doble corchetes para especificar el path correcto por tipo de sensor
+      # Luego usa operaciones de filtrar con doble corchetes y un par de corchetes para acceder el nombre correcto del archivo para el tipo de sensor y el día actual
+      write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE)
+    
+  })
+  
+}))
+

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

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

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

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.html b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.html new file mode 100644 index 0000000..5ee053b --- /dev/null +++ b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.html @@ -0,0 +1,2330 @@ + + + + + + + + + + + + + + + +Tutorial 05: Procesar Datos y Crear Gráficas + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Información sobre esta traducción +

+

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

+

+Resumen del tutorial y objetivos de aprendizaje +

+

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

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

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

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

+Cargar las funciones de ABISSMAL +

+

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

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

+Accede información sobre las funciones de ABISSMAL +

+

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

+

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

+

+Combinar los datos originales +

+

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

+

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

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

  • +
  • path es tu directorio general de trabajo

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

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

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

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

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

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

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

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

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

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

+

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

+

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

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

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

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

+Detectar eventos de posar +

+

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

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

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

+

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

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

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

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

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

+
View(perching)
+

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

+

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

+

+Procesar los datos originales +

+

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

+

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

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

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

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

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

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

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

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

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

+

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

+

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

+

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

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

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

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

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

+

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

+

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

+
ggplot()
+

+

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

+
ggplot(data = rfid_combined)
+

+

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

+

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

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

+

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

+

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

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

+

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

+

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

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

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

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

+

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

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

+

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

+

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

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

+

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

+

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

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

+

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

+

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

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

+

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

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

+

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

+
gg
+

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

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

+

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

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Tutorial_06_FinalizarAnalisis.html b/R/vignettes/Tutorial_06_FinalizarAnalisis.html new file mode 100644 index 0000000..f219e21 --- /dev/null +++ b/R/vignettes/Tutorial_06_FinalizarAnalisis.html @@ -0,0 +1,2379 @@ + + + + + + + + + + + + + + + +Tutorial 06: Finalizar Los Analisis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Información sobre esta traducción +

+

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

+

+Resumen del tutorial y objetivos de aprendizaje +

+

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

+

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

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

+Cargar funciones de ABISSMAL +

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

+Termina el pipeline de ABISSMAL +

+

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

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

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

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

+Revisa los resultados finales +

+

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

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

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

+

+Datos ausentes en R +

+

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

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

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

+

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

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

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

+

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

+

+Cuenta eventos por día +

+

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

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

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

+

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

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

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

+

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

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

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

+

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

+

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

+

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

+

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

+

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

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

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

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

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

+

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

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

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

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

+

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

+

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

+

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

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

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

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

+

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

+

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

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

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

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

+

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

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

+

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

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

+

Finalmente puedes guardar esta gráfica como un archivo:

+
gg
+

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

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

+

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

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

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

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Vignette_01_Introduction.html b/R/vignettes/Vignette_01_Introduction.html new file mode 100644 index 0000000..aba2193 --- /dev/null +++ b/R/vignettes/Vignette_01_Introduction.html @@ -0,0 +1,1828 @@ + + + + + + + + + + + + + + + +Vignette 01: Introduction + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Vignette overview and learning objectives +

+

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

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

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

+

+Configure your RStudio session +

+

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

+

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

+


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

+

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

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

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

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

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

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

  • +
+

The RStudio pane layout should now look like this:

+


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

+

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

+

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

+

+Create a local version of an existing GitHub repository +

+

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

+

Once you have installed GitHub Desktop:

+
    +
  • Open GitHub Desktop by clicking on the icon

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

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

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

  • +
  • Select “Clone repository”,

  • +
  • Select the tab “URL”

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

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

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

  • +
+

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

+


A screenshot of the directory that is the local ABISSMAL repository

+

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

+

+ABISSMAL data processing and analysis +

+

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

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

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

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

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

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

  10. +
+


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

+

+Troubleshoot errors online +

+

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

+

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

+

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

+

+Report bugs on GitHub +

+

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

+

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

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/README.html b/R/vignettes/Vignette_02_Setup.html similarity index 67% rename from R/vignettes/README.html rename to R/vignettes/Vignette_02_Setup.html index 7576a67..e67a629 100644 --- a/R/vignettes/README.html +++ b/R/vignettes/Vignette_02_Setup.html @@ -9,9 +9,11 @@ + + -README +Vignette 02: Setup + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Vignette overview and learning objectives +

+

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

+

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

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

+Load packages +

+

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

+

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

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

+Create a path object +

+

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

+

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

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

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

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

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

+

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

+

+Create timestamps of simulated detections of animal movements +

+

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

+

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

+

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

+

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

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

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

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

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

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

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

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

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

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

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

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

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

+

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

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

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

+

+Create metadata vectors +

+

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

+

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

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

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

+

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

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

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

+

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

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

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

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

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

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

+Create data frames with primary data and metadata +

+

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

+

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

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

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

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

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

+

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

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

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

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

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

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

+Create data frames using the tidyverse +

+

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

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

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

+

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

+

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

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

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

+

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

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

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

+

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

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Vignette_04_SaveData.html b/R/vignettes/Vignette_04_SaveData.html new file mode 100644 index 0000000..77d73d1 --- /dev/null +++ b/R/vignettes/Vignette_04_SaveData.html @@ -0,0 +1,2472 @@ + + + + + + + + + + + + + + + +Vignette 04: Save Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Vignette overview and learning objectives +

+

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

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

+Load packages and your working directory path +

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

+Create the simulated data +

+

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

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

+Simulate 3 days of RFID data collection +

+

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

+
# Make a vector for the experimental replicate
+exp_rep <- rep(x = "Nest_01", times = length(rfid_ts))
+
+# Make a vector of the PIT tag codes
+# Allocate the first 4 RFID detections to the first individual, the first perching event (4 detections) to the first individual, and the second perching event (6 detections) to the second individual
+# The 3 rep() expressions are combined into a single vector using the c() function
+PIT_tag <- c(rep("1357aabbcc", 4), rep("1357aabbcc", 4), rep("2468zzyyxx", 6))
+
+# Make the data frame with the experimental replicate metadata and the timestamps
+sim_dats_rfid <- data.frame(chamber_id = exp_rep, timestamps = rfid_ts)
+
+# Overwrite the data frame with the modified version that has columns for the year, month, and day
+sim_dats_rfid <- sim_dats_rfid %>%
+  dplyr::mutate(
+    year = 2023
+  ) %>%
+  dplyr::mutate(
+    month = 08,
+    day = 01
+  ) %>%
+  # Add the PIT tag metadata as a new column
+  dplyr::mutate(
+    PIT_tag = PIT_tag
+  ) %>% 
+  dplyr::mutate(
+    sensor_id = "RFID"
+  )
+
+glimpse(sim_dats_rfid)
+
## Rows: 14
+## Columns: 7
+## $ chamber_id <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nes…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+## $ PIT_tag    <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabbcc", "13…
+## $ sensor_id  <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+

Next, you can use this data frame to simulate data collection over +more than one day. To create more observations (rows) for two additional +days, you can append rows from a modified copy of +sim_dats_rfid to itself.

+

In the code below, you’re piping sim_dats_rfid into +bind_rows(), which indicates that this is the original +object to which you want to append or add new rows. Then the code inside +of bind_rows() indicates the data frame (the new rows) that +will be appended to sim_dats_rfid. In this case, the code +inside of bind_rows() pipes sim_dats_rfid into +dplyr::mutate(), which is a function that you’re using to +modify the day column to reflect a subsequent day of data +collection. Then you’ll repeat this process to simulate a third day of +data collection:

+
sim_dats_rfid <- sim_dats_rfid %>% 
+  bind_rows(
+    sim_dats_rfid %>% 
+      dplyr::mutate(
+        day = 02
+      )
+  ) %>% 
+  bind_rows(
+    sim_dats_rfid %>% 
+      dplyr::mutate(
+        day = 03
+      )
+  )
+
+glimpse(sim_dats_rfid) # Triple the number of rows, looks good
+
## Rows: 42
+## Columns: 7
+## $ chamber_id <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nes…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,…
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2,…
+## $ PIT_tag    <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabbcc", "13…
+## $ sensor_id  <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+

+Check data frame columns with base R +

+

You checked that simulated data frame has data collected over three +days by looking at the structure of the new object. You can also check +the unique values present in the column day. Below is a way +of checking the unique values contained in a column of a data frame, +using two different examples of base R notation for accessing +columns:

+
# The name of a data frame object, followed by a $ sign and then the name of a column allows you to pull out one column at a time from a data frame. A data frame column is a vector, so when you run this code you will see a vector of values print to the console
+sim_dats_rfid$day
+
##  [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3
+## [39] 3 3 3 3
+
# You can also access a column in a data frame by using double square brackets after the name of the data frame, and placing the column name in quotes inside of the inner brackets
+sim_dats_rfid[["day"]]
+
##  [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3
+## [39] 3 3 3 3
+
# You can use the function unique() to see the unique values of any vector, including a column of a data frame
+unique(sim_dats_rfid$day) # Three days, looks good
+
## [1] 1 2 3
+
unique(sim_dats_rfid[["day"]]) # Three days, looks good
+
## [1] 1 2 3
+

+Check data frame columns with the tidyverse +

+

You can also check the unique values in a column using functions from +the tidyverse. In the expression below, you’re piping +sim_dats_rfid into the function pull(), which +lets you pull the column day out of the data frame as a +vector. Then that vector is piped into the function +unique() to check the unique values contained within it. +The function unique() does not need an argument inside of +the parentheses here because you’re already piping the output that it +needs directly into the function.

+
# Three days, looks good
+sim_dats_rfid %>% 
+  pull(day) %>% 
+  unique()
+
## [1] 1 2 3
+

+Simulate 3 days of beam breaker data collection +

+

Next, repeat this process of creating a data frame with metadata for +the infrared beam breaker datasets. Since the beam breakers do not +collect unique individual identity information, you will add columns for +the year, month, day, and sensor type. You will also simulate data +collection for the beam breakers over the same three days as the RFID +system.

+
# Overwrite the vector exp_rep with a new vector the same length as o_irbb_ts and i_irbb_ts together
+exp_rep <- rep(x = "Nest_01", times = length(o_irbb_ts) + length(i_irbb_ts))
+
+# Here the timestamps of both beam breaker pairs have been added to the same column using c()
+sim_dats_irbb <- data.frame(chamber_id = exp_rep, timestamps = c(o_irbb_ts, i_irbb_ts))
+
+sim_dats_irbb <- sim_dats_irbb %>%
+  dplyr::mutate(
+    year = 2023,
+    month = 08,
+    day = 01,
+    # Add a unique sensor identifier by beam breaker pair
+    # Each unique label is repeated for the length of the vector of timestamps of each beam breaker pair
+    sensor_id = c(rep("Outer Beam Breakers", length(o_irbb_ts)), rep("Inner Beam Breakers", length(i_irbb_ts)))
+  )
+
+glimpse(sim_dats_irbb)
+
## Rows: 27
+## Columns: 6
+## $ chamber_id <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nes…
+## $ timestamps <chr> "09:59:59", "10:05:01", "10:59:59", "11:05:01", "06:05:05",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,…
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
+## $ sensor_id  <chr> "Outer Beam Breakers", "Outer Beam Breakers", "Outer Beam B…
+
sim_dats_irbb <- sim_dats_irbb %>% 
+  bind_rows(
+    sim_dats_irbb %>% 
+      dplyr::mutate(
+        day = 02
+      )
+  ) %>% 
+  bind_rows(
+    sim_dats_irbb %>% 
+      dplyr::mutate(
+        day = 03
+      )
+  )
+
+glimpse(sim_dats_irbb) # Triple the number of rows, looks good
+
## Rows: 81
+## Columns: 6
+## $ chamber_id <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nes…
+## $ timestamps <chr> "09:59:59", "10:05:01", "10:59:59", "11:05:01", "06:05:05",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,…
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
+## $ sensor_id  <chr> "Outer Beam Breakers", "Outer Beam Breakers", "Outer Beam B…
+
# Three days, looks good
+sim_dats_irbb %>% 
+  pull(day) %>% 
+  unique()
+
## [1] 1 2 3
+

+Save a data frame as a physical file +

+

The data frames that you create and manipulate in R can be saved as +physical files in your working directory. You have many different file +type options for saving data frames, but I recommend using .csv format +since this file type is compatible with R, Microsoft Word, and other +software. You can use the function write.csv() to save data +frames to .csv spreadsheets on your computer:

+
?write.csv
+

In order to write a physical file to your working directory, you need +to tell R 1) where to save the file and 2) the name of the file that you +want to create. You can pass both of these pieces of information to +write.csv() by combining your working directory path and +the file name through the function file.path(). For this +example, you’ll create a test file as you practice using +write.csv():

+
# Create a custom file name by combining the path for your working directory with the file name that you want to write out
+# The function file.path() will combine both pieces of information into a single file path
+rfid_file <- file.path(path, "test_file.csv")
+
+# This object contains the location where the file will be saved, followed by the file name
+rfid_file
+
## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/test_file.csv"
+

Next, you can pipe the data frame to write.csv() and +specify additional information for creating the .csv file, such as +whether you want to add a column of numeric row identifiers:

+
sim_dats_rfid %>% 
+  # Write out the data frame as a .csv spreadsheet. Do not include row names
+  #The "." below means that the function write.csv() will operate on the object that is piped in, which here is the data frame sim_dats_rfid
+  write.csv(x = ., file = rfid_file, row.names = FALSE)
+

As specified in the function documentation for +write.csv(), the function will include column names in the +resulting spreadsheet by default. The function will also not append new +information to the .csv if it already exists, so if you already created +this file then it will be overwritten when you run the function +again.

+

Next, you can check that write.csv() worked as expected +by using list.files() to check the files in your working +directory.

+
# List all files in the given path
+list.files(path)
+

You can also use list.files() to customize a search with +the argument called pattern. Using the argument +pattern here is similar to searching for a specific word +inside of a text document. The dollar sign placed after “.csv” means +that you’re telling the function to search specifically for all files +that end in the pattern “.csv”:

+
# List only files that end in the pattern ".csv" in the given path
+list.files(path, pattern = ".csv$")
+

+Read in a spreadsheet +

+

You can read one these files back into R with the function +read.csv(). In the code below, you’re piping the output of +read.csv() directly into glimpse() to check +out the structure of the resulting data frame. The output of the code is +printed to the console, but is not saved inside of an object.

+
read.csv(file.path(path, "test_file.csv")) %>% 
+  glimpse()
+

Now that you’ve practiced using write.csv() and +read.csv(), you can delete the temporary file that you +created by feeding the rfid_file object to the function +file.remove().

+
rfid_file <- file.path(path, "test_file.csv")
+rfid_file
+
+file.remove(rfid_file)
+

+Save simulated data for the ABISSMAL workflow +

+

In the code below, you’ll work through a pipeline to save the +simulated data in the format and locations expected by the custom +ABISSMAL functions. In order to use the ABISSMAL pipeline, the simulated +raw data needs to be saved in a separate spreadsheet per sensor and day +of data collection. These spreadsheets need to be saved inside of a +folder per sensor as well. ABISSMAL will automatically save data in +these ways when the tracking system is used to collect data from live +animals.

+

+Filter a data frame +

+

In the code below, you’ll practice how to use the function +dplyr::filter() to filter rows of a data frame by day and +then save a filtered data frame with write.csv(). To filter +a data frame, you can use a conditional statement inside of the function +dplyr::filter():

+
# Pipe the data frame into the filter() function
+sim_dats_rfid %>% 
+  # Filter the data frame by pulling out all rows in which the day column was equal to 1
+  dplyr::filter(day == 1) %>%
+  glimpse()
+
## Rows: 14
+## Columns: 7
+## $ chamber_id <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nes…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+## $ PIT_tag    <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabbcc", "13…
+## $ sensor_id  <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+
# Check that the filtering was done correctly by getting the unique values in the day column. Looks good
+sim_dats_rfid %>% 
+  dplyr::filter(day == 1) %>%
+  pull(day) %>% 
+  unique()
+
## [1] 1
+

You can obtain similar results by inverting the conditional statement +inside of dplyr::filter() to remove days that are +not the second or third days of data collection. Below +you added two conditional statements together using the symbol +“&”.

+
sim_dats_rfid %>% 
+  # Filter the data frame by pulling out all rows in which the day column was not equal to 2
+  dplyr::filter(day != 2 & day != 3) %>%
+  glimpse()
+
## Rows: 14
+## Columns: 7
+## $ chamber_id <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nes…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+## $ PIT_tag    <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabbcc", "13…
+## $ sensor_id  <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+
# Check that the filtering was done correctly by getting the unique values in the day column. Looks good
+sim_dats_rfid %>% 
+  dplyr::filter(day != 2 & day != 3) %>%
+  pull(day) %>% 
+  unique()
+
## [1] 1
+

Now that you’ve practiced filtering a data frame, you can combine +this filtering step with writing out a .csv file that contains the +filtered data frame with data collected on a single day:

+
# Make a new directory inside of your working directory for saving data
+file.path(path, "Data") # Check the new path
+dir.create(file.path(path, "Data")) # Create the new path
+
+# Then make a new directory inside of the Data folder for the raw RFID data
+file.path(path, "RFID") # Check the new path
+dir.create(file.path(path, "Data", "RFID")) # Create the new path
+
+# Make the new file name with your working directory path
+# Make sure to specify that the spreadsheet will be saved inside of the new folder "RFID"
+rfid_file <- file.path(path, "Data/RFID", "test.csv")
+rfid_file
+
+# Filter the simulated RFID data to pull out the first day of data collection
+sim_dats_rfid %>% 
+  dplyr::filter(day == 1) %>% 
+  # Write out the filtered data frame as a .csv spreadsheet. Do not include row names
+  # Remember that the "." means the function will use the object that is piped in, which here is the data frame filtered to retain day 1 of data collection only
+  write.csv(x = ., file = rfid_file, row.names = FALSE)
+

Check that the test file was created inside of the new RFID folder, +and then delete it.

+
list.files(file.path(path, "Data/RFID"), pattern = ".csv$")
+
+rfid_file <- file.path(path, "Data/RFID", "test.csv")
+rfid_file
+
+file.remove(rfid_file)
+

+Practicing writing a loop +

+

In order to write out a data frame for each day of data collection +per sensor, you could repeat the code above 6 times (three times per +sensor). But it’s good to avoid repeating the same code over and over, +since this can lead to messy scripts as well as a greater risk for +errors in data processing and analysis. Whenever you need to run the +same code many times, you can write a loop. Loops are an important +coding skill and we’ll work through building a loop step by step.

+

To start, you can practice building a loop with the function +lapply().

+
?lapply
+
+# Make a vector of the files to write out
+files <- c(file.path(path, "Data/RFID", "test1.csv"), file.path(path, "Data/RFID", "test2.csv"))
+
+files
+
## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Data/RFID/test1.csv"
+## [2] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Data/RFID/test2.csv"
+
length(files)
+
## [1] 2
+

Next, you can start writing out a looping structure. In the code +below, the argument X specifies the number of times that +the loop will run. In this case, X is a numeric vector from +1 to the length of the files vector and contains the +numbers 1 and 2. Therefore the loop will run twice, and each value in +X will be used in each loop iteration to write out one file +at a time

+

The argument FUN is a custom function, written using the +function(x){} notation. All of the code inside of the curly +brackets {} will be run for each iteration of the loop. The +argument x inside function() is the iterating +variable, or the variable that will take on a different value of the +vector supplied to X in each iteration of the loop.

+
# In this loop, the iterating variable x will take on each value in the vector supplied to the X argument. This means that in the first loop iteration, x will take on the numeric value of 1. In the second loop iteration, x will hold the numeric value 2. To test that this is true, you can run the loop below, which will print the value of x per iteration to the console
+lapply(X = 1:length(files), FUN = function(x){
+  
+  x
+  
+})
+
## [[1]]
+## [1] 1
+## 
+## [[2]]
+## [1] 2
+

As you can see, the output of this loop is a list with 2 elements. +Each list element is shown in double square brackets ([[1]] and [[2]]), +and each list element contains a vector of length 1 that holds the value +of the iterating variable in each iteration (1 and 2, respectively).

+

The iterating variable of the loop, or x, does not exist +as an object outside of the function. So if you print x +outside of the loop, no object will be found. If you created a object +called x outside of the loop above, then you will see the +contents of that object instead. This means that writing a loop that +uses x as an iterating variable will not affect other lines +of code that use an object called x outside of the loop, +and vice versa.

+

The iterating variable of a function does not always need to be +x, but can be another letter (i, +j, y, z) or any combination of +multiple letters and numbers, underscores, and periods (as long as the +variable name starts with a letter).

+

A useful property of the iterating variable is that you can use it to +index vectors, data frames, or other objects that you created outside of +the loop. For instance, you can use x and square bracket +indexing to print the name of each file that you want to save:

+
lapply(X = 1:length(files), FUN = function(x){
+  
+  files[x]
+  
+})
+
## [[1]]
+## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Data/RFID/test1.csv"
+## 
+## [[2]]
+## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Data/RFID/test2.csv"
+

You can also modify the code inside of the custom function to save +each of these files, by placing the expression files[x] +inside of the write.csv() function and piping a data frame +to write.csv().

+
lapply(X = 1:length(files), FUN = function(x){
+  
+  # In each iteration of the loop, you will save the data frame `sim_dats_rfid` as a separate spreadsheet with the file name specified in the given iteration
+  sim_dats_rfid %>%
+    write.csv(file = files[x], row.names = FALSE)
+  
+})
+

You should see 2 “NULL” outputs printed to the console if the loop +runs correctly. When you check the contents of the nested RFID folder, +you should see that both of the testing .csv files were written out:

+
list.files(file.path(path, "Data/RFID"))
+
## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+

You just wrote out 2 spreadsheets using 1 loop, but you wrote out the +same data frame to each spreadsheet. In order to write out a different +data frame to each spreadsheet, you can add the data frame filtering +that you learned above to the loop. In the code below, you’ll also +create another vector object called days, and then use the +iterating variable to filter sim_dats_rfid and write out a +spreadsheet by each day in days.

+
days <- c(1, 2, 3)
+
+lapply(X = 1:length(files), FUN = function(x){
+  
+  sim_dats_rfid %>%
+    # Filter the data frame by one day at a time
+    dplyr::filter(day == days[x]) %>%
+    # Write out data frame filtered by the given day to a separate spreadsheet 
+    write.csv(file = files[x], row.names = FALSE)
+  
+})
+

You can delete these files that you created for testing. In the code +below, you’re seeing another pattern searching example while checking +whether the function ran correctly. The string that you pass to the +argument pattern starts with the symbol ^, +which is a symbol that indicates you’re searching for all files that +start with the pattern “test”. You’re also telling the function +to return the full location of each file along with the file name, so +that file.remove() knows exactly where to look for each +file.

+
rem_files <- list.files(file.path(path, "Data/RFID"), pattern = "^test", full.names = TRUE)
+rem_files
+
+file.remove(rem_files)
+

+Use a loop to save RFID spreadsheets +

+

Now you can put all of these pieces together and use the loop to +write out a spreadsheet per day for the RFID sensor.

+
# Make a vector of the custom file names to write out
+files <- c(
+  "RFID_simulated_Pair-01_2023_08_01.csv", 
+  "RFID_simulated_Pair-01_2023_08_02.csv", 
+  "RFID_simulated_Pair-01_2023_08_03.csv"
+)
+
+# Add the file path for the correct directory
+files <- file.path(path, "Data/RFID", files) 
+files
+
+# Make a vector of the days to write out (1 day per iteration of the loop)
+days <- c(1, 2, 3)
+days
+
+# You can drop the lapply() argument names, since you're supplying the arguments in the order that the function expects
+invisible(lapply(1:length(files), function(x){
+  
+  sim_dats_rfid %>%
+    # Filter the data frame by one day at a time
+    dplyr::filter(day == days[x]) %>%
+    # Write out the filtered data frame to the correct spreadsheet for the given day
+    write.csv(file = files[x], row.names = FALSE)
+  
+}))
+

In the code above, you’ll see one additional change that we made to +the loop by wrapping it in the function invisible(). This +function silences the output printed to the console that you saw several +times above, in which the output of each lapply() iteration +is enclosed in double square brackets, and then a single pair of square +brackets. lapply() is a function that returns a list, and +when the function is carried out correctly but there is not output to +print, the function will return NULL values (indicating +empty output). This is the expected behavior of lapply() +for our purposes, because we used lapply() to write out +physical files rather than to return output to the R console. Since you +can check that the function worked by running list.files(), +using invisible() helps clean up the amount of text that +you need to check in your console.

+
# The new .csv files for each day of RFID data are present, looks good
+list.files(file.path(path, "Data/RFID"))
+

You can remove these files for now, since you will work on writing a +nested loop structure that automatically writes out the data for each +sensor type per day.

+
files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv")
+
+# Add the file path for the correct directory
+files <- file.path(path, "Data/RFID", files) 
+files
+
+file.remove(files)
+

If you want more practice writing loops, you can write out a loop +structure to save a spreadsheet for each day of data collection for the +infrared beam breaker dataset.

+

+Write a nested loop to save spreadsheets +

+

If you want to cut down on the amount of code that you’re writing to +save the raw data per sensor and day of data collection, you could write +out both the RFID and beam breaker data in the same looping structure. +To continue, you’ll need to create another directory for the beam +breaker data:

+
dir.create(file.path(path, "Data", "IRBB"))
+

To carry out the file filtering and writing for both sensor types +across days, you’ll use a type of object called a list. You +will then use these lists inside of a nested loop structure:

+
# Make a list of the custom file names to write out for each sensor type and day
+files <- list(
+  c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv"),
+  c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv", "IRBB_simulated_Pair-01_2023_08_03.csv")
+)
+
+glimpse(files)
+
## List of 2
+##  $ : chr [1:3] "RFID_simulated_Pair-01_2023_08_01.csv" "RFID_simulated_Pair-01_2023_08_02.csv" "RFID_simulated_Pair-01_2023_08_03.csv"
+##  $ : chr [1:3] "IRBB_simulated_Pair-01_2023_08_01.csv" "IRBB_simulated_Pair-01_2023_08_02.csv" "IRBB_simulated_Pair-01_2023_08_03.csv"
+

Lists are useful objects because they’re very flexible. Unlike +vectors, a single list can hold many different types of data. And then +unlike a data frame, the elements of a list do not need to have the same +dimensions. The elements of a list can also be different data structures +or object types themselves. For instance, lists can contain vectors, +data frames, and other lists all inside of the same larger list object. +The list that you created above has 2 elements, and each element is a +vector of 3 character strings containing the file names that you +supplied using the function c().

+

You can index lists in a manner that is similar to indexing vectors +and data frames, but indexing lists can be done with single or double +square brackets for different outcomes:

+
# Using a single square bracket to filter a list returns the first list element in list format
+files[1]
+
## [[1]]
+## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+
glimpse(files[1])
+
## List of 1
+##  $ : chr [1:3] "RFID_simulated_Pair-01_2023_08_01.csv" "RFID_simulated_Pair-01_2023_08_02.csv" "RFID_simulated_Pair-01_2023_08_03.csv"
+
# Using double square brackets returns the first list element only, so it removes the list structure and shows the original data structure of that element (here a vector)
+files[[1]]
+
## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+
glimpse(files[[1]])
+
##  chr [1:3] "RFID_simulated_Pair-01_2023_08_01.csv" ...
+

Lists can also have named elements, which makes it possible to access +elements by name.

+
# Make a named list of the custom file names to write out
+files <- list(
+  `RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv"),
+  `IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv", "IRBB_simulated_Pair-01_2023_08_03.csv")
+)
+
+glimpse(files)
+
## List of 2
+##  $ RFID: chr [1:3] "RFID_simulated_Pair-01_2023_08_01.csv" "RFID_simulated_Pair-01_2023_08_02.csv" "RFID_simulated_Pair-01_2023_08_03.csv"
+##  $ IRBB: chr [1:3] "IRBB_simulated_Pair-01_2023_08_01.csv" "IRBB_simulated_Pair-01_2023_08_02.csv" "IRBB_simulated_Pair-01_2023_08_03.csv"
+

Access the list elements by name:

+
# Using a single bracket returns the first element as a list, called "RFID"
+files["RFID"]
+
## $RFID
+## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+
# Using the dollar sign or double square brackets returns the first list element in its original format
+files$RFID
+
## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+
files[["RFID"]]
+
## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+

Lists are very useful data structures for setting up nested loop +operations. For instance, if you want to write out a spreadsheet per day +per sensor type, you need 1) a loop to iterate over sensor types, and +then 2) a loop to iterate over days per sensor. You can use lists to +create nested data structures to supply to a nested loop, which will +help you make sure that each layer of the loop runs in the way that you +expect. For instance, the list called files reflects the +nested loop that you need because files are listed first by sensor type +(each element of the list) and then by date (each element of the vector +inside of each list element):

+
# Make a vector of sensor labels
+sensors <- c("RFID", "IRBB")
+
+sensors
+
## [1] "RFID" "IRBB"
+
# Make a named list of the custom file names to write out
+files <- list(
+  `RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv"),
+  `IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv", "IRBB_simulated_Pair-01_2023_08_03.csv")
+)
+
+files
+
## $RFID
+## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+## 
+## $IRBB
+## [1] "IRBB_simulated_Pair-01_2023_08_01.csv"
+## [2] "IRBB_simulated_Pair-01_2023_08_02.csv"
+## [3] "IRBB_simulated_Pair-01_2023_08_03.csv"
+
# Make a list of file paths per sensor that will be used inside of the loop
+file_dirs <- list(
+  `RFID` = file.path(path, "Data/RFID"),
+  `IRBB` = file.path(path, "Data/IRBB") 
+)
+
+file_dirs
+
## $RFID
+## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Data/RFID"
+## 
+## $IRBB
+## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Data/IRBB"
+
# Make a list of the days to write out for each sensor
+# This could be a single vector rather than a list since you want to write out the same days per sensor, but a list is useful in case you wanted to change the days (and number of days) to write out per sensor
+days <- list(
+  `RFID` = c(1, 2, 3),
+  `IRBB` = c(1, 2, 3)
+)
+
+days
+
## $RFID
+## [1] 1 2 3
+## 
+## $IRBB
+## [1] 1 2 3
+
# Finally, you need to make a list of the data frames that you want to write out. Since you already know how to filter data frames by day, that code can go inside of the loop. Here you can specify the data frame to use in the filtering operation per sensor type
+dats <- list(
+  `RFID` = sim_dats_rfid,
+  `IRBB` = sim_dats_irbb
+)
+
+glimpse(dats)
+
## List of 2
+##  $ RFID:'data.frame':    42 obs. of  7 variables:
+##   ..$ chamber_id: chr [1:42] "Nest_01" "Nest_01" "Nest_01" "Nest_01" ...
+##   ..$ timestamps: chr [1:42] "10:00:00" "10:05:00" "11:00:00" "11:05:00" ...
+##   ..$ year      : num [1:42] 2023 2023 2023 2023 2023 ...
+##   ..$ month     : num [1:42] 8 8 8 8 8 8 8 8 8 8 ...
+##   ..$ day       : num [1:42] 1 1 1 1 1 1 1 1 1 1 ...
+##   ..$ PIT_tag   : chr [1:42] "1357aabbcc" "1357aabbcc" "1357aabbcc" "1357aabbcc" ...
+##   ..$ sensor_id : chr [1:42] "RFID" "RFID" "RFID" "RFID" ...
+##  $ IRBB:'data.frame':    81 obs. of  6 variables:
+##   ..$ chamber_id: chr [1:81] "Nest_01" "Nest_01" "Nest_01" "Nest_01" ...
+##   ..$ timestamps: chr [1:81] "09:59:59" "10:05:01" "10:59:59" "11:05:01" ...
+##   ..$ year      : num [1:81] 2023 2023 2023 2023 2023 ...
+##   ..$ month     : num [1:81] 8 8 8 8 8 8 8 8 8 8 ...
+##   ..$ day       : num [1:81] 1 1 1 1 1 1 1 1 1 1 ...
+##   ..$ sensor_id : chr [1:81] "Outer Beam Breakers" "Outer Beam Breakers" "Outer Beam Breakers" "Outer Beam Breakers" ...
+

Once you’ve set up the data structures that you want to loop over, +you can write out the nested loop itself. Since this is a complex loop +structure, it can be helpful to test the loop with set values of each +iterating variable (below these will be x and +y).

+

After writing out this loop but before running the full loop +structure, you should test the code inside of each loop layer. To do +this, you can set the values of each iterating variable outside of the +loop and then run the code inside of each loop. This form of testing is +equivalent to freezing the loop in time, so that you’re seeing the +output of the code for a single loop iteration (below, the first +iteration for each loop layer when x and y are +both set to 1).

+

To carry out this type of testing, you should run the code to freeze +the iterating variables on the first iteration of each loop. Then, you +should run the code inside of each loop, starting with the creation of +days_tmp, then the indexing and filtering the data frame, +and then the filtering of the file names. You should not run either of +the lines with lapply() in them, since you don’t want +either loop to fully execute before you’re sure that the inner code is +working as expected.

+

Important note: Above you learned that the iterating +variables in a loop do not exist outside of the loop. In this testing +that you will cary out, you’re not fully running either loop, and so the +code that you’re testing inside of each loop will “see” the values of +iterating variables that you initialized outside of the loops.

+

As you test the code inside of each loop, you should see that between +the outer and inner loops, you’re using the name of the sensor for the +given outer loop iteration (“RFID” for the first iteration) to set up +the days over which the inner loop will iterate (in +days_tmp). Inside of the inner loop, you’re filtering the +list of data frames by sensor, and then by the days that you want per +sensor. Finally, you’re indexing the file name for the right sensor type +and day with a combination of double and single bracket filtering on the +list of file names. Below, the lines of code that open and close each +loop are commented out in order to guide you through which lines of code +you should run in this testing step:

+
# Freezer the iterating variables for testing
+x <- 1
+y <- 1
+
+# The outer loop: start by iterating over sensors
+# invisible(lapply(1:length(sensors), function(x){
+  
+  # Index the named list of days to get the right days per sensor
+  # This indexing is important to set up the next loop correctly
+  sensors[x] # This is a string with the sensor name
+  
+  # Place the string with the sensor name inside of double square brackets to extract the vector of days for that sensor 
+  days_tmp <- days[[sensors[x]]]
+  
+  days_tmp
+  
+  # The inner loop: for each sensor, iterate over days
+  # lapply(1:length(days_tmp), function(y){
+    
+    # Get the data frame per sensor type using x inside of double square brackets to extract the given data frame from the list
+    dats[[x]] %>%
+      # Filter the data frame by one day at a time by using y to index the temporary vector of days (e.g. to extract a single element from that vector)
+      dplyr::filter(day == days_tmp[y]) %>% 
+      glimpse()
+    
+    # Use double bracket filtering to pull out the vector of file names for the given sensor from the overall list, then use y with single bracket filtering to pull a single file name from the resulting vector of names
+    files[[x]]
+    
+    files[[x]][y]
+    
+    # You'll also combine the file name with the right path:
+    file.path(file_dirs[[x]], files[[x]][y])
+    
+  # })
+  
+# }))
+

You should have a better sense now of how each loop operates over +different data structures to carry out the operation that you want +(writing out a single spreadsheet per sensor type and day). Next, you +can modify the full loop to replace the lines written for testing with +the final operations that you want to carry out:

+
# Start by iterating over sensors
+invisible(lapply(1:length(sensors), function(x){
+  
+  # Index the list of days to get the right days per sensor
+  # This indexing is important to set up the next loop correctly
+  days_tmp <- days[[sensors[x]]]
+  
+  # For the given sensor, iterate over days of data collection to write out a spreadsheet per sensor and day
+  lapply(1:length(days_tmp), function(y){
+    
+    # Get the data frame per sensor type using x and then filter by day using y
+    dats[[x]] %>%
+      # Filter the data frame by one day at a time
+      dplyr::filter(day == days_tmp[y]) %>%
+      # Use double-bracket filtering to pull out the right file path per sensor
+      # Then use double and single bracket filtering to pull out the right file name per sensor and day
+      write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE)
+    
+  })
+  
+}))
+

This loop should have created 1 file per sensor and day in the +correct directory per sensor. Check that these 6 files now exist inside +each directory per sensor within your working directory.

+
list.files(file.path(path, "Data/RFID"))
+
+list.files(file.path(path, "Data/IRBB"))
+

You learned more about filtering data frames, writing them out to +spreadsheets, and writing single-layer and nested loops in this +vignette. In the next vignette, you’ll use the spreadsheets of simulated +RFID and IRBB data to start the ABISSMAL data processing and analysis +workflow.

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Vignette_05_ProcessData_BuildPlots.html b/R/vignettes/Vignette_05_ProcessData_BuildPlots.html new file mode 100644 index 0000000..f55f5be --- /dev/null +++ b/R/vignettes/Vignette_05_ProcessData_BuildPlots.html @@ -0,0 +1,2282 @@ + + + + + + + + + + + + + + + +Vignette 05: Process and Plot Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Vignette overview and learning objectives +

+

In this fifth vignette, you will begin using the simulated detections +of animal movements in the ABISSMAL data processing and analysis +workflow, including combining raw data across days and pre-processing +the raw data. You will also make visualizations of the processed data. +You will continue to use coding skills that you learned in the previous +vignettes, and you will learn additional skills that include:

+
    +
  1. Accessing custom functions
  2. +
  3. Using custom functions
  4. +
  5. Making graphs with ggplot
  6. +
+

+Load packages and your working directory path +

+
rm(list = ls()) # Clean global environment
+
+library(tidyverse) # Load the set of tidyverse packages
+library(data.table) # Load other packages that the ABISSMAL functions require
+
+# Initialize an object with the path that is your working directory
+path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes"
+

+Load ABISSMAL functions +

+

The custom R functions available through ABISSMAL are stored in +physical files (extension .R) inside the local repository on your +computer (which you should have downloaded in vignette 01). In order to +start using the ABISSMAL functions, you need to load the physical .R +files so that the functions are available in your global environment. In +the code below, you will use the function source() to load +3 of the 5 main ABISSMAL functions, plus a script that holds a set of +utility functions:

+
# Load the function that combines raw data
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/combine_raw_data.R")
+
+# Load the function that detects perching events in the raw data
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_perching_events.R")
+
+# Load the function that pre-processes raw data
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/preprocess_detections.R")
+
+# Load a script with utility functions that each function above requires
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R")
+

+Access ABISSMAL function information +

+

After running the lines of code above, you should see that a whole +set of functions have been loaded into your global environment (check +the Environment pane). Many of these functions start with +check_, and those are utility functions. If you scroll +down, you’ll see that the three of the main ABISSMAL functions above +(combine_raw_data, detect_perching_events, +preprocess_detections) are all loaded in your global +environment as well. In the column to the right of the function names +you can also see a preview of each function’s arguments.

+

To get more information about each of these three main functions, you +can click the white square icon to the very right of each function in +the Environment pane, or run the code +View(function_name). This will open the script for the +given function a new tab in your Source pane. In each script for each +function, you’ll see lines of documentation starting with the symbols +“`# @”. You’ll see the function name and description first, and then a +description of each argument (parameter) for the function. If you keep +scrolling down, you’ll see a section with details about how the given +function works, and the information that it returns. After the lines of +documentation, you’ll see the code that makes up the function +itself.

+

+Combine raw data +

+

Once you’ve loaded the ABISSMAL functions, you can start using the +first function, combine_raw_data(), to combine data +collected across days per sensor into a single spreadsheet per sensor. +You’ll start by combining raw data for the RFID sensor collected over +different days into a single spreadsheet.

+

Here goes more information about the arguments that you are supplying +to the combine_raw_data() function below:

+
    +
  • sensors is a vector containing the labels of the +sensors for which you want to combine raw data. Below you’re specifying +RFID as a single sensor

  • +
  • path is your general working directory

  • +
  • data_dir is the folder that holds data inside of +your working directory

  • +
  • out_dir is the folder where you want to save the +combined raw data spreadsheet. The function will create this folder if +it does not already exist

  • +
  • tz is the timezone for converting timestamps to +POSIXct format. The default is “America/New York”, and you can check out +the “Time zones” section in the documentation for DateTimeClasses in R +for more information (?DateTimeClasses)

  • +
  • POSIXct_format is a string containing the POSIX +formatting information for how dates and timestamps should be combined +into a single column. The default is to encode the year as a 4 digit +number and the month and day as 2 digit numbers, separated by dashes. +The date is followed by a space, then the 2-digit hour, minute, and +decimal second (separated by colons)

  • +
+
# Combine raw data for the RFID and infrared beam breaker sensors separately
+combine_raw_data(sensors = "RFID", path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+

You can check that combine_raw_data() wrote a +spreadsheet of raw RFID data to the new directory +raw_combined:

+
list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$")
+
## [1] "combined_raw_data_IRBB.csv" "combined_raw_data_RFID.csv"
+

You can read the combined .csv file (called +“combined_raw_data_RFID.csv”) back into R to check out the structure of +this spreadsheet:

+
rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv"))
+
+glimpse(rfid_data)
+
## Rows: 42
+## Columns: 11
+## $ sensor_id          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ data_type          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ chamber_id         <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_0…
+## $ year               <int> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 202…
+## $ month              <int> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, …
+## $ day                <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, …
+## $ original_timestamp <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08…
+## $ timestamp_ms       <chr> "2023-08-01 10:00:00", "2023-08-01 10:05:00", "2023…
+## $ PIT_tag            <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabb…
+## $ data_stage         <chr> "raw_combined", "raw_combined", "raw_combined", "ra…
+## $ date_combined      <chr> "2024-04-07 2024-04-07 19:16:13.407951", "2024-04-0…
+

Reading this spreadsheet back into R created a data frame object. You +should be able to see that there are some new columns created by the +function, such as the column data_type. For this +spreadsheet, the columns sensor_id and +data_type contain the same information, but it’s useful to +have separate columns to keep track of the sensor id and type of sensor +used to collect data when multiple sensors are used (e.g. two beam +breaker pairs will each have a unique identifier in the +sensor_id column).

+

The function combine_raw_data() also created a new +timestamps column in POSIXct format for downsteam processing and +analysis, but it kept the original timestamps column. The function then +added columns to indicate the stage of data processing and the date that +the raw data were combined. Finally, if you check the folders with the +original raw RFID data, you’ll see that the original spreadsheets per +day were retained and were not overwritten.

+

You can also run combine_raw_data() with raw data from +multiple sensors by supplying a vector with the sensor labels to the +argument sensors. The function will still combine the raw +data per sensor into separate spreadsheets per sensor, and in the +process, you can avoid copy-pasting the code multiple times to run this +process for multiple sensors:

+
combine_raw_data(sensors = c("RFID", "IRBB"), path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+

The RFID data will be overwritten, and you should see an additional +spreadsheet with the raw IRBB data:

+
list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$")
+
## [1] "combined_raw_data_IRBB.csv" "combined_raw_data_RFID.csv"
+

+Detect perching events +

+

You can use the combined raw data from different sensors in +subsequent ABISSMAL functions to start making behavioral inferences from +the movement detection datasets. For instance, you can detect perching +events in the raw RFID data with the function +detect_perching_events(). You can read more about each +argument in the R script that contains this function.

+
detect_perching_events(file_nm = "combined_raw_data_RFID.csv", threshold = 2, run_length = 2, sensor_id_col_nm = "sensor_id", timestamps_col_nm = "timestamp_ms", PIT_tag_col_nm = "PIT_tag", rfid_label = "RFID", general_metadata_cols = c("chamber_id", "sensor_id"), path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", out_file_prefix = "perching_events", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+

detect_perching_events() can only operate on a single +file and sensor type at a time. The function will automatically create a +new folder called “processed”, and will save the .csv inside of that +folder with perching events (if these were detected using the temporal +threshold and run length above).

+

When we simulated data in the third and fourth vignettes, you +simulated perching events in the RFID dataset only. Did the code above +recover those perching events?

+
perching <- read.csv(file.path(path, "Data", "processed", "perching_events_RFID.csv"))
+
+glimpse(perching)
+
## Rows: 6
+## Columns: 11
+## $ chamber_id              <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "N…
+## $ sensor_id               <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID"
+## $ PIT_tag                 <chr> "1357aabbcc", "2468zzyyxx", "1357aabbcc", "246…
+## $ perching_start          <chr> "2023-08-01 08:00:00", "2023-08-01 11:30:00", …
+## $ perching_end            <chr> "2023-08-01 08:00:03", "2023-08-01 11:30:05", …
+## $ perching_duration_s     <int> 3, 5, 3, 5, 3, 5
+## $ unique_perching_event   <int> 1, 2, 3, 4, 5, 6
+## $ min_perching_run_length <int> 2, 2, 2, 2, 2, 2
+## $ threshold_s             <int> 2, 2, 2, 2, 2, 2
+## $ data_stage              <chr> "pre-processing", "pre-processing", "pre-proce…
+## $ date_preprocessed       <chr> "2024-04-07 2024-04-07 19:16:13.959793", "2024…
+

detect_perching_events() identified 6 total perching +events, which is the same number that you simulated in the previous +vignette (two perching events per day). You can look at the values +inside of data frame for more information about these perching +events:

+
# The timestamps when each perching event started
+perching$perching_start
+
## [1] "2023-08-01 08:00:00" "2023-08-01 11:30:00" "2023-08-02 08:00:00"
+## [4] "2023-08-02 11:30:00" "2023-08-03 08:00:00" "2023-08-03 11:30:00"
+
# The timestamps when each perching event ended
+perching$perching_end
+
## [1] "2023-08-01 08:00:03" "2023-08-01 11:30:05" "2023-08-02 08:00:03"
+## [4] "2023-08-02 11:30:05" "2023-08-03 08:00:03" "2023-08-03 11:30:05"
+
# The unique PIT tag identifier that tells you which individual was perched on the RFID antenna
+perching$PIT_tag
+
## [1] "1357aabbcc" "2468zzyyxx" "1357aabbcc" "2468zzyyxx" "1357aabbcc"
+## [6] "2468zzyyxx"
+

You can also view the whole data frame in a separate pane:

+
View(perching)
+

The information above tells you that there were 2 perching events +detected at 8:00 each day, and 2 perching events detected at 11:30 each +day (as expected). The PIT tag for each individual was detected once +each day, so there was 1 perching event performed by each individual on +each day.

+

Detecting perching events is not a requirement in the ABISSMAL +workflow, but it can be a useful step to obtain as much information from +the raw data before some detections are dropped during pre-processing +(see below).

+

+Pre-process raw data +

+

Once you’ve detected perching events in the raw data, you can move on +to pre-processing the raw data itself with +preprocess_detections(). The raw data can sometimes contain +multiple detections separated by a short period of time (e.g. RFID +detections when an individual is perching on the antenna), and these +multiple detections can be a source of noise when you’re trying to make +behavioral inferences across data collected by multiple sensors. When +the argument mode is set to “thin”, +preprocess_detections() removes detections separated by a +very short period of time (determined by the temporal threshold in +seconds passed to the argument thin_threshold), and retains +a reduced datasets of detections that still represents discrete movement +events.

+

preprocess_detections() also operates on a single sensor +type at a time:

+
preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+

You should now see an additional .csv file called +“pre_processed_data_RFID.csv” in the “processed” folder:

+
list.files(file.path(path, "Data/processed"))
+
## [1] "detection_clusters.csv"       "perching_events_RFID.csv"    
+## [3] "pre_processed_data_IRBB.csv"  "pre_processed_data_RFID.csv" 
+## [5] "scored_detectionClusters.csv"
+

You can read this file into R to check out the data structure. You +should see that there are fewer rows here compared to the spreadsheet of +raw data:

+
rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv"))
+
+glimpse(rfid_pp)
+
## Rows: 27
+## Columns: 11
+## $ data_type          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ chamber_id         <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_0…
+## $ year               <int> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 202…
+## $ month              <int> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, …
+## $ day                <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, …
+## $ timestamp_ms       <chr> "2023-08-01 08:00:00", "2023-08-01 08:00:02", "2023…
+## $ PIT_tag            <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabb…
+## $ sensor_id          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ thin_threshold_s   <int> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
+## $ data_stage         <chr> "pre-processed", "pre-processed", "pre-processed", …
+## $ date_pre_processed <chr> "2024-04-07 2024-04-07 19:16:14.288463", "2024-04-0…
+

Next, you can pre-process the raw beam breaker data and check out the +resulting .csv file:

+
preprocess_detections(sensor = "IRBB", timestamps_col_nm = "timestamp_ms", group_col_nm = "sensor_id", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+
+list.files(file.path(path, "Data/processed"))
+
## [1] "detection_clusters.csv"       "perching_events_RFID.csv"    
+## [3] "pre_processed_data_IRBB.csv"  "pre_processed_data_RFID.csv" 
+## [5] "scored_detectionClusters.csv"
+
irbb_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_IRBB.csv"))
+
+glimpse(irbb_pp)
+
## Rows: 66
+## Columns: 10
+## $ data_type          <chr> "IRBB", "IRBB", "IRBB", "IRBB", "IRBB", "IRBB", "IR…
+## $ chamber_id         <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_0…
+## $ year               <int> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 202…
+## $ month              <int> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, …
+## $ day                <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
+## $ timestamp_ms       <chr> "2023-08-01 06:05:04", "2023-08-01 06:05:05", "2023…
+## $ sensor_id          <chr> "Inner Beam Breakers", "Outer Beam Breakers", "Oute…
+## $ thin_threshold_s   <int> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
+## $ data_stage         <chr> "pre-processed", "pre-processed", "pre-processed", …
+## $ date_pre_processed <chr> "2024-04-07 2024-04-07 19:16:14.399455", "2024-04-0…
+

+Plot RFID data in a barcode plot +

+

Now that you’ve combined and processed the raw data per sensor, it’s +time to visualize these different datasets. Making visualizations as you +write code is important for generating high-quality figures for +publications and presentations, but also for checking your work.

+

In the code below, you’ll learn how to use functions from the +ggplot2 package to make a barcode style figure of the raw +and pre-processed RFID detection datasets.

+

Start by reading in the raw and pre-processed RFID data, as well as +the RFID perching events, and convert the timestamps to POSIX +format.

+
rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) %>%
+  # The timestamps must be converted to POSIX format every time that the data is read back into R for plotting
+  dplyr::mutate(
+    timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6"))
+  ) %>%
+  # Order the data frame by timestamps. The -desc() inside of arrange() means that the timestamps will be arranged in increasing order (less to more recent)
+  dplyr::arrange(-desc(timestamp_ms))
+
+# You should see that the timestamp_ms is in "dttm" format, which means that the POSIX conversion was done correctly
+glimpse(rfid_raw)
+
## Rows: 42
+## Columns: 11
+## $ sensor_id          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ data_type          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ chamber_id         <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_0…
+## $ year               <int> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 202…
+## $ month              <int> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, …
+## $ day                <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, …
+## $ original_timestamp <chr> "08:00:00", "08:00:01", "08:00:02", "08:00:03", "10…
+## $ timestamp_ms       <dttm> 2023-08-01 08:00:00, 2023-08-01 08:00:01, 2023-08-…
+## $ PIT_tag            <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabb…
+## $ data_stage         <chr> "raw_combined", "raw_combined", "raw_combined", "ra…
+## $ date_combined      <chr> "2024-04-07 2024-04-07 19:16:13.576772", "2024-04-0…
+
rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) %>%
+  # The timestamps must be converted to POSIX format every time that the data is read back into R for plotting
+  dplyr::mutate(
+    timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6"))
+  ) %>% 
+  dplyr::arrange(-desc(timestamp_ms))
+
+glimpse(rfid_pp)
+
## Rows: 27
+## Columns: 11
+## $ data_type          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ chamber_id         <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_0…
+## $ year               <int> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 202…
+## $ month              <int> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, …
+## $ day                <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, …
+## $ timestamp_ms       <dttm> 2023-08-01 08:00:00, 2023-08-01 08:00:02, 2023-08-…
+## $ PIT_tag            <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabb…
+## $ sensor_id          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ thin_threshold_s   <int> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
+## $ data_stage         <chr> "pre-processed", "pre-processed", "pre-processed", …
+## $ date_pre_processed <chr> "2024-04-07 2024-04-07 19:16:14.288463", "2024-04-0…
+
rfid_perch <- read.csv(file.path(path, "Data/processed/perching_events_RFID.csv")) %>%
+  # The start and end timestamps must be converted to POSIX format every time that the data is read back into R for plotting
+  dplyr::mutate(
+    perching_start = as.POSIXct(format(as.POSIXct(perching_start, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")),
+    perching_end = as.POSIXct(format(as.POSIXct(perching_end, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6"))
+  ) %>% 
+  dplyr::arrange(-desc(perching_start))
+
+glimpse(rfid_perch)
+
## Rows: 6
+## Columns: 11
+## $ chamber_id              <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "N…
+## $ sensor_id               <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID"
+## $ PIT_tag                 <chr> "1357aabbcc", "2468zzyyxx", "1357aabbcc", "246…
+## $ perching_start          <dttm> 2023-08-01 08:00:00, 2023-08-01 11:30:00, 2023…
+## $ perching_end            <dttm> 2023-08-01 08:00:03, 2023-08-01 11:30:05, 202…
+## $ perching_duration_s     <int> 3, 5, 3, 5, 3, 5
+## $ unique_perching_event   <int> 1, 2, 3, 4, 5, 6
+## $ min_perching_run_length <int> 2, 2, 2, 2, 2, 2
+## $ threshold_s             <int> 2, 2, 2, 2, 2, 2
+## $ data_stage              <chr> "pre-processing", "pre-processing", "pre-proc…
+## $ date_preprocessed       <chr> "2024-04-07 2024-04-07 19:16:13.959793", "2024…
+

Next, you can combine the original and pre-processed datasets into a +single data frame to facilitate combining them in the same plot. You +will add a new column (dataset) with labels in order to +identify the two different datasets.

+
rfid_combined <- rfid_raw %>%
+  dplyr::select(sensor_id, day, timestamp_ms) %>% 
+  dplyr::mutate(
+    dataset = "raw"
+  ) %>% 
+  bind_rows(
+    rfid_pp %>%
+      dplyr::select(sensor_id, day, timestamp_ms) %>% 
+      dplyr::mutate(
+        dataset = "pre-processed"
+      ) 
+  ) %>% 
+  dplyr::arrange(-desc(timestamp_ms))
+
+glimpse(rfid_combined)
+
## Rows: 69
+## Columns: 4
+## $ sensor_id    <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "…
+## $ day          <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
+## $ timestamp_ms <dttm> 2023-08-01 08:00:00, 2023-08-01 08:00:00, 2023-08-01 08:…
+## $ dataset      <chr> "raw", "pre-processed", "raw", "raw", "pre-processed", "r…
+

You will start plotting the data using functions from +ggplot2. This package is part of the +tidyverse, but can also be installed and used separately. +You can check out this link +for more resources to learn how to use ggplot notation to make different +types of plots. These resources include sections of three different +books with hands-on exercises at different levels, as well as an online +course and a webinar.

+

The ggplot2 package has a unique syntax for building +plots, in which you start making a plot by calling the function +ggplot(), and then add features by layering on other +ggplot2 functions with the + symbol.

+

If you call ggplot(), you’ll see that the function +immediately draws a blank plot in your Plots pane in RStudio.

+
ggplot()
+

+

The plot will still remain blank even when you supply information +about your data in order to set up plot aesthetics.

+
ggplot(data = rfid_combined)
+

+

You need to add other aesthetics functions to this base layer of the +plot in order to see your data. The functions that you use to layer +aesthetics over the empty plot will depend on the type of plot that you +want to make. For this example, you will make a barcode style plot, in +which each timestamp is shown as a thin vertical line. Barcode plots can +be useful visualizations when you’re working with timestamps, since the +most important information is contained in one dimension (time on the +x-axis). If you were to summarize the number of timestamps recorded on +each day, then you could create a line plot instead.

+

You can layer the function geom_segment() over the base +plot layer. geom_segment() allows you to add lines to a +plot, and the lines can communicate information in one or two dimensions +(width on the x-axis and height on the y-axis). In the plot you’ll make +below, you’ll use geom_segment() to add lines to the plot +that contain temporal information in one dimension (timestamps that +provide information on the x-axis only).

+
ggplot(data = rfid_combined) +
+  
+  # Add a vertical line for each timestamp
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  )
+

+

In the code above, geom_segment() adds a vertical line +segment to the plot for each detection in the full dataset. Using the +argument color inside of geom_segment(), you +told the function that these line segments should be colored by dataset +by supplying the column name that holds the dataset labels. The +color argument must be inside of the aes() +function (that controls the aesthetics for this layer of information) in +order for this color assignment by dataset to work correctly.

+

The line segment colors are automatically assigned by +ggplot using default colors, but you can change these +colors using the function scale_color_manual():

+
ggplot(data = rfid_combined) +
+  
+  # Add a vertical line for each timestamp
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  ) +
+  
+  scale_color_manual(values = c("orange", "darkgreen"))
+

+

The line segments are now colored with the new colors you specified. +But in the legend, the pre-processed data shows up first. If you want to +change the order of the dataset labels in the legend, and the colors +assigned to them, you can modify the dataset column in the +data frame that you used for plotting.

+

ggplot functions use a data type called +factors for automated encoding of aesthetics, such as the +color encoding you used above. Columns (or vectors) in factor format can +look like character type columns, but R stores the unique +values of each column as integers, and stores the unique character +values in a property called “levels”. You can change the order in which +values of a column are plotted by changing the order of these levels of +a factor column:

+
# Change the column dataset to data type "factor"
+# By specifying "raw" first in the argument levels, you are reordering the factor levels so that "raw" comes first 
+rfid_combined <- rfid_combined %>% 
+  dplyr::mutate(
+    dataset = factor(dataset, levels = c("raw", "pre-processed"))
+  )
+
+# The dataset column is now type "fct", or "factor"
+glimpse(rfid_combined)
+
## Rows: 69
+## Columns: 4
+## $ sensor_id    <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "…
+## $ day          <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
+## $ timestamp_ms <dttm> 2023-08-01 08:00:00, 2023-08-01 08:00:00, 2023-08-01 08:…
+## $ dataset      <fct> raw, pre-processed, raw, raw, pre-processed, raw, raw, pr…
+
# The levels of the factor column are ordered with "raw" first, rather than in alphabetical order
+levels(rfid_combined$dataset)
+
## [1] "raw"           "pre-processed"
+

After converting the dataset column to type “factor” +amnd reordering the levels of the unique character values in this +column, the unique values should appear in the correct order in the plot +legend (not in alphabetical order). You should also see that the colors +assigned to each dataset changed.

+
ggplot(data = rfid_combined) +
+  
+  # Add a vertical line for each timestamp
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  ) +
+  
+  scale_color_manual(values = c("orange", "darkgreen"))
+

+

In the plot that you just made, it’s very difficult to discriminate +between the lines for each dataset however. You can use the function +facet_wrap() to split up the two datasets into different +panels:

+
ggplot(data = rfid_combined) +
+  
+  # Add a vertical line for each timestamp
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  ) +
+  
+  scale_color_manual(values = c("orange", "darkgreen")) +
+  
+  # ~ is equivalent to "by", so that you're creating panels by the unique values in the column dataset
+  facet_wrap(~ dataset, nrow = 2, strip.position = "left")
+

+

By faceting this plot, you split up the datasets into different +panels but also lined them up on the x-axis so that it’s easier to +compare temporal patterns.

+

From this point of view though, it’s hard to see how the raw and +pre-processed datasets differ. You can filter the data with +tidyverse functions to plot the first 2 detections for each +of the raw and pre-processed datasets:

+
ggplot(data = rfid_combined %>%
+         # Group the data frame by each level in the column dataset
+         group_by(dataset) %>%
+         # For each unique dataset in the data frame, select the first two rows of data
+         slice(1:2) %>% 
+         ungroup()
+       ) +
+  
+  # Add a vertical line for each timestamp
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  ) +
+  
+  scale_color_manual(values = c("orange", "darkgreen")) +
+  
+  # ~ is equivalent to "by", so that you're creating panels by the unique values in the column dataset
+  facet_wrap(~ dataset, nrow = 2, strip.position = "left")
+

+

You should be able to see that the second timestamp in the raw data +was dropped from the pre-processed dataset (it was removed under the +temporal threshold used by preprocess_detections).

+

Next, you can add the perching data in the data frame +rfid_perch that you didn’t combine with the other two +datasets. This dataset doesn’t have a single timestamps column, but +rather has two columns that indicate the start and the end of each +perching event, respectively. You can add this dataset to the full plot +by using another geom_segment() layer. You’ll use +geom_segment() to add lines to the plot that contain +temporal information about when perching events started and ended.

+
ggplot(data = rfid_combined) +
+  
+  # Add a vertical line for each timestamp
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  ) +
+  
+  scale_color_manual(values = c("orange", "darkgreen")) +
+  
+  # ~ is equivalent to "by", so that you're creating panels by the unique values in the column dataset
+  facet_wrap(~ dataset, nrow = 2, strip.position = "left") +
+  
+  # Add the perching events to the plot
+  geom_segment(
+    data = rfid_perch,
+    aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5),
+    color = "blue",
+    linewidth = 0.3
+  )
+

+

In the code above for geom_segment(), you specified that +you wanted to add another dataset to the plot using the argument +data. The arguments x and y +determine where the beginning of each line segment is drawn on the x and +y-axes, respectively. You also need to specify where you want the line +segment to end on each axis. On the x-axis, by supplying the column name +perching_start to the argument x, and +perching_end to the argument xend, you’re +indicating that you want the line segment to start and end at the times +when perching was inferred to start and end. On the y-axis, the numbers +that you supplied to y and yend determined +where the line segments for perching events were drawn, which was just +above the other datasets. Note that the perching events were layered +over each facet of the plot by default.

+

There are some additional changes you can make to this plot to make +it more interpretable. You can change the position of the legend using +the argument legend.position inside of the general function +theme(). Below you will also save the plot inside of an +object, so that you don’t have to write out all of the code over and +over.

+
gg <- ggplot(data = rfid_combined) +
+  
+  # Add a vertical line for each timestamp
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  ) +
+  
+  scale_color_manual(values = c("orange", "darkgreen")) +
+  
+  # ~ is equivalent to "by", so that you're creating panels by the unique values in the column dataset
+  facet_wrap(~ dataset, nrow = 2, strip.position = "left") +
+  
+  # Add the perching events to the plot
+  geom_segment(
+    data = rfid_perch,
+    aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5),
+    color = "blue",
+    linewidth = 0.3
+  ) +
+  
+  theme(
+    legend.position = "top"
+  )
+
+gg
+

+

You can make some minor adjustments to the plot to make it easier on +the eyes, including changing the axis titles to be more informative, +changing the background to be white, and removing the y-axis text and +axis ticks. The y-axis does not contain information for interpretation +because the height of each segment does not reflect data that you want +to interpret.

+
gg <- gg +
+  
+  # Change the x and y axis title
+  xlab("Date and time") +
+  
+  # The y-axis does not contain information right now, so this title can be blank
+  ylab("") +
+  
+  # Use this function to convert the plot background to black and white
+  theme_bw() +
+  
+  # Use aesthetics functions to remove the y-axis text and ticks
+  theme(
+    axis.text.y = element_blank(),
+    axis.ticks.y = element_blank(),
+    legend.position = "top"
+  )
+
+gg
+

+

You can save plots that you make in R as physical image files. Below +you’ll use the function ggsave() to write the plot to an +image file on your computer.

+
gg
+

+
# Save the image file to your computer
+ggsave(file.path(path, "raw_processed_perching.tiff"), width = 8, height = 6, units = "in", dpi = 300)
+

You can continue to modify minor aesthetics to this image file to +create a high-quality figure for a publication. For instance, you can +change the final size of the image file (width, +height), as well as the resolution (dpi). You +can also change the size of the text on each axis and the axis titles, +or the legend position, as you play around with the final image +size.

+

In the next vignette, you will continue the ABISSMAL data analysis +pipeline and make a more complex and refined barcode style figure.

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Vignette_06_FinishAnalysisPipeline.html b/R/vignettes/Vignette_06_FinishAnalysisPipeline.html new file mode 100644 index 0000000..69d1f74 --- /dev/null +++ b/R/vignettes/Vignette_06_FinishAnalysisPipeline.html @@ -0,0 +1,2335 @@ + + + + + + + + + + + + + + + +Vignette 06: Finish Analysis Pipeline + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Vignette overview and learning objectives +

+

In this sixth and last vignette, you will finish using the simulated +detections of animal movements in the ABISSMAL data processing and +analysis workflow. You will detect clusters of detections that represent +distinct movement events, and then score behavioral inferences about the +movement events. You will also make plots to visualize these behavioral +inferences. You will continue to use coding skills that you learned in +the previous vignettes, and you will additionally learn how to build +more complex visualizations with ggplot.

+

+Load packages and your working directory path +

+
rm(list = ls()) # Clean global environment
+
+library(tidyverse) # Load the set of tidyverse packages
+library(data.table) # Load other packages that the ABISSMAL functions require
+
+# Initialize an object with the path that is your working directory
+path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes"
+

+Load ABISSMAL functions +

+
# Load the function that detects clusters in the pre-processed data
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_clusters.R")
+
+# Load the function that scores behavioral inferences about clusters
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/score_clusters.R")
+
+# Load a script with utility functions that each function above requires
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R")
+

+Finish the ABISSMAL pipeline +

+

Here you’ll use the ABISSMAL function detect_clusters() +to identify clusters of detections across sensor types (e.g. detections +from different sensors that were recorded close together in time).

+
# The run length needs to be set to 1 in order to correctly detect detection clusters of length 2
+detect_clusters(file_nms = c("pre_processed_data_RFID.csv", "pre_processed_data_IRBB.csv"), threshold = 2, run_length = 1, sensor_id_col_nm = "sensor_id", timestamps_col_nm = "timestamp_ms", PIT_tag_col_nm = "PIT_tag", rfid_label = "RFID", preproc_metadata_col_nms = c("thin_threshold_s", "data_stage", "date_pre_processed"), general_metadata_col_nms = c("chamber_id", "year", "month", "day"), path = path, data_dir = "Data/processed", out_dir = "Data/processed", out_file_nm = "detection_clusters.csv", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+

Next, you’ll score behavioral inferences about these detection +clusters with the function score_clusters().

+
score_clusters(file_nm = "detection_clusters.csv", rfid_label = "RFID", camera_label = NULL, outer_irbb_label = "Outer Beam Breakers", inner_irbb_label = "Inner Beam Breakers", video_metadata_col_nms = NULL, integrate_perching = TRUE, perching_dataset = "RFID", perching_prefix = "perching_events_", sensor_id_col_nm = "sensor_id", PIT_tag_col_nm = "PIT_tag", pixel_col_nm = NULL, video_width = NULL, video_height = NULL, integrate_preproc_video = FALSE, video_file_nm = NULL, timestamps_col_nm = NULL, path = path, data_dir = "Data/processed", out_dir = "Data/processed", out_file_nm = "scored_detectionClusters.csv", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+

+Check final results +

+

Now that you finished running the ABISSMAL pipeline to detect and +make inferences about movement events, you can check the final +results.

+
scored_clusters <- read.csv(file.path(path, "Data/processed", "scored_detectionClusters.csv")) %>% 
+  # The timestamps must be converted to POSIX format every time that the data is read back into R for plotting
+  dplyr::mutate(
+    start = as.POSIXct(format(as.POSIXct(start, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")),
+    end = as.POSIXct(format(as.POSIXct(end, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6"))
+  ) %>%
+  # Arrange the rows by increasing timestamps
+  dplyr::arrange(-desc(start))
+
+glimpse(scored_clusters)
+
## Rows: 33
+## Columns: 20
+## $ start                   <dttm> 2023-08-01 06:05:04, 2023-08-01 06:35:08, 202…
+## $ end                     <dttm> 2023-08-01 06:05:05, 2023-08-01 06:35:09, 202…
+## $ sensor_ids              <chr> "Inner Beam Breakers; Outer Beam Breakers", "O…
+## $ Edge_1                  <chr> "Inner Beam Breakers - Outer Beam Breakers", "…
+## $ Edge_2                  <chr> NA, NA, NA, NA, NA, "RFID - Inner Beam Breaker…
+## $ direction_scored        <chr> "exit", "entrance", "entrance", NA, NA, "entra…
+## $ direction_rule          <chr> "Inner Beam Breakers - Outer Beam Breakers", "…
+## $ indiv1_id               <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "135…
+## $ indiv2_id               <chr> "2468zzyyxx", "2468zzyyxx", "2468zzyyxx", "246…
+## $ total_indiv1_detections <int> NA, NA, NA, 2, NA, 1, 1, 1, 1, NA, 0, NA, NA, …
+## $ total_indiv2_detections <int> NA, NA, NA, 0, NA, 0, 0, 0, 0, NA, 3, NA, NA, …
+## $ individual_initiated    <chr> NA, NA, NA, "1357aabbcc", NA, "1357aabbcc", "1…
+## $ individual_ended        <chr> NA, NA, NA, "1357aabbcc", NA, "1357aabbcc", "1…
+## $ perching_sensor         <chr> NA, NA, NA, "RFID", NA, NA, NA, NA, NA, NA, "R…
+## $ perching_PIT_tag        <chr> NA, NA, NA, "1357aabbcc", NA, NA, NA, NA, NA, …
+## $ perching_start          <chr> NA, NA, NA, "2023-08-01 08:00:00", NA, NA, NA,…
+## $ perching_end            <chr> NA, NA, NA, "2023-08-01 08:00:03", NA, NA, NA,…
+## $ perching_duration_s     <int> NA, NA, NA, 3, NA, NA, NA, NA, NA, NA, 5, NA, …
+## $ data_stage              <chr> "integrated", "integrated", "integrated", "int…
+## $ date_processed          <chr> "2024-04-07 2024-04-07 19:16:49.488812", "2024…
+

How many entrances and exits were scored per day?

+

+Missing data in R +

+

In order to count the number of each of these events scored per day, +you need to know how to account for missing data in R. Missing data is +often indicated using the value NA or “not available”, +which is a specific type of logical value in R. You can figure out +whether a vector (or a column in a data frame) contains missing values +by using the function is.na(), which will return TRUE when +it finds an NA (missing) value in the given vector.

+
?is.na()
+
+x <- c(1, NA, 2, 3, NA)
+
+is.na(x)
+
## [1] FALSE  TRUE FALSE FALSE  TRUE
+

In the code above, you created a vector called x that +has 2 NA values. The function is.na() checks whether each +element of x is equivalent to NA, and returns +TRUE when that condition is met (when it finds missing +data).

+

Because is.na() is a conditional statement, you can also +use other special symbols relevant to conditional statements, such as +the ! symbol, which will invert a conditional statement. +For instance, in the code below, by adding ! before +is.na(), you’re now asking whether each element of +x is not equivalent to NA:

+
!is.na(x)
+
## [1]  TRUE FALSE  TRUE  TRUE FALSE
+

As you can see, adding the ! in front of +is.na() results in an inverted output compared to +is.na() alone, such that each value that was previously +TRUE is now FALSE. Together, the ability to +invert the is.na() conditional statement, plus the binary +output that is.na() returns, is really useful for finding +and filtering rows of a data frame.

+

For instance, the function dplyr::filter() will drop a +row whenever it encounters a value of FALSE in a given +column, and will retain a row whenever it encounters a value of +TRUE. If you want to drop rows that contain NA +values for a given column, you would add +!is.na(name_of_column) inside of +dplyr::filter(), which should return FALSE +every time it encounters a row with NA, and will remove +that row during filtering.

+

+Count events per day +

+

Now you can write code to count the number of entrance and exit +events scored per day. You will need to 1) make a new column with +information about the day per timestamp, 2) drop rows with missing data +for the direction scored (because this information cannot be scored for +some movements), 3) group the data frame by day and direction scored, +and then 4) count the number of rows per group.

+
scored_clusters %>%
+  # Extract the day from each timestamp and make a new column with this information
+  dplyr::mutate(
+    day = lubridate::day(start)
+  ) %>% 
+  # Here you're using the function is.na(), which will return TRUE when it finds an NA (missing) value in the given column. By placing the ! before is.na(), you're inverting the output, so that all TRUE values are converted to FALSE. As a result, dplyr::filter will drop all rows that return the value FALSE (e.g. all rows with missing values in the column direction_scored)
+  dplyr::filter(!is.na(direction_scored)) %>%
+  # Group the data frame by both columns for which you want to count rows (events). Here you want to count the number of entrances and exits (categories in the column direction_scored) per day (categories in the column day)
+  group_by(day, direction_scored) %>% 
+  # Then summarize the data: the number of rows here is the number of exits or entrances scored per day
+  dplyr::summarise(
+    n = n()
+  )
+
## `summarise()` has grouped output by 'day'. You can override using the `.groups`
+## argument.
+
## # A tibble: 6 × 3
+## # Groups:   day [3]
+##     day direction_scored     n
+##   <int> <chr>            <int>
+## 1     1 entrance             4
+## 2     1 exit                 4
+## 3     2 entrance             4
+## 4     2 exit                 4
+## 5     3 entrance             4
+## 6     3 exit                 4
+

Four entrance and exit events were scored per day. Does this line up +with the anticipated number of exits and entrances per day? If you go +back to the code where you created the simulated raw datasets in +vignettes 03 and 04, you should see that you started by simulating 2 +entrances and exits per day across the RFID and beam breaker datasets. +Then, you added 2 more entrance and 2 more exit events per day when you +simulated RFID detection failures (e.g. movements captured by the beam +breakers only). score_clusters() detected the correct +number of entrances and exits per day.

+

Next, check out the perching events. Start by filtering out all rows +that were not scored as perching events (rows that have NA values in the +column “perching_PIT_tag”). Then, select only the columns that have +information that is useful to check: the PIT tag codes, as well as the +start and end of each perching event.

+
scored_clusters %>% 
+  # Use a conditional statement with is.na() to retain only rows that have PIT tag codes associated with perching events
+  dplyr::filter(!is.na(perching_PIT_tag)) %>%
+  # Then select only the columns that you want to check visually
+  dplyr::select(start, end, perching_PIT_tag)
+
##                 start                 end perching_PIT_tag
+## 1 2023-08-01 08:00:00 2023-08-01 08:00:02       1357aabbcc
+## 2 2023-08-01 11:30:00 2023-08-01 11:30:04       2468zzyyxx
+## 3 2023-08-02 08:00:00 2023-08-02 08:00:02       1357aabbcc
+## 4 2023-08-02 11:30:00 2023-08-02 11:30:04       2468zzyyxx
+## 5 2023-08-03 08:00:00 2023-08-03 08:00:02       1357aabbcc
+## 6 2023-08-03 11:30:00 2023-08-03 11:30:04       2468zzyyxx
+

As specified in vignettes 03 and 04, the first perching event each +day was performed by the first individual (PIT tag “1357aabbcc”), and +the second was attributed to the second individual (PIT tag +“2468zzyyxx”).

+

How many movement events that were not perching events were +attributed to each individual?

+
scored_clusters %>%
+  # Extract the day from each timestamp and make a new column with this information
+  dplyr::mutate(
+    day = lubridate::day(start)
+  ) %>%
+  # Use a conditional statement with is.na() to retain only rows that have PIT tag codes not associated with perching events
+  # Here you're combining two conditional statements, which allows you to search for rows in the column individual_initiated that have PIT tag codes, but that also do not have a sensor label in the column perching_sensor (e.g. movement events that were not perching events)
+  dplyr::filter(!is.na(individual_initiated) & is.na(perching_sensor)) %>%
+  group_by(day, individual_initiated) %>% 
+  dplyr::summarise(
+    n = n()
+  )
+
## `summarise()` has grouped output by 'day'. You can override using the `.groups`
+## argument.
+
## # A tibble: 3 × 3
+## # Groups:   day [3]
+##     day individual_initiated     n
+##   <int> <chr>                <int>
+## 1     1 1357aabbcc               4
+## 2     2 1357aabbcc               4
+## 3     3 1357aabbcc               4
+

As you can see, 4 movement events that were not perching events were +attributed to the first individual (PIT tag code 1357aabbcc) on each +day. This is exactly what we expected when creating the simulated +dataset (see vignettes 03 and 04). More non-perching movement events +were detected across these days, but those events were not always +captured by the RFID antenna (e.g. the simulated RFID detection failures +that were captured by the beam breakers only). The beam breakers +captured those movement events but cannot record individual +identity.

+

+Build complex barcode plot +

+

Now that you checked out the final results, you can visualize them. +In the code below, you’ll learn how to build a barcode plot that is more +complex but also easier to interpret than the plot that you made in +vignette 05. For this plot, it would be helpful to visualize 3 types of +behavioral inferences or types of information over time: the direction +of movement (when available), the identity of the individual (when +available), and perching events.

+

You can start building the plot by adding vertical line segments for +non-perching movement events. The color of each line will indicate the +individual identity, or whether individual identity could not be +assigned. The line type of each line will indicate the direction of +movement, or if direction could not be scored.

+

In order to encode colors and line types correctly in the plot, you +need to modify the data frame of final results to convert NAs in the two +associated columns to more meaningful information. For instance, when +individual identity is missing, it would be useful to convert the +associated NA value to “unassigned”. In the code below, you will use the +function is.na() inside of ifelse() +conditional statements to change the NA values inside the columns +“individual_initiated” and “direction_scored” to more useful information +for plotting purposes.

+

First you can practice using is.na() inside of +ifelse() to create a new vector. In the code below, you’re +providing the conditional statement that you want to test (here you’re +testing whether the column holding the PIT tag code of the individual +that initiated a movement has NAs), the value you want to add to the +vector if the condition is true (“unassigned” when no PIT tag code is +present), and then the value that you want to add if the condition is +false (return the PIT tag code in the individual_initiated column if the +given value is not NA).

+
# is.na() returns TRUE when it encounters NAs inside of a vector (or column), and FALSE when a value inside of the vector is not NA
+is.na(scored_clusters$individual_initiated)
+
##  [1]  TRUE  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE
+## [13]  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE  TRUE
+## [25]  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE
+
# All NAs in this column are converted to "unassigned", but all other values were not changed
+ifelse(test = is.na(scored_clusters$individual_initiated), yes = "unassigned", no = scored_clusters$individual_initiated)
+
##  [1] "unassigned" "unassigned" "unassigned" "1357aabbcc" "unassigned"
+##  [6] "1357aabbcc" "1357aabbcc" "1357aabbcc" "1357aabbcc" "unassigned"
+## [11] "2468zzyyxx" "unassigned" "unassigned" "unassigned" "1357aabbcc"
+## [16] "unassigned" "1357aabbcc" "1357aabbcc" "1357aabbcc" "1357aabbcc"
+## [21] "unassigned" "2468zzyyxx" "unassigned" "unassigned" "unassigned"
+## [26] "1357aabbcc" "unassigned" "1357aabbcc" "1357aabbcc" "1357aabbcc"
+## [31] "1357aabbcc" "unassigned" "2468zzyyxx"
+

Now you can use is.na() inside of ifelse +statements to modify columns in the data frame.

+
scored_clusters_gg <- scored_clusters %>% 
+  dplyr::mutate(
+    # If this column has an NA value, then convert it to "unassigned"
+    # Else, do not change the given value
+    individual_initiated = ifelse(is.na(individual_initiated), "unassigned", individual_initiated),
+    # Repeat this process for the direction_scored column but with a different value
+    # Also, in the conditional statement below, you added is.na(perching_sensor) (after the & symbol) to only convert NA values in direction_scored when they were also not labeled as perching events in the column perching_sensor
+    direction_scored = ifelse(is.na(direction_scored) & is.na(perching_sensor), "not scored", direction_scored)
+  ) %>% 
+  # Next, you'll convert each of these columns to type factor and arrange the levels in order for plotting (for example, the valures "unassigned" and "not scored" should come last in the legend)
+  dplyr::mutate(
+    individual_initiated = factor(individual_initiated, levels = c("1357aabbcc", "2468zzyyxx", "unassigned")),
+    direction_scored = factor(direction_scored, levels = c("entrance", "exit", "not scored"))
+  )
+
+# Check the resulting changes using the function distinct() to see all of the unique values for both columns that were modified
+# The NA values in the direction_scored column are expected since these refer to perching events
+scored_clusters_gg %>% 
+  distinct(individual_initiated, direction_scored)
+
##   individual_initiated direction_scored
+## 1           unassigned             exit
+## 2           unassigned         entrance
+## 3           1357aabbcc             <NA>
+## 4           unassigned       not scored
+## 5           1357aabbcc         entrance
+## 6           1357aabbcc             exit
+## 7           2468zzyyxx             <NA>
+

This output looks good. You should see NA values in this +data frame, but they’re in the column “direction_scored” and associated +with the eprching events, which you’ll be adding to the plot in a +separate layer of code later on.

+

You can use this modified data frame to build the plot. In the code +below, you’ll add lines colored by individual identity and with line +types encoding the direction of movement. First you’ll specify the plot +aesthetics:

+
# Colors will be encoded in the same order as the levels of the column individual_initiated, so orange will encode "1357aabbcc"
+levels(scored_clusters_gg$individual_initiated)
+
## [1] "1357aabbcc" "2468zzyyxx" "unassigned"
+
cols <- c("orange", "darkgreen", "black")
+
+# Line types will be encoded in the same order as the levels of the column direction_scored, so dotted will encode "not scored"
+levels(scored_clusters_gg$direction_scored)
+
## [1] "entrance"   "exit"       "not scored"
+
ltys <- c("solid", "longdash", "dotted")
+

Then you can add lines to the plot by individual identity. Here, +you’re splitting up the geom_segment() calls by unique +values in the individual_initiated column. You only added segments for +the first individual and all non-perching events that were not assigned +to an individual, because from checking the results above, you know that +no non-perching events were assigned to the second individual.

+
ggplot() +
+  
+  # Add a vertical line for each non-perching event assigned to the first individual
+  geom_segment(
+    data = scored_clusters_gg %>% 
+      dplyr::filter(individual_initiated == "1357aabbcc"),
+    aes(x = start, y = 0, xend = end, yend = 0.5, linetype = direction_scored),
+    color = "orange",
+    linewidth = 0.5
+  ) +
+  
+  # Add a vertical line for each non-perching event that was not assigned to either individual
+  geom_segment(
+    data = scored_clusters_gg %>% 
+      dplyr::filter(individual_initiated == "unassigned"),
+    aes(x = start, y = 0.6, xend = end, yend = 1.1, linetype = direction_scored),
+    color = "black",
+    linewidth = 0.5
+  ) +
+  
+  # Add the custom linetype values to this plot
+  scale_linetype_manual(values = ltys) +
+  
+  # Remove the y-axis title
+  ylab("") +
+  
+  # Use this function to convert the plot background to black and white
+  theme_bw() +
+  
+  # Use aesthetics functions to remove the y-axis text and ticks
+  # Add an argument to change where the legend is located in the plot
+  theme(
+    axis.text.y = element_blank(),
+    axis.ticks.y = element_blank(),
+    legend.position = "top"
+  )
+

+

To make this plot, you vertically offset the line segments for the +first individual and those that were unassigned. This vertical offset +makes it easier to visually compare patterns over time. You created this +vertical offset by changing the values used for y and +yend in the second geom_segment() layer, so +that those line segments would start higher than the top of the first +set of segments.

+

You can make some additional modifications that would help make this +plot easier to interpret. First, the panel or facet labels on the left +could be changed from the date to the day of data collection (e.g. “Day +1”). The x-axis labels could also be changed to show only the time of +day (remove the month and day), and there could also be more labels +available (e.g. a label each half hour).

+

You can start by modifying the data frame to add information about +the day of data collection. You’ll create this new column by relying on +conditional statements with the ifelse() function, since +there are only 3 days of data collection that need recoding.

+
# Create a new column in the raw data for the date of data collection
+scored_clusters_gg2 <- scored_clusters_gg %>% 
+  # First you need to create a column with information about the day
+  dplyr::mutate(
+    day = lubridate::day(start)
+  ) %>% 
+  dplyr::mutate(
+    # Then recode the label for each day and save this in a new column
+    day_label = ifelse(day == 1, "Day 1", day), # Here the last argument is day because the column day_label does not exist yet
+    day_label = ifelse(day == 2, "Day 2", day_label),
+    day_label = ifelse(day == 3, "Day 3", day_label)
+  )
+
+# Looks good  
+glimpse(scored_clusters_gg2)
+
## Rows: 33
+## Columns: 22
+## $ start                   <dttm> 2023-08-01 06:05:04, 2023-08-01 06:35:08, 202…
+## $ end                     <dttm> 2023-08-01 06:05:05, 2023-08-01 06:35:09, 202…
+## $ sensor_ids              <chr> "Inner Beam Breakers; Outer Beam Breakers", "O…
+## $ Edge_1                  <chr> "Inner Beam Breakers - Outer Beam Breakers", "…
+## $ Edge_2                  <chr> NA, NA, NA, NA, NA, "RFID - Inner Beam Breaker…
+## $ direction_scored        <fct> exit, entrance, entrance, NA, not scored, entr…
+## $ direction_rule          <chr> "Inner Beam Breakers - Outer Beam Breakers", "…
+## $ indiv1_id               <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "135…
+## $ indiv2_id               <chr> "2468zzyyxx", "2468zzyyxx", "2468zzyyxx", "246…
+## $ total_indiv1_detections <int> NA, NA, NA, 2, NA, 1, 1, 1, 1, NA, 0, NA, NA, …
+## $ total_indiv2_detections <int> NA, NA, NA, 0, NA, 0, 0, 0, 0, NA, 3, NA, NA, …
+## $ individual_initiated    <fct> unassigned, unassigned, unassigned, 1357aabbcc…
+## $ individual_ended        <chr> NA, NA, NA, "1357aabbcc", NA, "1357aabbcc", "1…
+## $ perching_sensor         <chr> NA, NA, NA, "RFID", NA, NA, NA, NA, NA, NA, "R…
+## $ perching_PIT_tag        <chr> NA, NA, NA, "1357aabbcc", NA, NA, NA, NA, NA, …
+## $ perching_start          <chr> NA, NA, NA, "2023-08-01 08:00:00", NA, NA, NA,…
+## $ perching_end            <chr> NA, NA, NA, "2023-08-01 08:00:03", NA, NA, NA,…
+## $ perching_duration_s     <int> NA, NA, NA, 3, NA, NA, NA, NA, NA, NA, 5, NA, …
+## $ data_stage              <chr> "integrated", "integrated", "integrated", "int…
+## $ date_processed          <chr> "2024-04-07 2024-04-07 19:16:49.488812", "2024…
+## $ day                     <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2…
+## $ day_label               <chr> "Day 1", "Day 1", "Day 1", "Day 1", "Day 1", "…
+
scored_clusters_gg2 %>% 
+  distinct(day_label)
+
##   day_label
+## 1     Day 1
+## 2     Day 2
+## 3     Day 3
+

Now you can update the code to include the new date labels:

+
# Add the data frame as the default dataset for the base layer plot, so that the facet_wrap() layer below has data to plot
+ggplot(data = scored_clusters_gg2) +
+  
+  # Add a vertical line for each non-perching event assigned to the first individual
+  geom_segment(
+    data = scored_clusters_gg2 %>% 
+      dplyr::filter(individual_initiated == "1357aabbcc"),
+    aes(x = start, y = 0, xend = end, yend = 0.5, linetype = direction_scored),
+    color = "orange",
+    linewidth = 0.5
+  ) +
+  
+  # Add a vertical line for each non-perching event that was not assigned to either individual
+  geom_segment(
+    data = scored_clusters_gg2 %>% 
+      dplyr::filter(individual_initiated == "unassigned"),
+    aes(x = start, y = 0.6, xend = end, yend = 1.1, linetype = direction_scored),
+    color = "black",
+    linewidth = 0.5
+  ) +
+  
+  # Add the custom linetype values to this plot
+  scale_linetype_manual(values = ltys) +
+  
+  # Remove the y-axis title
+  ylab("") +
+  
+  # Use this function to convert the plot background to black and white
+  theme_bw() +
+  
+  # Use aesthetics functions to remove the y-axis text and ticks
+  # Add an argument to change where the legend is located in the plot
+  theme(
+    axis.text.y = element_blank(),
+    axis.ticks.y = element_blank(),
+    legend.position = "top"
+  ) +
+  
+  # Facet the plot by day (e.g. create a panel per day)
+  # Use the new day labels here
+  facet_wrap(~ day_label, nrow = 3, strip.position = "left")
+

+

Now that information about the day of data collection has been moved +to the facet labels, you need to fix the x-axis labels. The plot will be +more interpretable if you can line up the timestamps by hour and minute +for a direct comparison across days.

+

To line up the timestamps across days, you will need to update the +format of the columns that hold timestamps. The code to convert the +timestamps to a different format is nested and repetitive, but the +timestamp conversion will be performed correctly. When you prompt the R +to convert the timestamps to hours, minutes, and seconds only, R adds a +default year, month, and day beforehand (likely the current date that +you rant the code). This is expected, and it is not an error, but rather +makes it possible for the timestamps to align correctly over days in the +plot (since R sees all timestamps occurring on a single day).

+
scored_clusters_gg3 <- scored_clusters_gg2 %>% 
+  dplyr::mutate(
+    start_gg = as.POSIXct(strptime(format(as.POSIXct(start), "%H:%M:%S"), format = "%H:%M:%S")),
+    end_gg = as.POSIXct(strptime(format(as.POSIXct(end), "%H:%M:%S"), format = "%H:%M:%S"))
+  )
+  
+# You'll see that a new year, month, and day were appended to the updated timestamps, but this is expected (see above)
+glimpse(scored_clusters_gg3)
+
## Rows: 33
+## Columns: 24
+## $ start                   <dttm> 2023-08-01 06:05:04, 2023-08-01 06:35:08, 202…
+## $ end                     <dttm> 2023-08-01 06:05:05, 2023-08-01 06:35:09, 202…
+## $ sensor_ids              <chr> "Inner Beam Breakers; Outer Beam Breakers", "O…
+## $ Edge_1                  <chr> "Inner Beam Breakers - Outer Beam Breakers", "…
+## $ Edge_2                  <chr> NA, NA, NA, NA, NA, "RFID - Inner Beam Breaker…
+## $ direction_scored        <fct> exit, entrance, entrance, NA, not scored, entr…
+## $ direction_rule          <chr> "Inner Beam Breakers - Outer Beam Breakers", "…
+## $ indiv1_id               <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "135…
+## $ indiv2_id               <chr> "2468zzyyxx", "2468zzyyxx", "2468zzyyxx", "246…
+## $ total_indiv1_detections <int> NA, NA, NA, 2, NA, 1, 1, 1, 1, NA, 0, NA, NA, …
+## $ total_indiv2_detections <int> NA, NA, NA, 0, NA, 0, 0, 0, 0, NA, 3, NA, NA, …
+## $ individual_initiated    <fct> unassigned, unassigned, unassigned, 1357aabbcc…
+## $ individual_ended        <chr> NA, NA, NA, "1357aabbcc", NA, "1357aabbcc", "1…
+## $ perching_sensor         <chr> NA, NA, NA, "RFID", NA, NA, NA, NA, NA, NA, "R…
+## $ perching_PIT_tag        <chr> NA, NA, NA, "1357aabbcc", NA, NA, NA, NA, NA, …
+## $ perching_start          <chr> NA, NA, NA, "2023-08-01 08:00:00", NA, NA, NA,…
+## $ perching_end            <chr> NA, NA, NA, "2023-08-01 08:00:03", NA, NA, NA,…
+## $ perching_duration_s     <int> NA, NA, NA, 3, NA, NA, NA, NA, NA, NA, 5, NA, …
+## $ data_stage              <chr> "integrated", "integrated", "integrated", "int…
+## $ date_processed          <chr> "2024-04-07 2024-04-07 19:16:49.488812", "2024…
+## $ day                     <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2…
+## $ day_label               <chr> "Day 1", "Day 1", "Day 1", "Day 1", "Day 1", "…
+## $ start_gg                <dttm> 2024-04-07 06:05:04, 2024-04-07 06:35:08, 202…
+## $ end_gg                  <dttm> 2024-04-07 06:05:05, 2024-04-07 06:35:09, 202…
+

Now you can update the plotting code to change the aesthetics of the +x-axis using the function scale_x_datetime() to specify +that you want x-axis labels every half hour. Also add an x-axis title, +and remove the y-axis grid lines inside of each panel:

+
gg <- ggplot(data = scored_clusters_gg3) +
+  
+  # Add a vertical line for each non-perching event assigned to the first individual
+  geom_segment(
+    data = scored_clusters_gg3 %>% 
+      dplyr::filter(individual_initiated == "1357aabbcc"),
+    aes(x = start_gg, y = 0, xend = end_gg, yend = 0.5, linetype = direction_scored),
+    color = "orange",
+    linewidth = 0.5
+  ) +
+  
+  # Add a vertical line for each non-perching event that was not assigned to either individual
+  geom_segment(
+    data = scored_clusters_gg3 %>% 
+      dplyr::filter(individual_initiated == "unassigned"),
+    aes(x = start_gg, y = 0.6, xend = end_gg, yend = 1.1, linetype = direction_scored),
+    color = "black",
+    linewidth = 0.5
+  ) +
+  
+  # Add the custom linetype values to this plot
+  scale_linetype_manual(values = ltys) +
+  
+  # Remove the y-axis title
+  ylab("") +
+  
+  # Use this function to convert the plot background to black and white
+  theme_bw() +
+  
+  # Use aesthetics functions to remove the y-axis text and ticks
+  # Add an argument to change where the legend is located in the plot
+  theme(
+    axis.text.y = element_blank(),
+    axis.ticks.y = element_blank(),
+    legend.position = "top"
+  ) +
+  
+  # Facet the plot by day (e.g. create a panel per day)
+  # Use the new day labels here
+  facet_wrap(~ day_label, nrow = 3, strip.position = "left") +
+  
+  # Change the aesthetics of the x-axis text
+  scale_x_datetime(
+    date_breaks = "30 mins",
+    date_labels = "%H:%M"
+  ) +
+  
+  # Add an x-axis title
+  xlab("Time of day (HH:MM)") +
+  
+  # Remove the y-axis grid lines (major and minor) inside of each panel
+  theme(
+    panel.grid.major.y = element_blank(),
+    panel.grid.minor.y = element_blank()
+  ) 
+
+gg
+

+

There’s still one more important piece of information to add to the +plot: the perching events. You’ll add these using +geom_segment(), but you’ll make these line segments short +and thick, so that they appear more like dots on the plot:

+
gg <- gg + 
+  
+  # Add the perching events as rounded segments, and now encode color through the column individual_initiated
+  geom_segment(
+    data = scored_clusters_gg3 %>% 
+      dplyr::filter(!is.na(perching_sensor)),
+    aes(x = start_gg, y = 1.2, xend = end_gg, yend = 1.2, color = individual_initiated),
+    linewidth = 2, lineend = "round"
+  ) +
+  
+  # Add the custom colors. These colors also apply to the lines that represent non-perching events that you added in previous geom_segement() layers
+  scale_color_manual(values = cols)
+
+gg
+

+

You should see that a color legend appears at the top of the plot +with the addition of the new geom_segment() layer. Both +legends are a little messy. You can improve the plot by updating the +title of each legend, increasing the size of the legend text, and +reducing the white space between the plot and the legends. To modify the +legend titles, you’ll rely on the functions guides() and +guide_legend(). To increase the legend text size, you’ll +use the argument legend.text inside of the function +theme(). To reduce white space between the plot and +legends, you’ll use the argument legend.margin inside of +the function theme().

+
# Check out more information about the function used to control margins (white space) around the legend
+?margin
+
+gg <- gg +
+  
+  # Increase the legend text size and reduce white space between the plot and legends
+  theme(
+    legend.text = element_text(size = 10),
+    legend.margin = margin(-1, -1, -1, -1, unit = "pt")
+  ) +
+
+  # Change the legend titles
+  guides(
+    linetype = guide_legend(title = "Direction"),
+    color = guide_legend(title = "Individual")
+  )
+
+gg
+

+

Finally, you can save this plot as an image file:

+
gg
+

+
# Save the image file to your computer
+ggsave(file.path(path, "behavioral_inferences.tiff"), width = 8, height = 6, units = "in", dpi = 300)
+

You can continue to modify minor aesthetics to this image file to +create a high-quality figure for a publication. For instance, you can +change the final size of the image file (width, +height), as well as the resolution (dpi). You +can also change the size of the text on each axis and the axis titles, +or the legend position, as you play around with the final image +size.

+

This is the all of the code used for the final plot, condensed and +reorganized:

+
ggplot(data = scored_clusters_gg3) +
+  
+  # Add a vertical line for each non-perching event assigned to the first individual
+  geom_segment(
+    data = scored_clusters_gg3 %>% 
+      dplyr::filter(individual_initiated == "1357aabbcc"),
+    aes(x = start_gg, y = 0, xend = end_gg, yend = 0.5, linetype = direction_scored),
+    color = "orange",
+    linewidth = 0.5
+  ) +
+  
+  # Add a vertical line for each non-perching event that was not assigned to either individual
+  geom_segment(
+    data = scored_clusters_gg3 %>% 
+      dplyr::filter(individual_initiated == "unassigned"),
+    aes(x = start_gg, y = 0.6, xend = end_gg, yend = 1.1, linetype = direction_scored),
+    color = "black",
+    linewidth = 0.5
+  ) +
+  
+  # Add the perching events as rounded segments, and now encode color through the column individual_initiated
+  geom_segment(
+    data = scored_clusters_gg3 %>% 
+      dplyr::filter(!is.na(perching_sensor)),
+    aes(x = start_gg, y = 1.2, xend = end_gg, yend = 1.2, color = individual_initiated),
+    linewidth = 2, lineend = "round"
+  ) +
+  
+  # Add the custom linetype values to this plot
+  scale_linetype_manual(values = ltys) +
+  
+  # Add the custom colors
+  scale_color_manual(values = cols) +
+  
+  # Change the legend titles
+  guides(
+    linetype = guide_legend(title = "Direction"),
+    color = guide_legend(title = "Individual")
+  ) +
+  
+  # Add an x-axis title
+  xlab("Time of day (HH:MM)") +
+  
+  # Remove the y-axis title
+  ylab("") +
+  
+  # Change the aesthetics of the x-axis text
+  scale_x_datetime(
+    date_breaks = "30 mins",
+    date_labels = "%H:%M"
+  ) +
+  
+  # Facet the plot by day (e.g. create a panel per day)
+  # Use the new day labels here
+  facet_wrap(~ day_label, nrow = 3, strip.position = "left") +
+  
+  # Use this function to convert the plot background to black and white
+  theme_bw() +
+  
+  # Use aesthetics functions to remove the y-axis text and ticks
+  # Add an argument to change where the legend is located in the plot
+  # Remove the y-axis grid lines (major and minor) inside of each panel
+  # Increase the legend text size and reduce white space between the plot and legend
+  theme(
+    axis.text.y = element_blank(),
+    axis.ticks.y = element_blank(),
+    legend.position = "top",
+    panel.grid.major.y = element_blank(),
+    panel.grid.minor.y = element_blank(),
+    legend.text = element_text(size = 10),
+    legend.margin = margin(-1, -1, -1, -1, unit = "pt")
+  )
+

You completed the final vignette of the ABISSMAL data processing and +analysis pipeline. You also practiced your coding and data science +skills in a biological context. Well done! It would be very helpful if +you could complete the brief Google form for a post-vignette evaluation +that will help us continue to improve these vignettes over time. A link +to this Google form will be available in the README file for the +vignettes.

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + From 32cc23107264eb08beba6a0d5a064430cf2d454f Mon Sep 17 00:00:00 2001 From: lastralab Date: Fri, 26 Apr 2024 12:02:15 -0400 Subject: [PATCH 63/69] [PCT-472] edited Tutorial 01 and added language to the html tag in html files --- R/vignettes/README.md | 20 ++++---- R/vignettes/Tutorial_01_Introduccion.Rmd | 48 +++++++++---------- R/vignettes/Tutorial_01_Introduccion.html | 8 ++-- R/vignettes/Tutorial_02_Configuracion.Rmd | 2 +- R/vignettes/Tutorial_02_Configuracion.html | 4 +- R/vignettes/Tutorial_03_SimularDatos.html | 2 +- R/vignettes/Tutorial_04_GuardarDatos.html | 2 +- ...torial_05_ProcesarDatos_CrearGraficas.html | 2 +- .../Tutorial_06_FinalizarAnalisis.html | 2 +- R/vignettes/Vignette_01_Introduction.html | 2 +- R/vignettes/Vignette_02_Setup.html | 2 +- R/vignettes/Vignette_03_SimulateData.html | 2 +- R/vignettes/Vignette_04_SaveData.html | 2 +- .../Vignette_05_ProcessData_BuildPlots.html | 2 +- .../Vignette_06_FinishAnalysisPipeline.html | 2 +- R/vignettes/styles.css | 2 +- 16 files changed, 52 insertions(+), 52 deletions(-) diff --git a/R/vignettes/README.md b/R/vignettes/README.md index d0a3182..929ec83 100644 --- a/R/vignettes/README.md +++ b/R/vignettes/README.md @@ -9,7 +9,7 @@ The goal of these vignettes is to disseminate R coding skills in a biological co 1. An introduction to RStudio that includes downloading the ABISSMAL GitHub repository, an introduction to the ABISSMAL data processing and analysis pipeline, and information about troubleshooting errors and reporting bugs -2. Learn how to set up a virtual workspace, including cleaning your global environment, running code inside of RMarkdown files, package installation, keyboard shortcuts, creating and specifying a working directory +2. Learn how to set up a virtual workspace, including cleaning your global environment, running code inside RMarkdown files, package installation, keyboard shortcuts, creating and specifying a working directory 3. Create simulated datasets of animal movements while learning how to create objects in R, different data types in R, and how to manipulate objects with conditional statements and piping expressions @@ -26,20 +26,20 @@ After completing the vignettes, it would be very helpful if you could complete t

Resumen

-Nuestro objetivo es diseminar habilidades de programación en R en un contexto biológico a través del sistema de ABISSMAL. Tenemos seis tutoriales que cubren los siguientes temas: +Nuestro objetivo es diseminar habilidades de programación en R en un contexto biológico a través del sistema de ABISSMAL. Tenemos seis tutoriales que cubren los siguientes puntos: -1. Una introducción a RStudio que incluye descargar el repositorio de GitHub de ABISSMAL, una introducción al `pipeline` de procesar y analizar datos de ABISSMAL, e información sobre cómo manejar y reportar errores +1. Una introducción a RStudio que incluye cómo descargar el repositorio de GitHub de ABISSMAL, una introducción al `pipeline` para procesar y analizar datos de ABISSMAL, e información sobre cómo manejar y reportar errores -2. Aprende cómo configurar un espacio virtual, incluyendo limpiar tu ambiente global, ejecutar código adentro de trozos de código de archivos de RMarkdown, instalar paquetes, atajos para escribir código, y crear y especificar un directorio de trabajo +2. Aprender cómo configurar un espacio virtual, incluyendo limpiar tu ambiente global, ejecutar código dentro de piezas de código de archivos de RMarkdown, instalar paquetes, atajos para escribir código, y crear y especificar un directorio de trabajo -3. Crea datos simulados de movimientos de animales mientras aprendes cómo crear objetos en R, qué son los diferentes tipos de datos en R, y cómo manipular objetos con frases condicionales y expresiones de `piping` +3. Crear datos simulados de movimientos de animales mientras se aprende cómo crear objetos en R, qué son los diferentes tipos de datos en R, y cómo manipular objetos con frases condicionales y expresiones de `piping` -4. Guarda los datos simulados y aprende cómo escribir datos a archivos físicos en tu computadora, manipular `dataframes` con R base y el `tidyverse`, y escribir bucles sencillos y complejos +4. Guardar los datos simulados y aprender cómo escribir datos a archivos físicos en tu computadora, manipular `dataframes` con R base y el `tidyverse`, y escribir bucles (`loops`) sencillos y complejos -5. Trabaja en el `pipeline` de procesar datos con ABISSMAL mientras aprendes cómo acceder y usas funciones customizadas, y también cómo visualizar marcas de tiempo en una figura de código de barras +5. Trabajar en el `pipeline` de procesar datos con ABISSMAL mientras aprendes cómo acceder y usar funciones customizadas, y también cómo visualizar marcas de tiempo en una figura de código de barras -6. Termina el `pipeline` de análisis de datos de ABISSMAL mientras usas las habilidades de programación que practicaste en los tutoriales anteriores, y terminarás con crear una figura de código de barras más compleja y refinada +6. Terminar el `pipeline` de análisis de datos de ABISSMAL mientras se emplean las habilidades de programación que practicaste en los tutoriales anteriores, y al final vas a crear una figura de código de barras más compleja y refinada -Cada tutorial está disponible como un archivo de RMarkdown (extension `.Rmd`) que puedes abrir en RStudio. El reporte "tejido" de cada tutorial está disponible como un archivo en formato HTML, y puedes abrir esos archivos en tu navegador por defecto. Los archivos de HTML contienen texto, código, y los resultados de los trozos de código. +Cada tutorial está disponible como un archivo de RMarkdown (extension `.Rmd`) que puedes abrir en RStudio. El reporte de cada tutorial está disponible como un archivo en formato HTML, estos archivos se pueden abrir en cualquier navegador de internet (Google Chrome, Firefox, Safari, etc). Los archivos de HTML contienen texto, código, y los resultados de las piezas de código. -Sería una gran ayuda si después de completar los tutoriales también completarías una encuesta a través de [Google Forms](https://forms.gle/CaQXVWDrY5oHg8mCA). La información que compartes nos ayudará mejorar los tutoriales en el futuro. +Sería una gran ayuda si después de completar los tutoriales pudieras completar una encuesta a través de [Google Forms](https://forms.gle/CaQXVWDrY5oHg8mCA). La información que compartas nos ayudará mejorar los tutoriales en el futuro. diff --git a/R/vignettes/Tutorial_01_Introduccion.Rmd b/R/vignettes/Tutorial_01_Introduccion.Rmd index 1dfa4c0..bb4eca6 100644 --- a/R/vignettes/Tutorial_01_Introduccion.Rmd +++ b/R/vignettes/Tutorial_01_Introduccion.Rmd @@ -12,11 +12,11 @@ output:

Información sobre esta traducción

-Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos en este primer tutorial para reportar un "Issue". +Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por Tania Molina, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos en este primer tutorial para reportar un "Issue".

Resumen del tutorial y objetivos de aprendizaje

-En este primer tutorial, 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: +En este primer tutorial aprenderás acerca del programa RStudio, cómo crear una versión local del repositorio de ABISSMAL en GitHub, y en qué consiste el `pipeline` de análisis de datos en ABISSMAL. En resumen: 1. Configurar tu sesión de RStudio 2. Crear una versión local de un repositorio de GitHub @@ -24,7 +24,7 @@ En este primer tutorial, vas a leer sobre el programa RStudio, aprender cómo cr 4. Solucionar problemas con tu código usando recursos en línea 5. Reportar problemas de código a GitHub -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. +Hay muchas formas de completar una tarea o solucionar un problema en R, mantén esto en mente mientras completas estos tutoriales. En cada tutorial, vas a aprender diferentes ejemplos sobre cómo manejar ciertas asignaciones o solucionar problemas específicas con código, pero estos ejemplos no son un resumen exhaustivo de cómo manejar cada tarea ni cómo solucionar cada problema individualmente. La recomendación es que uses estos tutoriales como una oportunidad para practicar tus habilidades de escribir código en un contexto biológico.

Configurar tu sesión de RStudio

@@ -34,7 +34,7 @@ Cuando hayas instalado R y RStudio en tu computadora, puedes hacer clic en el í
![Una imagen de la configuración predeterminada de los paneles de RStudio, con el panel de la consola debajo del panel de la fuente](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/RStudio_01.png) -Con esta configuración de paneles 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: +Con esta configuración de paneles puede ser difícil ver el código escrito y guardado en archivos en el panel de la fuente, así como el resultado de ese código en el panel de la consola. Puedes reconfigurar los paneles para que el panel de la fuente esté al lado izquierdo del panel de la consola, y así facilitará ver los resultados de tu código inmediatamente. Puedes seguir los siguientes pasos para reconfigurar los paneles en RStudio: * Selecciona la opción "Tools" en el menú @@ -57,29 +57,29 @@ También puedes cambiar el tamaño de la fuente del texto y código que escribes

Crear una versión local de un repositorio de GitHub

-Si nunca has usado GitHub, recomiendo bajar el programa [GitHub Desktop](https://docs.github.com/es/desktop). Este programa es una interfaz gráfica de usuario para 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](https://github.com/lastralab/ABISSMAL). +Si nunca has usado GitHub, recomiendo bajar el programa [GitHub Desktop](https://docs.github.com/es/desktop). Este programa es una interfaz gráfica de usuario para la plataforma de GitHub, que provee un sistema de control de versiones. Vamos a seguir los siguientes pasos para crear una versión local del repositorio de ABISSMAL que está en GitHub para que puedas acceder las funciones de R de ABISSMAL en tu propia computadora. Si ya sabes cómo usar GitHub y cómo usar Git en el terminal, puedes seguir las instrucciones que tenemos en el [README de ABISSMAL](https://github.com/lastralab/ABISSMAL). -Cuando hayas instalado GitHub Desktop, puedes: +Después de instalar GitHub Desktop: -* Hacer clic en el ícono de GitHub Desktop para abrir el programa +* Haz 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 +* Abre tu navegador web y ve 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 +* Haz clic en el botón verde de "Code" y copia la URL debajo de la opción "HTTPS" en el menú desplegable -* Ir al menú de GitHub Desktop y seleccionar "File" +* Ve al menú de GitHub Desktop y selecciona "File" -* Seleccionar "Clone repository" +* Selecciona "Clone repository" -* Seleccionar la pestaña "URL" +* Selecciona 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" +* Pega 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 +* Revisa que el directorio en la caja "Local path" es la ubicación en tu computadora donde quieres instalar el repositorio de ABISSMAL. Por ejemplo, si "Local path" es "/home/YourUserName/Desktop/ABISSMAL", el repositorio de ABISSMAL se instalará directamente en tu Desktop -* Seleccionar "Clone" cuando estés lista para crear una versión local del repositorio de ABISSMAL en tu computadora +* Selecciona "Clone" cuando estés lista para crear una versión local del repositorio de ABISSMAL en tu computadora -Cuando el repositorio se haya instalado en tu computadora, deberías de poder ver el siguiente directorio y una lista de archivos adentro de una carpeta llamada "ABISSMAL": +Cuando el repositorio se haya instalado en tu computadora, deberías de poder ver el siguiente directorio y una lista de archivos dentro de una carpeta llamada "ABISSMAL":
![Una imagen del directorio de la versión local del repositorio de ABISSMAL](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/ABISSMAL_localrepo.png) @@ -92,13 +92,13 @@ ABISSMAL provee 5 funciones diferentes de R para procesar y analizar datos, y de 1. `combine_raw_data` automáticamente combina hojas de cálculo de los datos originales que fueron colectados por día y los guarda en una sola hoja de cálculo a través de todos los días de colección de datos. Esta concatenación de datos se realiza por cada tipo de sensor, por ejemplo, los sensores de rayos infrarrojo o una cámara de infrarrojo, y no cambia ni reemplaza los datos originales. Esta función puede usar datos de los sensores de RFID ("radio frequency identification"), los sensores infrarrojo, la cámara, o el sensor de temperatura de ABISSMAL. Las siguientes funciones no aceptan datos de temperatura -2. `detect_perching_events` toma como entrada los resultados de `combine_raw_data` y detecta grupos de detecciones que ocurrieron cerca en el tiempo como eventos de posar. Estos eventos de 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 +2. `detect_perching_events` toma como entrada los resultados de `combine_raw_data` y detecta grupos de detecciones que ocurrieron cerca en el tiempo como eventos de posar. Estos eventos de pose representan periodos de tiempo cuando un individuo estuvo postrado en la antena de RFID (como percha) situado en la entrada del contenedor de nido. La función usa datos de los sensores ubicados en la entrada del contenedor (RFID o los sensores de infrarrojo) y devuelve una hoja de cálculo de las coordenadas temporales de los eventos de posar inferidos -3. `preprocess_detections` usa como entrada los resultados de `combine_raw_data` y remueve detecciones que ocurrieron muy cerca en el tiempo. Esta función devuelve una hoja de cálculo de datos pre-procesados por tipo de sensor, y en cada hoja de cálculo, las detecciones consecutivas deberían de estar separadas por 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 +3. `preprocess_detections` usa como entrada los resultados de `combine_raw_data` y remueve detecciones que ocurrieron muy cerca en el tiempo. Esta función devuelve una hoja de cálculo de datos pre-procesados por tipo de sensor, y en cada hoja de cálculo, las detecciones consecutivas deberían de estar separadas por uno o más umbrales temporales predeterminados. Por ejemplo, cuando usas un umbral temporal de 1 segundo para filtrar las detecciones originales, solo una detección puede ocurrir por segundo en los datos procesados -4. `detect_clusters` usa como entrada los resultados de `preprocess_detections` obtenidos con dos o más tipos de sensores. La función identifica detecciones a través de dos o más tipos de sensores que ocurrieron cerca en el tiempo, y devuelve información temporal y metadatos sobre cada grupo o `cluster` (cúmulo). Cada `cluster` de detecciones representa un evento discreto de movimiento de un individuo o más que un individuo +4. `detect_clusters` usa como entrada los resultados de `preprocess_detections` obtenidos con dos o más tipos de sensores. La función identifica detecciones a través de dos o más tipos de sensores que ocurrieron cerca en el tiempo, y devuelve información temporal y metadatos sobre cada grupo o `cluster` (cúmulo). Cada `cluster` de detecciones representa un evento discreto de movimiento de uno o más individuos -5. `score_clusters` usa como entrada los resultados de `detect_clusters`. Esta función también puede usar los resultados de `detect_perching_events` para integrar los eventos de posar que fueron identificados en los datos originales de RFID o los sensores de infrarrojo, y puede usar los resultados de `preprocess_detections` para integrar los eventos de grabación de vídeos que no fueron incluidos en los `clusters` detectados por `detect_clusters`. Esta función hace inferencias de comportamiento de los eventos de movimiento, incluyendo la dirección de movimiento, la magnitud de movimiento, la identidad 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. +5. `score_clusters` usa como entrada los resultados de `detect_clusters`. Esta función también puede usar los resultados de `detect_perching_events` para integrar los eventos de posar que fueron identificados en los datos originales de RFID o los sensores de infrarrojo, y puede usar los resultados de `preprocess_detections` para integrar los eventos de grabación de vídeos que no fueron incluidos en los `clusters` detectados por `detect_clusters`. Esta función hace inferencias de comportamiento de los eventos de movimiento, incluyendo la dirección de movimiento, la magnitud de movimiento, la identidad del individuo (cuando datos de RFID se encontraron en un `cluster`), y en dónde ocurrió el inicio de la secuencia de movimiento (en la entrada o adentro del contenedor de nido). La función devuelve una hoja de cálculo de inferencias de comportamiento y otros metadatos sobre cada evento de movimiento, y estos resultados se pueden usar para visualizaciones y análisis estadísticos.
![La figura 4 del manuscrito de ABISSMAL con un `pipeline` con las 5 funciones primarias](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/Figure4_ComputationalAnalyses.png) @@ -106,14 +106,14 @@ ABISSMAL provee 5 funciones diferentes de R para procesar y analizar datos, y de

Solucionar errores en línea

-Mientras escribes y ejecutas código encontrarás errores que a veces pueden ser frustrantes. Experimentar y solucionar los errores que te salen es una parte muy importante de tu proceso de aprendizaje en R (u otros lenguajes de programación). Los errores surgen por diferentes razones, incluyendo errores de tipeo 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. +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 tipeo 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. +Cuando experimentas un error con tu código, es importante intentar solucionar el error independientemente antes de suponer que el error nació de errores de las funciones de ABISSMAL o los tutoriales. Hay muchos recursos en línea que puedes usar para solucionar errores comunes o inusuales en R. Puedes empezar con copiar y pegar el mensaje de error que ves en tu consola en un buscador, y así deberías de ver varias opciones de foros públicos dónde otras personas han consultado y solucionado errores similares. También deberías de poder de usar herramientas de IA generativo como ChatGPT para buscar errores de mecanografía u otros problemas con tu código. Otra opción útil es leer la documentación de R para investigar si el error que ves esta relacionado con un paquete o una función que puede ser una dependencia de ABISSMAL. -Ya cuando hayas investigado cuidadosamente un error, y estas segura que el error no se debe a un error de tipeo 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). +Ya cuando hayas investigado cuidadosamente un error, y estas segura de que el error no se debe a un error de mecanografía o problemas con la estructura de tus datos, luego puedes considerar si el error se debe a un problema con las funciones de ABISSMAL o los tutoriales mismos, y puedes reportar el error a las desarolladoras de ABISSMAL en GitHub (ver la siguiente sección).

Reportar errores en GitHub

-Es posible que encuentres errores mientras trabajas en cada tutorial, incluyendo errores con código que no ejecuta, o código que devuelve resultados incorrectos. Estos errores 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](https://github.com/lastralab/ABISSMAL/issues) en el repositorio de ABISSMAL, y seguir las instrucciones en el esquema del "Issue" nuevo para añadir la información que las desarrolladoras necesitan para trabajar efectivamente en el error. También puedes añadir la etiqueta de "bug" (o error) a tu "Issue". Si encuentras un error con los tutoriales puedes crear un "Issue" usando los mismos pasos, y deberías de clarificar que el error está relacionado al código de algún tutorial en particular. +Es posible que encuentres errores mientras trabajas en cada tutorial, incluyendo errores con código que no ejecuta, o código que devuelve resultados incorrectos. Estos errores se pueden manifestar desde las funciones de ABISSMAL o con el código de algún tutorial. Si encuentras un error en una función de ABISSMAL, puedes crear un "Issue" (una tarea o problema) en el repositorio de GitHub. Para crear un "Issue" nuevo, puedes seleccionar "New Issue" en la página de [Issues](https://github.com/lastralab/ABISSMAL/issues) en el repositorio de ABISSMAL, y seguir las instrucciones en el esquema del "Issue" nuevo para añadir la información que las desarrolladoras necesitan para trabajar efectivamente en el error. También puedes añadir la etiqueta de "bug" (o error) a tu "Issue". Si encuentras un error con los tutoriales puedes crear un "Issue" usando los mismos pasos, y deberías de clarificar que el error está relacionado al código de algún tutorial en particular. En el siguiente tutorial, vamos a crear datos simulados de movimiento de animales para aprender cómo usar las cinco funciones primarias de ABISSMAL. \ No newline at end of file diff --git a/R/vignettes/Tutorial_01_Introduccion.html b/R/vignettes/Tutorial_01_Introduccion.html index 75d6f83..8da7eea 100644 --- a/R/vignettes/Tutorial_01_Introduccion.html +++ b/R/vignettes/Tutorial_01_Introduccion.html @@ -1,6 +1,6 @@ - + @@ -1728,7 +1728,7 @@

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 tipeo mientras escribes código, problemas +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 @@ -1742,13 +1742,13 @@

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 tipeo +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 tipeo o problemas con la +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 diff --git a/R/vignettes/Tutorial_02_Configuracion.Rmd b/R/vignettes/Tutorial_02_Configuracion.Rmd index bea1253..158438c 100644 --- a/R/vignettes/Tutorial_02_Configuracion.Rmd +++ b/R/vignettes/Tutorial_02_Configuracion.Rmd @@ -127,7 +127,7 @@ libr

Resolver frases incompletas en la consola

-Es importante vigilar el panel de la consola mientras escribes y ejecutas código en RStudio. El símbolo de `>` (o "mayor que") en la consola significa que la consola terminó de ejecutar código y está lista para otra operación. Cuando ves el símbolo de `+` (o "más") en la consola, este símbolo indica que la frase de código que acabas de ejecutar no está completa. Las frases incompletas de código surgen de errores de tipeo, 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: +Es importante vigilar el panel de la consola mientras escribes y ejecutas código en RStudio. El símbolo de `>` (o "mayor que") en la consola significa que la consola terminó de ejecutar código y está lista para otra operación. Cuando ves el símbolo de `+` (o "más") en la consola, este símbolo indica que la frase de código que acabas de ejecutar no está completa. Las frases incompletas de código surgen de errores de mecanografía, como cuando te faltó abrir o cerrar los paréntesis en una función. Abajo hay un ejemplo de una frase incompleta de código: ```{r eval = FALSE} library(tidyverse diff --git a/R/vignettes/Tutorial_02_Configuracion.html b/R/vignettes/Tutorial_02_Configuracion.html index 02f2df3..0696f48 100644 --- a/R/vignettes/Tutorial_02_Configuracion.html +++ b/R/vignettes/Tutorial_02_Configuracion.html @@ -1,6 +1,6 @@ - + @@ -1751,7 +1751,7 @@

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 tipeo, como cuando te faltó +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
diff --git a/R/vignettes/Tutorial_03_SimularDatos.html b/R/vignettes/Tutorial_03_SimularDatos.html index b7593cd..fb038e2 100644 --- a/R/vignettes/Tutorial_03_SimularDatos.html +++ b/R/vignettes/Tutorial_03_SimularDatos.html @@ -1,6 +1,6 @@ - + diff --git a/R/vignettes/Tutorial_04_GuardarDatos.html b/R/vignettes/Tutorial_04_GuardarDatos.html index b2386e9..9ff0060 100644 --- a/R/vignettes/Tutorial_04_GuardarDatos.html +++ b/R/vignettes/Tutorial_04_GuardarDatos.html @@ -1,6 +1,6 @@ - + diff --git a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.html b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.html index 5ee053b..ac5a1d6 100644 --- a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.html +++ b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.html @@ -1,6 +1,6 @@ - + diff --git a/R/vignettes/Tutorial_06_FinalizarAnalisis.html b/R/vignettes/Tutorial_06_FinalizarAnalisis.html index f219e21..a79f3f5 100644 --- a/R/vignettes/Tutorial_06_FinalizarAnalisis.html +++ b/R/vignettes/Tutorial_06_FinalizarAnalisis.html @@ -1,6 +1,6 @@ - + diff --git a/R/vignettes/Vignette_01_Introduction.html b/R/vignettes/Vignette_01_Introduction.html index aba2193..d77d612 100644 --- a/R/vignettes/Vignette_01_Introduction.html +++ b/R/vignettes/Vignette_01_Introduction.html @@ -1,6 +1,6 @@ - + diff --git a/R/vignettes/Vignette_02_Setup.html b/R/vignettes/Vignette_02_Setup.html index e67a629..00799ef 100644 --- a/R/vignettes/Vignette_02_Setup.html +++ b/R/vignettes/Vignette_02_Setup.html @@ -1,6 +1,6 @@ - + diff --git a/R/vignettes/Vignette_03_SimulateData.html b/R/vignettes/Vignette_03_SimulateData.html index 2708d5f..21f1553 100644 --- a/R/vignettes/Vignette_03_SimulateData.html +++ b/R/vignettes/Vignette_03_SimulateData.html @@ -1,6 +1,6 @@ - + diff --git a/R/vignettes/Vignette_04_SaveData.html b/R/vignettes/Vignette_04_SaveData.html index 77d73d1..7ad397f 100644 --- a/R/vignettes/Vignette_04_SaveData.html +++ b/R/vignettes/Vignette_04_SaveData.html @@ -1,6 +1,6 @@ - + diff --git a/R/vignettes/Vignette_05_ProcessData_BuildPlots.html b/R/vignettes/Vignette_05_ProcessData_BuildPlots.html index f55f5be..4d0d2fe 100644 --- a/R/vignettes/Vignette_05_ProcessData_BuildPlots.html +++ b/R/vignettes/Vignette_05_ProcessData_BuildPlots.html @@ -1,6 +1,6 @@ - + diff --git a/R/vignettes/Vignette_06_FinishAnalysisPipeline.html b/R/vignettes/Vignette_06_FinishAnalysisPipeline.html index 69d1f74..db060f5 100644 --- a/R/vignettes/Vignette_06_FinishAnalysisPipeline.html +++ b/R/vignettes/Vignette_06_FinishAnalysisPipeline.html @@ -1,6 +1,6 @@ - + diff --git a/R/vignettes/styles.css b/R/vignettes/styles.css index 8272378..b71f3d9 100644 --- a/R/vignettes/styles.css +++ b/R/vignettes/styles.css @@ -43,7 +43,7 @@ pre code { font-size: 16px; /* color: #708090; /* - /* background-color: #F8F8FF; /* + /* background-color: #F8F8FF; */ } From 65838a9e18507ca2fc312b49545c23348bcf6f93 Mon Sep 17 00:00:00 2001 From: lastralab Date: Fri, 26 Apr 2024 12:03:38 -0400 Subject: [PATCH 64/69] [PCT-472] added my name an role as editor --- R/vignettes/Tutorial_01_Introduccion.html | 2 +- R/vignettes/Tutorial_02_Configuracion.Rmd | 2 +- R/vignettes/Tutorial_02_Configuracion.html | 2 +- R/vignettes/Tutorial_03_SimularDatos.Rmd | 2 +- R/vignettes/Tutorial_03_SimularDatos.html | 2 +- R/vignettes/Tutorial_04_GuardarDatos.Rmd | 2 +- R/vignettes/Tutorial_04_GuardarDatos.html | 2 +- R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd | 2 +- R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.html | 2 +- R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd | 2 +- R/vignettes/Tutorial_06_FinalizarAnalisis.html | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/R/vignettes/Tutorial_01_Introduccion.html b/R/vignettes/Tutorial_01_Introduccion.html index 8da7eea..54a1518 100644 --- a/R/vignettes/Tutorial_01_Introduccion.html +++ b/R/vignettes/Tutorial_01_Introduccion.html @@ -1542,7 +1542,7 @@

2023-12-27

Información sobre esta traducción

-

Este tutorial fue traducido al español por Grace Smith-Vidaurre, +

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 diff --git a/R/vignettes/Tutorial_02_Configuracion.Rmd b/R/vignettes/Tutorial_02_Configuracion.Rmd index 158438c..e18cc51 100644 --- a/R/vignettes/Tutorial_02_Configuracion.Rmd +++ b/R/vignettes/Tutorial_02_Configuracion.Rmd @@ -18,7 +18,7 @@ knitr::opts_chunk$set(echo = TRUE, eval = FALSE)

Información sobre esta traducción

-Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos en el primer tutorial para reportar un "Issue". +Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por Tania Molina, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos en el primer tutorial para reportar un "Issue".

Resumen del tutorial y objetivos de aprendizaje

diff --git a/R/vignettes/Tutorial_02_Configuracion.html b/R/vignettes/Tutorial_02_Configuracion.html index 0696f48..afd555c 100644 --- a/R/vignettes/Tutorial_02_Configuracion.html +++ b/R/vignettes/Tutorial_02_Configuracion.html @@ -1542,7 +1542,7 @@

2023-12-27

Información sobre esta traducción

-

Este tutorial fue traducido al español por Grace Smith-Vidaurre, +

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 diff --git a/R/vignettes/Tutorial_03_SimularDatos.Rmd b/R/vignettes/Tutorial_03_SimularDatos.Rmd index f7baf25..a8b295d 100644 --- a/R/vignettes/Tutorial_03_SimularDatos.Rmd +++ b/R/vignettes/Tutorial_03_SimularDatos.Rmd @@ -18,7 +18,7 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE)

Información sobre esta traducción

-Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos en el primer tutorial para reportar un "Issue". +Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por Tania Molina, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos en el primer tutorial para reportar un "Issue".

Resumen del tutorial y objetivos de aprendizaje

diff --git a/R/vignettes/Tutorial_03_SimularDatos.html b/R/vignettes/Tutorial_03_SimularDatos.html index fb038e2..936ee23 100644 --- a/R/vignettes/Tutorial_03_SimularDatos.html +++ b/R/vignettes/Tutorial_03_SimularDatos.html @@ -1542,7 +1542,7 @@

2023-12-27

Información sobre esta traducción

-

Este tutorial fue traducido al español por Grace Smith-Vidaurre, +

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 diff --git a/R/vignettes/Tutorial_04_GuardarDatos.Rmd b/R/vignettes/Tutorial_04_GuardarDatos.Rmd index 848c5d1..5952d74 100644 --- a/R/vignettes/Tutorial_04_GuardarDatos.Rmd +++ b/R/vignettes/Tutorial_04_GuardarDatos.Rmd @@ -18,7 +18,7 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE)

Información sobre esta traducción

-Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos en el primer tutorial para reportar un "Issue". +Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por Tania Molina, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos en el primer tutorial para reportar un "Issue".

Resumen del tutorial y objetivos de aprendizaje

diff --git a/R/vignettes/Tutorial_04_GuardarDatos.html b/R/vignettes/Tutorial_04_GuardarDatos.html index 9ff0060..c099883 100644 --- a/R/vignettes/Tutorial_04_GuardarDatos.html +++ b/R/vignettes/Tutorial_04_GuardarDatos.html @@ -1542,7 +1542,7 @@

2023-12-27

Información sobre esta traducción

-

Este tutorial fue traducido al español por Grace Smith-Vidaurre, +

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 diff --git a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd index 8d919f9..9c351af 100644 --- a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd +++ b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd @@ -18,7 +18,7 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE)

Información sobre esta traducción

-Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos en el primer tutorial para reportar un "Issue". +Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por Tania Molina, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos en el primer tutorial para reportar un "Issue".

Resumen del tutorial y objetivos de aprendizaje

diff --git a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.html b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.html index ac5a1d6..8331e4a 100644 --- a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.html +++ b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.html @@ -1543,7 +1543,7 @@

2023-12-27

Información sobre esta traducción

-

Este tutorial fue traducido al español por Grace Smith-Vidaurre, +

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 diff --git a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd index b6eb6fb..1483ea2 100644 --- a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd +++ b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd @@ -18,7 +18,7 @@ knitr::opts_chunk$set(echo = TRUE, eval = TRUE)

Información sobre esta traducción

-Este tutorial fue traducido al español por Grace Smith-Vidaurre, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos en el primer tutorial para reportar un "Issue". +Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por Tania Molina, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos en el primer tutorial para reportar un "Issue".

Resumen del tutorial y objetivos de aprendizaje

diff --git a/R/vignettes/Tutorial_06_FinalizarAnalisis.html b/R/vignettes/Tutorial_06_FinalizarAnalisis.html index a79f3f5..c060865 100644 --- a/R/vignettes/Tutorial_06_FinalizarAnalisis.html +++ b/R/vignettes/Tutorial_06_FinalizarAnalisis.html @@ -1542,7 +1542,7 @@

2023-12-27

Información sobre esta traducción

-

Este tutorial fue traducido al español por Grace Smith-Vidaurre, +

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 From ff96589f1739848f5507f116da4cfcf84f5f2e7a Mon Sep 17 00:00:00 2001 From: lastralab Date: Fri, 26 Apr 2024 12:49:25 -0400 Subject: [PATCH 65/69] =?UTF-8?q?[PCT-472]=20replaced=20costumbre=20with?= =?UTF-8?q?=20pr=C3=A1ctica=20and=20edited=20tutorial=2002?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- R/vignettes/Tutorial_02_Configuracion.Rmd | 64 +++++++++++----------- R/vignettes/Tutorial_02_Configuracion.html | 12 ++-- R/vignettes/Tutorial_03_SimularDatos.Rmd | 2 +- R/vignettes/Tutorial_03_SimularDatos.html | 2 +- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/R/vignettes/Tutorial_02_Configuracion.Rmd b/R/vignettes/Tutorial_02_Configuracion.Rmd index e18cc51..a3c2cce 100644 --- a/R/vignettes/Tutorial_02_Configuracion.Rmd +++ b/R/vignettes/Tutorial_02_Configuracion.Rmd @@ -22,22 +22,22 @@ Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por T

Resumen del tutorial y objetivos de aprendizaje

-Vamos a configurar tu espacio de trabajo virtual para sesiones de escribir código en R y usar las funciones de ABISSMAL en este segundo tutorial. Vas a aprender habilidades básicas de programación en R y buenas costumbres de la ciencia abierta para escribir código, incluyendo cómo: +En este segundo tutorial, vamos a configurar tu espacio de trabajo virtual para sesiones de código en R y para usar las funciones de ABISSMAL. Vas a aprender habilidades básicas de programación en R y mejores prácticas de la ciencia abierta para escribir código, incluyendo cómo: 1. Usar archivos de RMarkdown 2. Limpiar tu ambiente global -3. Ejecutar código adentro de un "trozo" de RMarkdown +3. Ejecutar código adentro de un "chunk" (trozo, pedazo) de RMarkdown 4. Aprender sobre funciones en R y su documentación 5. Instalar y acceder paquetes 6. Comentar tu código 7. Atajos de RStudio para escribir código 8. Crear y usar una carpeta o un directorio de trabajo -

Usar archivos de RMarkdown

+

Uso de archivos de RMarkdown

-Cada tutorial en esta serie de tutoriales esta disponible como un archivo de RMarkdown (extensión .Rmd) y también como un archivo de HTML que puedes abrir y ver en tu navegador por defecto. Cada archivo de HTML fue generado por "tejer" el archivo de RMarkdown, o convertir el texto y el código junto con los resultados del código en un reporte en formato HTML. Puedes leer [la documentación de RMarkdown](https://rmarkdown.rstudio.com/lesson-1.html) o este tutorial por [Teresa Boca en RPubs](https://rpubs.com/tereboca/informe_rmakrdown) para aprender más sobre cómo usar RMarkdown para escribo código y generar reportes. +Cada tutorial en esta serie de tutoriales está disponible como un archivo de RMarkdown (extensión .Rmd) y también como un archivo de HTML que puedes abrir y ver en tu navegador favorito. Cada archivo de HTML fue generado para entrelazar el archivo de RMarkdown, o convertir el texto y el código junto con los resultados del código en un reporte en formato HTML. Puedes leer [la documentación de RMarkdown](https://rmarkdown.rstudio.com/lesson-1.html) o este tutorial por [Teresa Boca en RPubs](https://rpubs.com/tereboca/informe_rmakrdown) para aprender más sobre cómo usar RMarkdown para escribo código y generar reportes. -Los archivos de RMarkdown facilitan el proceso de compartir tu código y tus resultados. Si nunca has usado RMarkdown, la mejor forma de completar los tutoriales será crear un archivo de RMarkdown nuevo para cada tutorial y escribir el código por ti misma. Aprenderás más si escribes el código y los comentarios (adentro y afuera de cada trozo de código) en tus propias palabras. También puedes abrir el reporte HTML de cada tutorial en tu navegador para tener una guía mientras escribes el código de cada tutorial en tu propio archivo de RMarkdown. Como otra alternativa, puedes abrir el archivo de RMarkdown del tutorial al lado de 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: +Los archivos de RMarkdown facilitan el proceso de compartir tu código y tus resultados. Si nunca has usado RMarkdown, la mejor forma de completar los tutoriales será crear un archivo de RMarkdown nuevo para cada tutorial y escribir el código por ti misma. Aprenderás más si escribes el código y los comentarios (adentro y afuera de cada trozo de código) en tus propias palabras. También puedes abrir el reporte HTML de cada tutorial en tu navegador para tener una guía mientras escribes el código de cada tutorial en tu propio archivo de RMarkdown. Como otra alternativa, puedes abrir el archivo de RMarkdown del tutorial al lado de tu propio archivo de RMarkdown si añades una tercera columna a la configuración de tus paneles de RStudio. Para ello debes seleccionar lo siguiente: * "Tools" * "Global Options" @@ -45,38 +45,38 @@ Los archivos de RMarkdown facilitan el proceso de compartir tu código y tus res * "Add Column" para añadir otro panel de fuente en una tercera columna * Luego puedes abrir el tutorial original en un panel de fuente, y tu propio archivo de RMarkdown en el otro panel de fuente -Si ya tienes experiencia con escribir código en R y usar archivos de RMarkdown, puedes abrir el archivo original de RMarkdown de un tutorial y ejecutar el código adentro de cada trozo para completar cada tutorial (con algunas modificaciones de los directorios para especificar los directorios y archivos en tu computadora). Si quieres preservar el código original en cada archivo de RMarkdown, puedes crear una copia de cada tutorial y modificar las copias mientras completas cada tutorial. +Si ya tienes experiencia con escribir código en R y usar archivos de RMarkdown, puedes abrir el archivo original de RMarkdown de un tutorial y ejecutar el código adentro de cada trozo (chunk) para completar cada tutorial (con algunas modificaciones de los directorios para usar los directorios y archivos de tu computadora). Si quieres preservar el código original en cada archivo de RMarkdown, puedes crear una copia de cada tutorial y modificar las copias mientras completas cada tutorial.

Limpiar tu ambiente global

Tu ambiente global es tu espacio virtual en R, y puede contener paquetes y objectos diferentes que facilitarán tus objetivos de programación y/o analizar datos. Puedes ver los paquetes y objetos que existen en tu ambiente con hacer clic en la pestaña de ambiente o "Environment" en el mismo panel que incluye las pestañas de "History" y "Connections". -Si iniciaste una sesión nueva de RStudio puede que tu ambiente global esté vacío. Pero si estás trabajando en una sesión vieja, o 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 costumbre 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. +Si iniciaste una sesión nueva de RStudio puede que tu ambiente global esté vacío. Pero si estás trabajando en una sesión vieja, o en un ambiente de un proyecto de R (RProject), tu ambiente global ya puede contener paquetes y objetos. Limpiar tu ambiente global es una buena práctica a seguir cada vez que empieces una sesión de código. Si no limpias tu ambiente global, incluso cuando uses el mismo código entre sesiones, corres el riesgo de usar versiones viejas de objetos que no reflejan los cambios más recientes en tu código. -Puedes limpiar tu ambiente global directamente de la interfaz de RStudio con hacer clic en el ícono de la escoba debajo de la pestaña de "Environment" (haz clic en "Yes" con "hidden objects" para también limpiar objetos escondidos). +Puedes limpiar tu ambiente global directamente de la interfaz de RStudio con hacer clic en el ícono de la escoba debajo de la pestaña de "Environment" (haz clic en "Yes" con "hidden objects" para limpiar objetos escondidos también). -También puedes limpiar tu ambiente global con ejecutar código como se detalla abajo. Puedes ejecutar el código adentro del siguiente trozo de formas diferentes: +También puedes limpiar tu ambiente global ejecutando el código que se detalla abajo. Puedes ejecutarlo dentro del siguiente trozo de formas diferentes: * Haz clic en el ícono de la flecha verde en la parte superior derecha del trozo de código para ejecutar solamente el código en este trozo -* Puedes ubicar tu cursor (y hacer clic) en cualquiera posición adentro de la línea de código, y luego navegar al ícono de "Run" en la parte superior derecha del panel de fuente que tiene un cuadro blanco y una flecha verde. En el menú desplegable, selecciona "Run Selected Lines" o "Run Current Chunk" (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 luego navegar al ícono de "Run" en la parte superior derecha del panel de fuente que tiene un cuadro blanco y una flecha verde. En el menú desplegable, selecciona "Run Selected Lines" o "Run Current Chunk" (para usar los atajos de cada comando, ve los siguientes dos puntos). "Run Selected Lines" ejecutará la línea de código en donde esté tu cursor, y "Run Current Chunk" ejecutará todo el código adentro del trozo (independientemente de la posición de tu cursor) -* Puedes ubicar tu cursor (y hacer clic) en cualquiera posición adentro de la línea de código, y usar el atajo de "Ctrl" + "Enter" para ejecutar la línea de código corriente donde esté tu cursor +* Puedes ubicar tu cursor (y hacer clic) en cualquiera posición adentro de la línea de código, y usar el atajo de "Ctrl" + "Enter" para ejecutar la línea de código donde esté tu cursor -* Para ejecutar todo el código en el trozo, puedes usar el atajo "Ctrl" + "Shift" + "Enter" +* Para ejecutar todo el código en el trozo (chunk), puedes usar el atajo "Ctrl" + "Shift" + "Enter" -El primer atajo arriba, para ejecutar una línea de código a la vez, es muy útil para poder ver los resultados de cada línea de código y revisarlos por errores. +El primer atajo arriba (para ejecutar una línea de código a la vez), es muy útil para poder ver los resultados de cada línea de código y buscar errores. ```{r} rm(list = ls()) ``` -El código arriba para limpiar tu ambiente global es una expresión anidada con dos funciones: `rm()` y `ls()`. La notación de `()` se usa para funciones en R. Las funciones son operaciones que puedes aplicar en tu propio código 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()`). +El código arriba para limpiar tu ambiente global es una expresión anidada con dos funciones: `rm()` y `ls()`. La notación de `()` se usa para funciones en R. Las funciones son operaciones que puedes aplicar en tu propio código usando el nombre de una función específica. R tiene una colección de funciones básicas que puedes acceder sin necesitar un paquete específico, incluyendo las dos funciones arriba (`rm()` y `ls()`).

Acceder documentación de funciones de R

-Puedes acceder la documentación para las funciones que usaste arriba con hacer clic en la pestaña de "Help" en el panel que incluye "Files" y "Plots", o puedes escribir el nombre de la función en la barra de búsqueda en el panel de "Help". También puedes acceder la documentación de funciones con ejecutar este código: +Puedes acceder la documentación para las funciones que usaste arriba con hacer clic en la pestaña de "Help" en el panel que incluye "Files" y "Plots", o puedes escribir el nombre de la función en la barra de búsqueda en el panel de "Help". También puedes acceder la documentación de funciones ejecutando el siguiente código: ```{r} ?rm @@ -89,15 +89,15 @@ Puedes acceder la documentación para las funciones que usaste arriba con hacer ``` -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. +La documentación de cada función contiene secciones específicas que pueden ser útiles para entender el uso y el propósito de la función, sobre todo los argumentos de la función. Estos argumentos son los valores que la función requiere de la usuaria para poder guiar o modificar la operación. Muchas funciones tienen valores por defecto para algunos de sus argumentos que se usarán cuando no asignas valores específicos. -Por ejemplo, la función `rm()` tiene un argumento `list()` (seguido por un `=` o signo 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. +Por ejemplo, la función `rm()` tiene un argumento `list()` (seguido por un `=` o signo de igual, que se usa para asignar un valor específico a un argumento). Necesitas proveer información después del argumento `list()` para que `rm()` limpie tu ambiente global de la forma que quieres. Para eliminar todo en tu ambiente global, estamos usando los resultados de la función `ls()`, que imprime los nombres de todos los objetos en tu ambiente global.

Instalar y cargar paquetes

-Después de limpiar tu ambiente global, 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`](https://www.tidyverse.org/) es una colección de paquetes de R que provee funciones y expresiones útiles para analizar datos. +Después de limpiar tu ambiente global, necesitas configurar tu espacio virtual de trabajo para preparar tu sesión de código y empezar a analizar datos. Un paso importante es asegurarte de que puedes acceder funciones que necesitas pero que no están disponibles a través de la colección de funciones básicas de R. Por ejemplo, [el `tidyverse`](https://www.tidyverse.org/) es una colección de paquetes de R que provee funciones y expresiones básicas útiles para analizar datos. -Si no has instalado el `tidyverse` en tu computadora local, necesitas instalar esta colección de paquetes para acceder funciones útiles para analizar datos. El código abajo instala el `tidyverse` de [CRAN](https://cran.r-project.org/), el "Comprehensive R Archive Network" en línea que contiene miles de paquetes de R. +Si no has instalado el `tidyverse` en tu computadora local, necesitas instalar esta colección de paquetes para acceder estas funciones de análisis de datos. El código siguiente instala el `tidyverse` de [CRAN](https://cran.r-project.org/), el "Comprehensive R Archive Network" en línea que contiene miles de paquetes de R. ```{r} # Instala el tidyverse de CRAN @@ -105,9 +105,9 @@ 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 costumbre 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 costumbre. 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. +En el trozo de arriba, añadí un comentario antes del código usando el símbolo de `#`, un signo numeral o `hashtag`. Cualquier texto que escribes después de un `hashtag` será ignorado cuando ejecutas tu código. Es buena práctica comentar tu código, sobre todo cuando estés aprendiendo cómo escribir código en R. Para biólogas, comentar tu código incluso cuando eres experta también es una muy buena práctica. Comentar tu código es una forma de documentar tu trabajo y sirve para hacer el código que publicas con manuscritos o herramientas más accesible y entendible para otros en la comunidad. -Cuando hayas instalado el `tidyverse` en tu computadora no necesitas instalarlo otra vez (por ejemplo, la siguiente vez que abres RStudio). 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`. +Cuando hayas instalado el `tidyverse` en tu computadora no necesitas instalarlo otra vez (por ejemplo, la siguiente vez que abres RStudio ya estará disponible). Lo único que necesitas hacer cada vez que abres otra sesión de RStudio es cargar el paquete en tu ambiente global para poder acceder las funciones contenidas adentro de la colección de paquetes del `tidyverse`. ```{r} library(tidyverse) @@ -116,9 +116,9 @@ library(tidyverse)

Atajos para escribir código

-RStudio tiene varios atajo útiles para escribir código. Puedes encontrar estos atajos con ir a "Tools" en el menú de RStudio, luego seleccionar "Keyboard Shortcuts Help", que debería de abrir una ventana nueva con todos los atajos por defecto en RStudio. Arriba aprendiste sobre unos atajos para ejecutar código adentro de un trozo de RStudio. Algunos atajos útiles son "Shift + Ctrl + C", que puedes usar para comentar o silenciar de una a múltiples líneas de código a la vez (o sea, convertir código a comentarios), y "Ctrl + Alt + I", que automáticamente crea un trozo nuevo de RMarkdown. +RStudio tiene varios atajos útiles para escribir código. Puedes encontrar estos atajos yendo a "Tools" en el menú de RStudio, y luego seleccionando "Keyboard Shortcuts Help", que debería de abrir una ventana nueva con todos los atajos por defecto en RStudio. Arriba aprendiste sobre unos atajos para ejecutar código dentro de un trozo (chunk) de RStudio. Otros atajos útiles son "Shift + Ctrl + C", que puedes usar para comentar o silenciar de una a múltiples líneas de código a la vez (es decir, convertir líneas de código en comentarios), y "Ctrl + Alt + I", que automáticamente crea un trozo nuevo de RMarkdown. -RStudio también contiene un atajo de autocompletar 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). +RStudio también contiene un atajo de autocompletar usando el tabulador (la tecla que dice "tab"). Por ejemplo, en el trozo de abajo, después de escribir `libr` y hacer clic en el tabulador, deberías de poder ver una ventana pequeña que demuestra todas las funciones, paquetes u objetos disponibles que empiezan en el patrón "libr". Puedes usar las teclas con flechas (arriba, abajo, etc) para seleccionar la opción que quieres, y "Enter" para completar la línea (por ejemplo, para escribir `library()` para cargar un paquete). ```{r eval = FALSE} libr @@ -134,20 +134,20 @@ 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 costumbre 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. +Deberías de ver que el símbolo de `+` aparece en la consola cuando ejecutas el código arriba, porque te faltó un paréntesis para cerrar la función `library()`. Tienes dos opciones para resolver este problema. Primero, si sabes que símbolo te falta para completar la frase, puedes escribir este símbolo directamente en la consola y terminar de ejecutar el código con seleccionar "Enter". La segunda opción que tienes es hacer clic en la consola y luego seleccionar "Esc", que va a borrar la frase incompleta de la consola y reiniciar la consola para que puedas ejecutar más código (después de corregir tu error en el código en el panel de la fuente). Es buena práctica vigilar la consola para revisar que el código que ejecutas produce resultados o errores. Si ejecutas muchas frases de código a la vez y no observas los resultados que esperas en la consola, puede que tengas una frase incompleta por allí, y sería mejor limpiar la consola y revisar bien tu código antes de ejecutarlo otra vez.

Crear tu directorio de trabajo

-El siguiente paso en el proceso de configurar tu espacio virtual de trabajo es decidir donde estará tu directorio de trabajo para tu sesión de 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. +El siguiente paso en el proceso de configurar tu espacio virtual de trabajo, es decidir donde estará tu directorio de trabajo para tu sesión de código. Un directorio es una ubicación física en tu computadora (o una carpeta) donde R va a buscar archivos para leer o cargar datos. Cuando escribes datos de R como archivos físicos, estos archivos físicos se crearán en tu directorio de trabajo. -Puedes usar la función `getwd()` para revisar tu directorio corriente de trabajo. +Puedes usar la función `getwd()` para revisar tu directorio actual de trabajo. ```{r eval = TRUE} getwd() ``` -Mi directorio de trabajo por defecto es la carpeta en mi computadora donde guardé este archivo de RMarkdown. Para trabajar en un directorio aparte que contiene solo los datos generados en estos tutoriales, puedes mejor crear un directorio nuevo o carpeta nueva en tu computadora: +Mi directorio de trabajo por defecto es la carpeta en mi computadora donde guardé este archivo de RMarkdown. Para trabajar en un directorio aparte que contiene solo los datos generados en estos tutoriales, puedes crear un directorio nuevo o carpeta nueva en tu computadora: ```{r} ?dir.create @@ -156,9 +156,9 @@ 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`. +En el código arriba, deberías de reemplazar el `path`, o la combinación de directorios (en este ejemplo, el `path` es "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales") para que represente la ubicación en tu computadora donde quieres guardar la carpeta nueva que se llamará `ABISSMAL_tutoriales` (u otro nombre que prefieras). Si usas el sistema operativo de Windows, tienes que cambiar la dirección de los símbolos de barra porque el `path` que aparece en Windows para una carpeta va a tener barras invertidas (`\`) y R acepta solo barras inclinadas (`/`) en un `path`. -Es común configurar tu directorio de trabajo con la función `setwd()` en cursos preliminares de programación en R. Es buena costumbre 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()`. +Es común configurar tu directorio de trabajo con la función `setwd()` según cursos preliminares de programación en R. Sin embargo, es buena práctica evitar usar `setwd()` en código que quieres compartir con colaboradores o código que quieres compartir con la comunidad en general siguiendo la filosofía de ciencia abierta, por ejemplo cuando publicas un articulo o compartes una nueva herramienta. Usar `setwd()` cuando compartes tu código es suponer que todos los que van a usar tu código tienen el mismo directorio de trabajo en su computadora y esto es muy poco probable. Hay otras formas en que puedes especificar tu directorio de trabajo a través de tu código sin depender de `setwd()`. Por ejemplo, digamos que quieres crear una copia del primer tutorial y luego guardar esa copia adentro del directorio que creamos arriba. Podemos usar funciones de la colección base de R para copiar y guardar este archivo al directorio correcto de trabajo. Para evitar errores, vas a necesitar actualizar los `paths` abajo para que representen la carpeta en tu computadora con los tutoriales, y también tu directorio de trabajo: ```{r} @@ -170,16 +170,16 @@ file.copy( ``` -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. +En el código arriba especificamos dos argumentos a la función `file.copy()`. El primer argumento especifica la ubicación del archivo que queremos copiar ("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/") y el nombre del archivo mismo ("Tutorial_01_Introduccion.Rmd"). El segundo argumento especifica la ubicación donde queremos guardar la copia que vamos a crear de este archivo ("/home/gsvidaurre/Desktop/ABISSMAL_tutoriales/") y un nombre nuevo para la copia que vamos a crear ("Tutorial_01_Introduccion_copy.Rmd"). Los `paths` en cada argumento están entrecomillas en cada argumento, para indicar que la información entrecomillas se debería de tratar como el tipo de datos `character` en R, que es un término formal para la información en formato de texto. -El resultado en la consola debería de ser "[1] TRUE" si el archivo se copió y se guardó correctamente después de que ejecutaste el código. Puedes revisar que la función ejecutó bien 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". +El resultado en la consola debería de ser "[1] TRUE" si el archivo se copió y se guardó correctamente después de que ejecutaste el código. Puedes revisar que la función ejecutó bien navegando a la carpeta donde se debió de haber guardado el archivo copiado, y ver que la copia existe. También puedes usar la función base de R `list.files()` desde RStudio para revisar si el archivo que copiaste existe en tu directorio de trabajo. El resultado de `list.files()` es una lista de todos los archivos adentro de tu directorio de trabajo y esta lista debería de contener un solo archivo: "Tutorial_01_Introduccion_copy.Rmd". ```{r} list.files(path = "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales") ``` -El código arriba es un ejemplo sobre cómo puedes especificar tu directorio de trabajo en el código que escribes sin tener que depender de `setwd()`. En los siguientes tutoriales vas a ver otros ejemplos sobre cómo puedes leer archivos 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: +El código arriba es un ejemplo sobre cómo puedes especificar tu directorio de trabajo en el código que escribes sin tener que depender de `setwd()`. En los siguientes tutoriales vas a ver otros ejemplos sobre cómo puedes leer archivos o crear archivos en tu directorio de trabajo sin la función `setwd()`. Antes de empezar el siguiente tutorial, puedes borrar el archivo que copiaste a tu directorio de trabajo: ```{r} file.remove("/home/gsvidaurre/Desktop/ABISSMAL_tutoriales/Tutorial_01_Introduction_copy.Rmd") diff --git a/R/vignettes/Tutorial_02_Configuracion.html b/R/vignettes/Tutorial_02_Configuracion.html index afd555c..2b88043 100644 --- a/R/vignettes/Tutorial_02_Configuracion.html +++ b/R/vignettes/Tutorial_02_Configuracion.html @@ -1555,7 +1555,7 @@

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 costumbres de la ciencia abierta para escribir código, incluyendo +buenas prácticas de la ciencia abierta para escribir código, incluyendo cómo:

  1. Usar archivos de RMarkdown
  2. @@ -1621,7 +1621,7 @@

    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 -costumbre seguir cada vez que empieces una sesión de escribir código. Si +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.

    @@ -1708,10 +1708,10 @@

    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 costumbre comentar tu +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 costumbre. Comentar tu código es una forma de documentar +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 @@ -1765,7 +1765,7 @@

    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 -costumbre vigilar la consola para revisar que el código que ejecutas +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 @@ -1802,7 +1802,7 @@

    (/) 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 costumbre evitar usar setwd() en código que quieres +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. diff --git a/R/vignettes/Tutorial_03_SimularDatos.Rmd b/R/vignettes/Tutorial_03_SimularDatos.Rmd index a8b295d..4c92a91 100644 --- a/R/vignettes/Tutorial_03_SimularDatos.Rmd +++ b/R/vignettes/Tutorial_03_SimularDatos.Rmd @@ -180,7 +180,7 @@ glimpse(exp_rep) ``` -Usar `times = length(rfid_ts)` es mejor costumbre 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. +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. ```{r} diff --git a/R/vignettes/Tutorial_03_SimularDatos.html b/R/vignettes/Tutorial_03_SimularDatos.html index 936ee23..571c46e 100644 --- a/R/vignettes/Tutorial_03_SimularDatos.html +++ b/R/vignettes/Tutorial_03_SimularDatos.html @@ -1792,7 +1792,7 @@

    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 costumbre que +

    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 From 551a834cb2955f374e02e16704837f0770d61f37 Mon Sep 17 00:00:00 2001 From: lastralab Date: Fri, 26 Apr 2024 13:36:46 -0400 Subject: [PATCH 66/69] [PCT-472] edited tutorial 03 --- R/vignettes/Tutorial_03_SimularDatos.Rmd | 76 ++++++++++++------------ 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/R/vignettes/Tutorial_03_SimularDatos.Rmd b/R/vignettes/Tutorial_03_SimularDatos.Rmd index 4c92a91..c218f0c 100644 --- a/R/vignettes/Tutorial_03_SimularDatos.Rmd +++ b/R/vignettes/Tutorial_03_SimularDatos.Rmd @@ -22,21 +22,21 @@ Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por T

    Resumen del tutorial y objetivos de aprendizaje

    -En este tercer tutorial, vamos a crear datos simulados de movimientos de animales que fueron detectados por diferentes sensores. El proceso de simular estos datos reemplaza el proceso de colección de datos que provee ABISSMAL para grabar datos de animales en vivo. Generar estos datos simulados te va a proveer más oportunidades para practicar habilidades básicas de escribir código y tener control sobre la creación de estos datos te ayudará entender los pasos diferentes de los análisis de datos que siguen. Si quieres ver datos recolectados de pájaros con el `software` de ABISSMAL, y el código que usamos para analizar esos datos, puedes revisar el preprint del [manuscrito de metodos](https://ecoevorxiv.org/repository/view/6268/) que tiene enlaces a los datos y el código que son públicamente accesibles. +En este tercer tutorial, vamos a crear datos simulados de movimientos de animales que fueron detectados por diferentes sensores. El proceso de simular estos datos sustituye el proceso de colección de datos que proporciona ABISSMAL para grabar datos de animales en vivo. Estos datos simulados te van a generar más oportunidades para practicar habilidades básicas de escribir código y tener control sobre la creación de estos datos te ayudará a entender los diferentes pasos del análisis de datos que se mencionan a continuación. Si quieres ver datos recolectados de pájaros con el `software` de ABISSMAL, y el código que usamos para analizar esos datos, puedes revisar el preprint del [manuscrito de metodos](https://ecoevorxiv.org/repository/view/6268/) que tiene enlaces a los datos mismos y al código que son públicamente accesibles. -A través del proceso de 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: +A través del proceso de simulción de datos en este tutorial, vas a continuar usando tus habilidades de programación que aprendiste en el segundo tutorial, y vas a aprender más sobre: -1. Crear objetos como vectores y `dataframes` +1. Cómo crear objetos como vectores y `dataframes` 2. Tipos de datos en R -3. Indexar y manipular objetos -4. Usar frases condicionales +3. Cómo indexar y manipular objetos +4. Cómo usar frases condicionales 5. Expresiones de `pipe` en el `tidyverse`

    Cargar paquetes

    -En el tutorial anterior 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. +En el tutorial anterior aprendiste cómo instalar el `tidyverse`, una colección de paquetes para la ciencia de datos. También aprendiste sobre directorios de trabajo y creaste un directorio nuevo en tu computadora para guardar archivos de datos o imágenes que vas a generar en los siguientes tutoriales. -Cada vez que 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. +Cada vez que inicias un archivo nuevo de RMarkdown o R, es importante configurar tu espacio virtual antes de empezar a analizar datos. Con el trozo de abajo, vas a limpiar tu ambiente global y cargar el `tidyverse` usando código que viste en el segundo tutorial, pero ahora todo el código está combinado en un solo chunk (trozo). ```{r message = FALSE, warning = FALSE} rm(list = ls()) # Limpia tu ambiente global @@ -45,11 +45,11 @@ library(tidyverse) # Carga la colección de paquetes en el tidyverse ``` -

    Crear un objeto de `path`

    +

    Crear un objeto de `path` (ubicación)

    -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`. +El siguiente paso será especificar la ubicación de tu directorio de trabajo. En el segundo tutorial, usaste un `string`, o una secuencia de caracteres entrecomillas para especificar el `path` de tu directorio de trabajo mientras usabas funciones diferentes. En vez de copiar y pegar la misma secuencia de caracteres o "string" cada vez que quieres usar este `path`, es más eficiente almacenar esta secuencia de caracteres (que especifica el `path` de tu directorio de trabajo) adentro de un objeto nuevo, y luego usar el nombre del objeto cuando necesitas especificar el `path`. -Para crear un objeto de tu `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. +Para crear un objeto de tu propio `path`, puedes escribir el nombre del objeto que quieres crear al lado izquierdo del trozo (sin comillas), luego los símbolos para crear un objeto en R (`<-`), y finalmente la información que quieres asignar a este objeto. En este caso, la información que quieres guardar adentro de este objeto es tu directorio de trabajo en el formato de un `string`, y esta información necesita estar entrecomillas. ```{r eval = TRUE} path <- "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales" @@ -65,13 +65,13 @@ path También puedes ver el contenido de `path` con hacer clic en la pestaña de "Environment" y revisar la columna al lado derecho del nombre del objeto. Ahora puedes confirmar que `path` es un objeto nuevo que contiene información sobre tu directorio de trabajo y está disponible en tu ambiente global para más operaciones. -Puedes practicar eliminar solamente el objeto `path` de tu ambiente global con escribir y ejecutar el código `rm(list = "path")`. Después de ejecutar este código, deberías de inicializar `path` otra vez usando el código arriba. +Puedes practicar eliminar solamente el objeto `path` de tu ambiente global con escribir y ejecutar el código `rm(list = "path")`. Después de ejecutar este código, deberás de inicializar `path` otra vez usando el código de arriba.

    Crear marcas de tiempo para detecciones simuladas de movimientos

    -Los datos primarios que colecta ABISSMAL son marcas de tiempo que indican el momento en el tiempo cuando un sensor se activó y grabó movimiento. Muchas (pero no todas) de estas detecciones se pueden asignar a uno o más animales que se movieron cerca de un sensor, por ejemplo, cuando un pájaro entra a un contenedor de nido a través de una antena circular de RFID ("radio frequency identification") 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. +Los datos primarios que colecta ABISSMAL son marcas de tiempo que indican el momento en el tiempo cuando un sensor se activó y grabó movimiento. Muchas (pero no todas) de estas detecciones se pueden asignar a uno o más animales que se movieron cerca de un sensor, por ejemplo, cuando un pájaro entra a un contenedor de nido a través de una antena circular de RFID ("radio frequency identification") instalado en la entrada del contenedor. En los siguientes trozos de código, vamos a generar datos simulados que representan datos de detecciones grabados por el sensor de RFID y también sensores de infrarrojo. -Digamos que estamos recolectando datos para dos pájaros adultos a través de sensores de ABISSMAL montados en un contenedor de nido. La antena de RFID está montada en la entrada del contenedor, y un par de sensores de infrarrojo, el par "externo", está montado 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. +Digamos que estamos recolectando datos para dos pájaros adultos a través de sensores de ABISSMAL montados en un contenedor de nido. La antena de RFID está montada en la entrada del contenedor, y un par de sensores de infrarrojo, el par "externo", está montado enfrente de la antena de RFID para capturar movimientos afuera de la entrada del contenedor. Un segundo par de sensores de infrarrojo, el par "interno", está montado detrás de la antena de RFID para capturar movimientos adentro de la entrada del contenedor. En este ejemplo simulado, el par externo de sensores infrarrojo se va a activar cuando un pájaro entra al contenedor de nido, luego se activará la antena de RFID, y por último el par interno de sensores infrarrojo. Cuando un pájaro sale del contenedor, el par interno de sensores infrarrojo debería de activarse primero, luego la antena de RFID, y finalmente el par externo de sensores infrarrojo. Esto indicará la dirección del pájaro: entrando o saliendo. Empezaremos con crear un objeto que contiene las marcas de tiempo simuladas para la antena de RFID. Este objecto se llamara `rfid_ts` y va a contener cuatro marcas de tiempo en formato de horas:minutos:segundos. Cada marca de tiempo estará entrecomillas para indicar que estamos usando información de texto o secuencia de caracteres en el formato `string` de R. @@ -86,7 +86,7 @@ rfid_ts <- c("10:00:00", "10:05:00", "11:00:00", "11:05:00") Puedes ver las propiedades diferentes del objeto `rfid_ts`: ```{r} -rfid_ts # Ejecuta el nombre del objeto para ver sus contenidos +rfid_ts # Ejecuta el nombre del objeto para ver su contenido is.vector(rfid_ts) # Un valor binario indica si rfid_ts es un vector (TRUE) o no (FALSE) @@ -96,7 +96,7 @@ length(rfid_ts) # Este vector tiene cuatro elementos ``` -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. +Continuemos simulando dos movimientos de entrada y dos movimientos de salida del contenedor. Podemos escoger marcas de tiempo para el par externo de sensores de infrarrojo que preceden las marcas de tiempo de RFID y marcas de tiempo del par interno de sensores infrarrojo que siguen las marcas de tiempo de RFID para simular un evento de entrada. Podemos simular eventos de salida con las marcas de tiempo en el orden opuesto (el par interno de sensores se activa primero, luego RFID, luego el par externo de sensores de infrarrojo). Vamos a separar las detecciones de cada sensor adentro de cada movimiento de entrada y salida por un segundo. Aquí "IRBB" significa "infrared beam breakers" o sensores de infrarrojo. ```{r} # Simula marcas de tiempo para el par externo ("o_") e interno ("i_") de sensores infrarrojo para una entrada, una salida, y luego otra entrada y salida @@ -123,7 +123,7 @@ glimpse(rfid_ts) Otro tipo de información importante es simular ruido en las detecciones de los sensores. Por ejemplo, la antena de RFID puede fallar en detectar la etiqueta PIT ("passive integrated transponder") de un individuo, y los sensores de infrarrojo pueden activarse cuando los pájaros dejan material de nido colgando en la entrada del contenedor. En ambos casos, los sensores infrarrojo deberían de activar pero no la antena de RFID. ```{r} -# Simula unas fallas de detección de la antena de RFID a través de ambos pares de sensores infrarrojo +# Simula fallas de detección de la antena de RFID a través de ambos pares de sensores infrarrojo # Estas fallas de detección del sensor de RFID surgieron en cuatro eventos simulados adicionales (dos entradas y dos salidas) o_irbb_ts <- c(o_irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") i_irbb_ts <- c(i_irbb_ts, "06:05:04", "06:35:09", "07:15:41", "11:10:24") @@ -131,14 +131,14 @@ i_irbb_ts <- c(i_irbb_ts, "06:05:04", "06:35:09", "07:15:41", "11:10:24") glimpse(o_irbb_ts) glimpse(i_irbb_ts) -# Luego simula unas detecciones de ruido para el par externo de sensores infrarrojo +# Luego simula detecciones de ruido para el par externo de sensores infrarrojo o_irbb_ts <- c(o_irbb_ts, "09:45:01", "09:45:02", "09:45:03", "09:45:04", "09:45:05", "09:45:06", "09:45:07", "09:45:08", "09:45:09", "09:45:10", "09:45:11") glimpse(o_irbb_ts) ``` -Acabas de crear datos simulados de movimientos de animales con 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. +Acabas de crear datos simulados de movimientos de animales con datos que representan errores de detección, pero por el momento estos datos se encuentran en diferentes vectores separados y les faltan metadatos muy importantes. Por ejemplo, metadatos útiles incluyen información sobre la réplica experimental, la fecha, y para los datos de RFID, el código alfanumérico único de cada etiqueta PIT que fue detectada por la antena de RFID. Algunos de estos metadatos son críticos para los análisis más adelante, como la fecha y los códigos de las etiquetas PIT. Los vectores son estructuras útiles en R, pero una limitación de los vectores es que no puedes combinar diferentes tipos de datos en un solo objeto. Puedes intentar combinar diferentes tipos de datos en un vector: ```{r} @@ -154,13 +154,13 @@ c(1, 1, TRUE, FALSE) ``` -Cuando intentas combinar los tipos de dato `character`, `numeric`, y `binary` en el mismo vector, todos los elementos del vector se convierten al tipo de 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. +Cuando intentas combinar los tipos de dato `character`, `numeric`, y `binary` en el mismo vector, todos los elementos del vector se convierten al tipo de dato `character`. Algo parecido resulta cuando intentas combinar datos de tipo `numeric` y `binary` en el mismo vector, pero en este caso, los valores se convierten a numérico. En este ejemplo, también aprendiste que los valores binarios TRUE y FALSE en R son equivalentes a los valores numéricos 1 y 0, respectivamente.

    Crear vectores de metadatos

    Para los análisis que siguen, necesitas poder crear metadatos con diferentes tipos de datos, y luego combinar los datos primarios para cada sensor (las marcas de tiempo por sensor) con los metadatos importantes. Puedes empezar con crear vectores de metadatos para las marcas de tiempo de RFID, incluyendo información sobre la réplica experimental, la fecha, y la identidad de la etiqueta PIT para cada detección. -Para crear un vector de metadatos sobre la réplica experimental, vas a usar la función `rep()` para repetir la información sobre la réplica experimental automáticamente, en vez de copiar y pegar 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. +Para crear un vector de metadatos sobre la réplica experimental, vas a usar la función `rep()` para repetir la información sobre la réplica experimental automáticamente, en vez de copiar y pegar la misma información varias veces. Para configurar el número de veces que la información sobre la réplica experimental se va a repetir, también vas a usar la función `length()` que calcula el tamaño del vector `rfid_ts` automáticamente, y comunicarle este resultado a `rep()`. Crear un vector de metadatos que tiene el mismo tamaño (largo) que el vector de `rfid_ts` será útil para combinar estos vectores en un solo objeto más adelante. ```{r} # La documentación nos dice que rep() espera dos argumentos, `x` y `time` @@ -180,9 +180,9 @@ glimpse(exp_rep) ``` -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. +Usar `times = length(rfid_ts)` es mejor práctica que configurar el tamaño de `rfid_ts` manualmente (por ejemplo, `times = 14`). Configurar el valor de `times` manualmente es suponer que el objeto `rfid_ts` no ha cambiado adentro de una sesión de escribir código, o entre sesiones diferentes, y esto puede ser una suposición peligrosa. Cuando usas `times = length(rfid_ts)` te aseguras que el código arriba va a crear un vector de metadatos del mismo largo o tamaño que `rfid_ts` sin importar qué tantas modificaciones le hayas hecho a `rfid_ts`. -Puedes también usar una frase condicional 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. +Puedes también usar una frase condicional (condición) para confirmar que el vector de metadatos es del mismo tamaño que el vector de marcas de tiempo de RFID. Las frases condicionales puede ser útiles para revisar suposiciones en tu código, o para construir nuevos datos y funciones. ```{r} # Si esta condición se cumple, el resultado en la consola debería de ser "[1] TRUE" @@ -190,7 +190,7 @@ length(rfid_ts) == length(exp_rep) ``` -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). +En la frase condicional de arriba, estas usando los símbolos `==` para cuestionar si los dos vectores `rfid_ts` y `exp_rep` tienen la misma cantidad de elementos (si tienen el mismo largo o tamaño). También puedes usar los símbolos `!=` para preguntar si los dos vectores `rfid_ts` y `exp_rep` *no* tienen la misma cantidad de elementos (o sea, si *no* tienen el mismo largo): ```{r} @@ -214,31 +214,31 @@ seq(from = 5, to = length(rfid_ts), by = 1) # Si quieres filtrar elementos no consecutivos, puedes crear un vector de índices con la función `c()` c(1, 3, 5, 6, 8, 10, 11, 12, 13, 14) -## Filtra un vector por índices numéricos +## Filtra un vector con í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 +# Cuando insertas cualquiera de las expresiones arriba dentro de los corchetes que vienen después del nombre del vector, puedes seleccionar los elementos del índice 5 a lo largo de rfid_ts, y así eliminar los primeros cuatro elementos rfid_ts[5:length(rfid_ts)] rfid_ts[seq(from = 5, to = length(rfid_ts), by = 1)] rfid_ts[c(1, 3, 5, 6, 8, 10, 11, 12, 13, 14)] -# Puedes usar 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 +# Puedes usar cualquiera de los métodos arriba para crear una secuencia de índices que quieras eliminar, y luego usar el símbolo `-` dentro de los corchetes para eliminar los elementos en esos índices particulares. Por ejemplo: +rfid_ts[-c(1:4)] # los números deberían de estar adentro de la función `c()` para que funcione el filtro de forma invertida ``` Luego puedes revisar si esta versión modificada de `rfid_ts` es el mismo largo que `exp_rep`: ```{r} -# Esta frase debería de resultar en TRUE, porque estos vectores ya no tienen el mismo largo +# Esta frase debería de resultar en TRUE, porque es verdad que estos vectores ya no tienen el mismo largo length(rfid_ts[-c(1:4)]) != length(exp_rep) ```

    Crear `dataframes` con datos primarios y metadatos

    -Es importante combinar los metadatos con los datos primarios para análisis futuros, y puedes combinarlos usando un tipo de objeto que se llama un `dataframe`. 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. +Es importante combinar los metadatos con los datos primarios para análisis futuros, y puedes combinarlos usando un tipo de objeto que se llama un `dataframe` o cuadro de datos. Los `dataframes` son parecidos a las hojas de cálculo porque tienen dos dimensiones (filas y columnas), y puedes guardar múltiples tipos de datos diferentes en el mismo `dataframe`. También puedes guardar `dataframes` en hojas de cálculo o archivos físicos en tu computadora. Para combinar dos vectores en un solo `dataframe`, los vectores tienen que tener el mismo largo. Cuando intentas combinar dos vectores de largo diferentes, deberías de recibir un mensaje de error en la consola especificando que los dos argumentos no tienen el mismo número de filas: ```{r eval = FALSE} @@ -259,9 +259,9 @@ glimpse(sim_dats) ``` -Cuando revisas la estructura del objeto `sim_dats`, el `dataframe` nuevo, puedes ver que tiene 14 filas y dos columnas. Para cada columna (después del símbolo de "$"), puedes ver que el nombre de la columna (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`. +Cuando revisas la estructura del objeto `sim_dats`, el `dataframe` nuevo, puedes ver que tiene 14 filas y dos columnas. Para cada columna (después del símbolo de "$"), puedes ver que el nombre de la columna (`exp_rep` y `rfid_ts`), el tipo de dato en cada columna (en este momento cada columna es del tipo `character`), y luego los valores de cada columna en las primeras filas del `dataframe`. -Puedes cambiar los nombres de cada columna con añadir un nombre nuevo y el símbolo `=` antes de cada vector. Abajo el vector `exp_rep` se convierte en la columna `replicate` con la réplica experimental y el vector `rfid_ts` se convierte en la columna `timestamps` con las marcas de tiempo. +Puedes cambiar los nombres de cada columna con añadir un nombre nuevo y el símbolo `=` antes de cada vector. Abajo, el vector `exp_rep` se convierte en la columna `replicate` con la réplica experimental y el vector `rfid_ts` se convierte en la columna `timestamps` con las marcas de tiempo. ```{r} sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) @@ -302,7 +302,7 @@ glimpse(sim_dats)

    Crear `dataframes` usando el `tidyverse`

    -En el código arriba, usaste código de R base para añadir una columna nueva y actualizar el nombre de esa columna. Te tomó varias líneas de código para completar estas operaciones. Puedes reducir la cantidad de código que necesitas para estos pasos si eliminas las líneas de código que usaste para revisar las operaciones. Pero otra forma para reducir la cantidad de código que escribes para esta serie de operaciones es usar la notación y colección de funciones del `tidyverse`: +En el código anterior, usaste código de R básico para añadir una columna nueva y actualizar el nombre de esa columna. Te tomó varias líneas de código para completar estas operaciones. Puedes reducir la cantidad de código que necesitas para estos pasos si eliminas las líneas de código que usaste para revisar las operaciones. Pero otra forma para reducir la cantidad de código que escribes para esta serie de operaciones es usar la notación y colección de funciones del `tidyverse`: ```{r} # Crear el dataframe de nuevo con dos columnas @@ -318,11 +318,11 @@ glimpse(sim_dats) ``` -Acabas de añadir una columna para el año con el nombre correcto en menos líneas de código. En la notación del `tidyverse`, el símbolo de `%>%` significa una operación de `pipe`, en que usas el objeto antes del símbolo de `%>%` como entrada para la 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`. +Acabas de añadir una columna para el año con el nombre correcto en menos líneas de código. En la notación del `tidyverse`, el símbolo de `%>%` significa una operación de `pipe`, en que usas el objeto antes del símbolo de `%>%` como entrada para la función que sigue el símbolo de `%>%`. Arriba usaste el objeto de `sim_dats` como entrada para la función `mutate()`, que usaste para crear la columna `year`. -La notación `dplyr::` antes de `mutate()` indica que la función `mutate()` se debería de acceder desde el paquete que se llama `dplyr`. Incluir el nombre del paquete con dos puntos repetidos dos veces es una notación importante 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. +La notación `dplyr::` antes de `mutate()` indica que la función `mutate()` se debería de acceder desde el paquete que se llama `dplyr`. Incluir el nombre del paquete con dos puntos repetidos dos veces es una notación importante que se debe usar cuando hay múltiples funciones accesibles en tu ambiente global con el mismo nombre. Por ejemplo, si usas otros paquetes aparte de `dplyr` que también tienen funciones que se llaman `mutate()`, y no especificas cual paquete quieres usar, puedes terminar con errores inmediatos (como cuando el código no se puede ejecutar). Incluso si el código ejecuta, usar la operación equivocada de otra función de `mutate()` puede introducir errores a tus análisis más adelante que son difíciles de identificar. -Las operaciones de `piping` con el símbolo `%>%` (o un `pipe`) pueden simplificar el código que escribes porque no creas tantos objetos intermedios como cuando usas R base. Por 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: +Las operaciones de `piping` con el símbolo `%>%` (o un `pipe`) pueden simplificar el código que escribes porque no creas tantos objetos intermedios como cuando usas R base. Por esta misma razón, puede tomar mucha práctica solucionar errores con operaciones de `piping` cuando estas operaciones son largas y anidadas. Una forma útil para revisar resultados intermedios dentro de operaciones largas de `piping` es incluir la función `glimpse()` entre diferentes pasos de la operación: ```{r} # Crear el data frame otra vez con solo dos columnas @@ -339,7 +339,7 @@ sim_dats %>% ``` -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`. +En el código anterior, también aprendiste una forma nueva para repetir un valor adentro de una operación de `piping` con la notación `.` adentro de una función, que significa que la operación ejecutará con el objeto actual. En el ejemplo arriba, `.` se refiere al objeto `sim_dats` que se usó como entrada para la operación entera de `piping`. Como el símbolo `.` está adentro de la función `nrow()`, la función debería de devolver el número de filas de `sim_dat`. Puedes usar una operación parecida para añadir dos columnas al `dataframe` que contienen información del mes y día: ```{r} @@ -360,6 +360,6 @@ sim_dats %>% ``` -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`. +En el código anterior, añadiste dos columnas numéricas más al `dataframe` y lo hiciste sin necesitar usar la función `rep()` para repetir valores. Esto fue posible porque usaste un `dataframe` que ya existía como la entrada a las operaciones de `dplyr::mutate()`, y el único valor que especificaste para cada columna nueva del año, mes, y día se repitió automáticamente para llenar todas las filas en el `dataframe` para cada columna. Especificar un solo valor para una nueva columna puede ayudar a reducir la cantidad de código que escribes, pero sólo cuando de verdad quieres que el mismo valor se repita por todas las filas del `dataframe`. -Como no guardaste estas modificaciones a `sim_dats` en un objeto, el resultado del código arriba se va a imprimir 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. \ No newline at end of file +Como no guardaste estas modificaciones a `sim_dats` en un objeto, el resultado del código arriba se va a imprimir en la consola. En el siguiente tutorial, vas a guardar este `dataframe` de datos simulados de RFID y sensores infrarrojo en hojas de cálculo como archivos físicos en tu computadora. \ No newline at end of file From 7e9b3c78d0bf86c8e4156335e87ed1c192804bef Mon Sep 17 00:00:00 2001 From: lastralab Date: Fri, 26 Apr 2024 14:25:51 -0400 Subject: [PATCH 67/69] [PCT-472] edited tutorial 04 --- R/vignettes/Tutorial_04_GuardarDatos.Rmd | 128 +++++++++++----------- R/vignettes/Tutorial_04_GuardarDatos.html | 24 ++-- 2 files changed, 76 insertions(+), 76 deletions(-) diff --git a/R/vignettes/Tutorial_04_GuardarDatos.Rmd b/R/vignettes/Tutorial_04_GuardarDatos.Rmd index 5952d74..efd8d94 100644 --- a/R/vignettes/Tutorial_04_GuardarDatos.Rmd +++ b/R/vignettes/Tutorial_04_GuardarDatos.Rmd @@ -22,7 +22,7 @@ Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por T

    Resumen del tutorial y objetivos de aprendizaje

    -En este cuarto tutorial, vas a guardar hojas de cálculo de las detecciones simuladas de movimientos de animales a tu computadora. Vas a continuar usando habilidades que aprendiste en los tutoriales anteriores, y vas a aprender nuevas habilidades que incluyen: +En este cuarto tutorial, vas a guardar hojas de cálculo de las detecciones simuladas de movimientos de animales en tu computadora. Vas a continuar usando habilidades que aprendiste en los tutoriales anteriores, y vas a aprender nuevas habilidades que incluyen: 1. Indexar y filtrar `dataframes` 2. Revisar la estructura de `dataframes` con R base y el `tidyverse` @@ -79,7 +79,7 @@ glimpse(i_irbb_ts)

    Simula tres días de colección de datos de RFID

    -En el código abajo, vas a combinar el vector de marcas de tiempo de RFID que inicializaste arriba con metadatos en un `dataframe`. Estos metadatos van a incluir el año, el mes, y el día, y también una columna con los valores de dos etiquetas PIT (una etiqueta por individuo simulado), y una columna con información sobre el tipo de sensor. Deberías de reconocer partes de este código desde el tutorial anterior: +En el código siguiente, vas a combinar el vector de marcas de tiempo de RFID que inicializaste arriba con metadatos en un `dataframe`. Estos metadatos van a incluir el año, el mes, y el día, y también una columna con los valores de dos etiquetas PIT (una etiqueta por individuo simulado), y una columna con información sobre el tipo de sensor. Deberías de reconocer partes de este código del tutorial anterior: ```{r} # Crea un vector para la réplica experimental @@ -114,9 +114,9 @@ glimpse(sim_dats_rfid) ``` -Ahora puedes usar este `dataframe` para simular el proceso de colección de datos a través de dos días más. Para crear 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`. +Ahora puedes usar este `dataframe` para simular el proceso de colección de datos a través de dos días más. Para crear observaciones (filas) de dos días adicionales, puedes adjuntar las filas de una copia modificada de `sim_dats_rfid` al objeto original de `sim_dats_rfid`. -En el código 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: +En el código siguiente, estás usando una operación `pipe` para usar `sim_dats_rfid` como entrada en `bind_rows()`, y con esta notación estás especificando que `sim_dats_rfid` es el objeto original al cual quieres adjuntar más filas. Luego el código dentro de `bind_rows()` especifica el `dataframe`, o las filas nuevas que quieres adjuntar a `sim_dats_rfid`. En este caso, el código adentro de `bind_rows()` asigna `sim_dats_rfid` a `dplyr::mutate()` para modificar la columna de `day` y representar un día adicional de colección de datos. Luego repites este proceso para añadir un tercer día de recolectar datos simulados: ```{r} sim_dats_rfid <- sim_dats_rfid %>% @@ -133,7 +133,7 @@ sim_dats_rfid <- sim_dats_rfid %>% ) ) -glimpse(sim_dats_rfid) # Tres veces el número original de filas, se ve bien +glimpse(sim_dats_rfid) # Tres veces el número original de filas ``` @@ -142,22 +142,22 @@ glimpse(sim_dats_rfid) # Tres veces el número original de filas, se ve bien Acabas de revisar la estructura del `dataframe` para confirmar que los datos simulados tienen datos recolectados a través de tres días. También puedes revisar los valores únicos que están presentes en la columna de `day`. Abajo puedes ver una forma de revisar los valores únicos adentro de una columna de un `dataframe`, usando dos ejemplos diferentes de notación de R base para acceder columnas adentro de un `dataframe`: ```{r} -# Escribir una expresión con el nombre de un objeto dataframe, un símbolo $, y el nombre de una columna te ayuda 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 +# Escribir una expresión con el nombre de un objeto dataframe, un símbolo $, y el nombre de una columna te ayuda a acceder una columna a la vez en un dataframe. Una columna en un dataframe es un vector, por ende cuando ejecutas este código deberías de ver un vector de valores impreso en la consola sim_dats_rfid$day -# Puedes también acceder una columna en un dataframe 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 +# Puedes también acceder una columna en un dataframe indexando, si escribes dos pares de corchetes (ambos pares de abrir y cerrar) después del nombre del dataframe, y colocas el nombre de la columna entrecomillas adentro del par interno de corchetes sim_dats_rfid[["day"]] -# Puedes usar la función unique() para ver los valores únicos adentro de un vector, incluyendo una columna en un dataframe +# Puedes usar la función unique() para ver los valores únicos dentro de un vector, incluyendo una columna en un dataframe unique(sim_dats_rfid$day) # Tres días, se ve bien unique(sim_dats_rfid[["day"]]) # Tres días, se ve bien ``` -

    Revisar columnas de un `dataframe` con el `tidyverse`

    +

    Revisar columnas en un `dataframe` con el `tidyverse`

    -También puedes revisar valores únicos en una columna usando funciones del `tidyverse`. En la expresión 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`. +También puedes revisar valores únicos en una columna usando funciones del `tidyverse`. En la expresión de abajo, vas a usar una expresión de `pipe` para facilitar el `dataframe` `sim_dats_rfid` a la función `pull()`, que va a acceder la columna `day` del `dataframe` como un vector. Luego este vector de la columna `day` se va a usar como entrada a la función `unique()` para revisar los valores únicos del vector mismo. La función `unique()` no requiere un argumento adentro de los paréntesis porque ya recibió el valor de entrada que necesita a través de la operación de `piping`. ```{r} # Tres días, se ve bien @@ -172,7 +172,7 @@ sim_dats_rfid %>% Ahora puedes repetir este proceso de crear un `dataframe` con metadatos para los datos de los sensores de infrarrojo. Dado que los sensores de infrarrojo no colectan información sobre la identidad única de individuos, vas a añadir columnas para el año, el mes, el día, y el tipo de sensor. También vas a simular la colección de datos para estos sensores a través de los mismos tres días que los datos simulados de RFID. ```{r} -# Sobrescribe el vector exp_rep con un vector nuevo que tiene el mismo largo que los vectores o_irbb_ts y i_irbb_ts juntos +# Sobrescribe el vector exp_rep con un vector nuevo que tenga el mismo largo que los vectores o_irbb_ts y i_irbb_ts juntos exp_rep <- rep(x = "Nest_01", times = length(o_irbb_ts) + length(i_irbb_ts)) # Añade las marcas de tiempo de ambos pares de sensores infrarrojo a la misma columna usando c() @@ -184,7 +184,7 @@ sim_dats_irbb <- sim_dats_irbb %>% 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 + # Cada etiqueta única se repetirá por el tamaño del vector de marcas de tiempo de cada par de sensores infrarrojo sensor_id = c(rep("Outer Beam Breakers", length(o_irbb_ts)), rep("Inner Beam Breakers", length(i_irbb_ts))) ) @@ -204,7 +204,7 @@ sim_dats_irbb <- sim_dats_irbb %>% ) ) -glimpse(sim_dats_irbb) # Tres veces el número de filas, se ve bien +glimpse(sim_dats_irbb) # Tres veces el número de filas # Tres días, se ve bien sim_dats_irbb %>% @@ -222,11 +222,11 @@ Los `dataframes` que creas y manipulas en R se pueden guardar como archivos fís ``` -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()`: +Para escribir un archivo físico en tu directorio de trabajo, necesitas comunicarle a R 1) donde guardar el archivo y 2) el nombre del archivo que quieres crear. Puedes pasarle ambas piezas de información a `write.csv()` con combinar tu directorio de trabajo y el nombre del archivo usando la función `file.path()`. Para este ejemplo, vas a crear un archivo de prueba mientras practicas cómo usar `write.csv()`: ```{r} -# Combina el path 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 +# Combina el path (camino o ubicación) a tu directorio de trabajo con el nombre del archivo que quieres escribir +# La función file.path() combinará ambas piezas de información en un solo path para este archivo rfid_file <- file.path(path, "test_file.csv") # Este objeto contiene la ubicación dónde vas a guardar el archivo, y luego el nombre del archivo @@ -234,17 +234,17 @@ rfid_file ``` -Luego puedes proveer el `dataframe` a `write.csv()` usando un `pipe` y puedes especificar información adicional para crear el archivo `.csv`, como si quieres añadir una columna adicional de identidades numéricas de las filas: +Luego puedes proveer el `dataframe` a `write.csv()` usando un `pipe` y puedes especificar información adicional para crear el archivo `.csv`, como si quisieras añadir una columna adicional de identidades numéricas de las filas: ```{r eval = FALSE} sim_dats_rfid %>% # Escribe el dataframe como una hoja de cálculo en formato .csv. No incluyes los nombres de las filas (row.names = FALSE) - # El símbolo "." abajo significa que la función write.csv() va a operar sobre el objeto que proveyó el pipe, que en este caso es el objeto sim_dats_rfid + # El símbolo "." abajo significa que la función write.csv() va a operar sobre el objeto que proporcionó el pipe, que en este caso es el objeto sim_dats_rfid write.csv(x = ., file = rfid_file, row.names = FALSE) ``` -Como se especifica en la documentación para la función `write.csv()`, esta función va a incluir las nombres de las columnas en la hoja de cálculo por defecto. La función 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. +Como se especifica en la documentación para la función `write.csv()`, esta función va a incluir las nombres de las columnas en la hoja de cálculo por defecto. La función no va a adjuntar esta información del `dataframe` en el archivo de `.csv` si esta hoja de cálculo ya existe, o sea, si ya creaste el archivo de `.csv` y vuelves a correr el código arriba, el archivo se va a sobrescribir por defecto. Puedes revisar que `write.csv()` funcionó como esperabas con usar `list.files()` para ver los archivos en tu directorio de trabajo. ```{r eval = FALSE} @@ -254,7 +254,7 @@ 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". +También puedes usar `list.files()` para customizar una búsqueda con el argumento `pattern` (patrón). Usar el argumento `pattern` es parecido a buscar una palabra específica adentro de un documento de texto. El símbolo de "$" después de ".csv" significa que la función debería de buscar todos los archivos que *terminan* en el patrón ".csv". ```{r eval = FALSE} # Devuelve sólo archivos que terminan en el patrón ".csv" en este path particular @@ -264,7 +264,7 @@ list.files(path, pattern = ".csv$")

    Leer una hoja de cálculo

    -Ahora puedes leer uno de estos archivos con R usando la función `read.csv()`. En el código abajo, vas a proveer el resultado de `read.csv()` a la función `glimpse()` para revisar la estructura del `dataframe` creado en R cuando leíste el archivo. El resultado de este código se imprime a la consola porque no está guardado adentro de un objeto. +Ahora puedes leer uno de estos archivos con R usando la función `read.csv()`. En el código abajo, vas a proveer el resultado de `read.csv()` a la función `glimpse()` para revisar la estructura del `dataframe` cuando leíste el archivo. El resultado de este código se imprime a la consola porque no está guardado adentro de un objeto. ```{r eval = FALSE} read.csv(file.path(path, "test_file.csv")) %>% @@ -272,7 +272,7 @@ read.csv(file.path(path, "test_file.csv")) %>% ``` -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()`. +Ahora que practicaste usar `write.csv()` y `read.csv()`, puedes eliminar el archivo temporal que creaste proveyendo el objeto `rfid_file` a la función `file.remove()`. ```{r eval = FALSE} rfid_file <- file.path(path, "test_file.csv") @@ -284,7 +284,7 @@ file.remove(rfid_file)

    Guardar datos simulados para análisis con ABISSMAL

    -En el código abajo, vas a trabajar con una serie de pasos para guardar los datos simulados en el formato y en las ubicaciones esperadas por las funciones de ABISSMAL. Para poder usar estas funciones de ABISSMAL, los datos simulados originales se tienen que guardar en una hoja de cálculo diferente por el tipo de sensor y el día de colección de datos. Estas hojas de cálculo se tienen que guardar adentro de una carpeta por tipo de sensor. ABISSMAL guarda los datos originales de esta misma forma cuando el sistema se usa para colectar datos empíricos de animales. +En el código de abajo, vas a trabajar con una serie de pasos para guardar los datos simulados en el formato y en las ubicaciones esperadas por las funciones de ABISSMAL. Para poder usar estas funciones de ABISSMAL, los datos simulados originales se tienen que guardar en una hoja de cálculo diferente por el tipo de sensor y el día de colección de datos. Estas hojas de cálculo se tienen que guardar adentro de una carpeta por tipo de sensor. ABISSMAL guarda los datos originales de esta misma forma cuando el sistema se usa para colectar datos empíricos de animales.

    Filtrar un `dataframe`

    @@ -297,7 +297,7 @@ sim_dats_rfid %>% dplyr::filter(day == 1) %>% glimpse() -# 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 +# Revisa que este paso de filtrar se hizo correctamente. El único valor dentro de la columna del día debería de ser 1 sim_dats_rfid %>% dplyr::filter(day == 1) %>% pull(day) %>% @@ -305,15 +305,15 @@ sim_dats_rfid %>% ``` -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 "&". +Puedes obtener resultados similares cuando inviertes la frase condicional dentro de `dplyr::filter()` para eliminar los días que **no** fueron ni el segundo día ni el tercer día de colección de datos. Abajo combinaste dos frases condicionales usando el símbolo de "&". ```{r} sim_dats_rfid %>% - # Filtra el dataframe con seleccionar todas las filas en que los valores en la columna de día no son iguales a 2 o 3 + # Filtra el dataframe seleccionando todas las filas en que los valores en la columna de día no son iguales a 2 o 3 dplyr::filter(day != 2 & day != 3) %>% glimpse() -# Usa la función par ver los valores únicos en una columna para revisar que el proceso de filtrar se completo bien. Se ve bien +# Usa la función par ver los valores únicos en una columna para verificar que el proceso de filtrar se completo bien sim_dats_rfid %>% dplyr::filter(day != 2 & day != 3) %>% pull(day) %>% @@ -340,13 +340,13 @@ 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 + # Escribe el dataframe filtrado como una hoja de cálculo en formato .csv. No incluyas los nombres para las filas # Recuerda que el símbolo "." significa que la función va a usar el objeto que proveyó la operación de "pipe", que aquí es el dataframe filtrado para seleccionar solo el primer día de colección de datos write.csv(x = ., file = rfid_file, row.names = FALSE) ``` -Revisa que el archivo de prueba se creó adentro de la carpeta nueva de RFID, y luego puedes eliminar este archivo. +Revisa que el archivo de prueba se creó correctamente dentro de la carpeta nueva de RFID, y luego puedes eliminar este archivo. ```{r eval = FALSE} list.files(file.path(path, "Data/RFID"), pattern = ".csv$") @@ -358,9 +358,9 @@ file.remove(rfid_file) ``` -

    Practicar escribir un bucle

    +

    Practicar escribir un bucle (loop)

    -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. +Puedes repetir el código de arriba seis veces (tres veces por sensor) para escribir un `dataframe` por cada día de colección de datos por sensor. Pero es mejor evitar repetir el mismo código varias veces, porque cuando escribes código de esta forma es más difícil mantener archivos organizados de código y también es más fácil introducir errores mientras procesas y analizas datos. Cuando necesitas ejecutar el mismo código varias veces, es mejor escribir un bucle o `loop`. Escribir bucles es una habilidad muy importante y vamos a construir un bucle paso por paso. Vas a practicar cómo escribir un bucle con la función `lapply()`. ```{r} @@ -375,12 +375,12 @@ length(files) ``` -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. +Ahora puedes empezar a escribir la estructura de un bucle. En el código siguiente, el argumento `X` es el número de veces que se va a ejecutar el bucle. En este caso, `X` es un vector numérico de 1 por el largo del vector `files` y contiene los números uno y dos. Por ende, el bucle va a ejecutar dos veces, y cada valor consecutivo en `X` se va a usar en cada iteración correspondiente del bucle para escribir un archivo a la vez. -El argumento `FUN` es una función customizada que fue escrita usando la notación `function(x){}`. Todo el código adentro de las llaves curvas (abre y cierre) se ejecutará en cada iteración del bucle. El argumento `x` adentro de `funcion()` es el variable de iteración, o el variable que va a tomar un valor diferente del vector `X` en cada iteración. +El argumento `FUN` es una función customizada que fue escrita usando la notación `function(x){}`. Todo el código adentro de las llaves curvas (abre y cierre) se ejecutará en cada iteración del bucle. El argumento `x` adentro de `funcion()` es la variable de iteración, o la variable que va a tomar un valor diferente del vector `X` en cada iteración. ```{r} -# En este bucle el 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 +# En este bucle la variable de iteración x va a tomar cada valor del vector en el argumento X. Por ejemplo, en la primera iteración del bucle, x va a tomar el valor numérico de 1. En la segunda iteración del bucle, x va a tomar el valor numérico de 2. Para probar esta lógica puedes ejecutar el bucle siguiente, y ver el valor de x que se va a imprimir en cada iteración en la consola lapply(X = 1:length(files), FUN = function(x){ x @@ -389,13 +389,13 @@ lapply(X = 1:length(files), FUN = function(x){ ``` -Como puedes ver, el resultado de este bucle es un `list` con dos elementos. Cada elemento del `list` está rodeado de dos pares de corchetes ([[1]] y [[2]]) y contiene un vector con un largo de uno que contiene el valor del variable de iteración (uno y dos, respectivamente). +Como puedes ver, el resultado de este bucle es un `list` con dos elementos. Cada elemento del `list` está rodeado de dos pares de corchetes ([[1]] y [[2]]) y contiene un vector con un tamaño de 1 que contiene el valor de la variable de iteración (1 y 2, respectivamente). -El 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 el 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, o `x`, no existe como un objeto afuera de la función del bucle. Si imprimes `x` afuera del bucle, no se va a encontrar ese objeto. Si creaste un objeto `x` afuera del bucle, verás los contenidos de este objeto cuando imprimes `x`. O sea, escribir un bucle que usa `x` como la variable de iteración no afectará otras líneas de código que usan un objeto que se llama `x` afuera de la función, y viceversa. -El 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. +La variable de iteración de una función puede ser otras letras del alfabeto como `i`, `j`, `y`, `z`, o una combinación de múltiples letras, números, guiones bajos, o puntos, siempre y cuando el nombre de la variable empiece con una letra. -Una propiedad útil del 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: +Una propiedad útil de la variable de iteración es que puedes usarlo para indexar vectores, `dataframes`, u otros objetos que creaste afuera del bucle. Por ejemplo, puedes usar `x` e indexar con corchetes para imprimir el nombre de cada archivo que quieras crear: ```{r} lapply(X = 1:length(files), FUN = function(x){ @@ -406,7 +406,7 @@ lapply(X = 1:length(files), FUN = function(x){ ``` -También puedes modificar el código adentro de la función customizada para guardar cada archivo si metes la expresión `files[x]` adentro de la función `write.csv()` y usas una operación de `piping` para usar un `dataframe` como entrada para `write.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]` dentro de la función `write.csv()` y usas una operación de `piping` para usar un `dataframe` como entrada para `write.csv()`. ```{r eval = FALSE} lapply(X = 1:length(files), FUN = function(x){ @@ -419,14 +419,14 @@ lapply(X = 1:length(files), FUN = function(x){ ``` -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: +Deberías de ver dos resultados `NULL` en la consola si el bucle se ejecuta correctamente. Cuando revises los contenidos de la carpeta anidada de RFID, vas a poder ver que ambos archivos de `.csv` de prueba se escribieron en esta ubicación: ```{r} list.files(file.path(path, "Data/RFID")) ``` -Acabas de escribir dos hojas de cálculo usando un bucle, pero escribiste el mismo `dataframe` a cada hoja de cálculo. Para poder escribir un `dataframe` diferente a cada hoja de cálculo, puedes añadir el paso de filtrar el `dataframe` que aprendiste arriba. En el código 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`. +Acabas de escribir dos hojas de cálculo usando un bucle, pero escribiste el mismo `dataframe` a cada hoja de cálculo. Para poder escribir un `dataframe` diferente a cada hoja de cálculo, puedes añadir el paso de filtrar el `dataframe` que aprendiste arriba. En el código siguiente, también vas a crear otro objecto de vector que se llama `days` (días) y luego usarás la variable de iteración para filtrar `sim_dats_rfid` y escribir una hoja de cálculo por cada día en `days`. ```{r eval = FALSE} days <- c(1, 2, 3) @@ -443,7 +443,7 @@ lapply(X = 1:length(files), FUN = function(x){ ``` -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. +Puedes borrar estos archivos que creaste de prueba. En el código siguiente, vas a ver otro ejemplo de cómo buscar un patrón de texto mientras buscas evidencia de que los archivos de prueba se crearon (antes de borrarlos). La secuencia de caracteres que usas para el argumento `pattern` empieza con el símbolo de `^`, el cual significa que quieres buscar todos los archivos que *empiezan* con el patrón "test". También estás especificando que quieres devolver la ubicación (`path`) completa de cada archivo usando el argumento `full.names = TRUE`, para que la función `file.remove()` tenga toda la información que necesita para borrar estos archivos de prueba. ```{r eval = FALSE} rem_files <- list.files(file.path(path, "Data/RFID"), pattern = "^test", full.names = TRUE) @@ -479,22 +479,22 @@ 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 + # Escribe el dataframe filtrado en la hoja de cálculo correcta para el día actual write.csv(file = files[x], row.names = FALSE) })) ``` -En el código 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. +En el código de arriba, deberías de poder ver un cambio adicional que hicimos al bucle al rodearlo con la función `invisible()`. Esta función silencia el resultado que se imprime en la consola (que viste cuando ejecutaste trozos de código anteriores), donde el resultado de cada iteración de `lapply()` está rodeado de dos pares de corchetes, y luego un solo par de corchetes. `lapply()` es una función que devuelve un `list`, y cuando la función se ejecuta correctamente pero no hay resultados para imprimir (como cuando creas un archivo físico), la función debería de devolver valores de `NULL` que significan resultados vacíos. Este comportamiento se espera con nuestro uso de `lapply()` porque usamos la función para escribir archivos físicos y no para devolver resultados a la consola. Ya que puedes usar `list.files()` para revisar que `lapply()` se ejecutó bien, usar `invisible()` te ayudará a minimizar la cantidad de texto que tienes que revisar en tu consola. ```{r eval = FALSE} -# Los archivos nuevos de .csv para cada día de datos de RFID están presentes en el directorio esperado, se ve bien +# Los archivos nuevos de .csv para cada día de datos de RFID están presentes en el directorio esperado, muy bien list.files(file.path(path, "Data/RFID")) ``` -Ahora puedes eliminar los archivos que acabas de 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. +Ahora puedes eliminar los archivos que acabas de crear, porque vas a trabajar en escribir un bucle anidado que va a escribir automáticamente los datos de cada día para cada tipo de sensor. ```{r eval = FALSE} files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv") @@ -507,7 +507,7 @@ 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 más práctica escribiendo bucles, puedes escribir un bucle para guardar una hoja de cálculo para cada día de colección de datos de los sensores infrarrojo.

    Escribe un bucle anidado para crear hojas de cálculo

    @@ -521,7 +521,7 @@ dir.create(file.path(path, "Data", "IRBB")) Para lograr filtrar y escribir datos para ambos tipos de sensores a través de los días de colección de datos, vas a usar un tipo de objeto en R que se llama un `list`. Vas a usar estos `lists` adentro del bucle anidado: ```{r} -# Crea un list de los nombres de archivos customizados para guardar datos para cada tipo de sensor y día +# Crea un list de los nombres de archivos customizados para guardar datos para cada tipo de sensor por día files <- list( c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv"), c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv", "IRBB_simulated_Pair-01_2023_08_03.csv") @@ -531,7 +531,7 @@ glimpse(files) ``` -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()`. +Los `lists` son objetos útiles porque son muy flexibles. A diferencia de los vectores, una sola lista puede contener varios diferentes tipos de datos. A diferencia de un `dataframe`, los elementos de una lista no necesitan tener las mismas dimensiones. Los elementos de una lista también pueden ser diferentes tipos de objetos con estructuras diferentes. Por ejemplo, un solo `list` puede contener vectores, `dataframes`, y otros `lists`. El `list` que creaste arriba tiene dos elementos, y cada elemento es un vector de tres secuencias de caracteres que contienen los nombres de archivos que especificaste usando la función `c()`. Puedes indexar un `list` de una forma parecida a indexar vectores y `dataframes`, pero usar un par o dos pares de corchetes devuelve resultados diferentes: ```{r} @@ -540,7 +540,7 @@ Puedes indexar un `list` de una forma parecida a indexar vectores y `dataframes` files[1] glimpse(files[1]) -# 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) +# Usar dos pares de corchetes devuelve solo el elemento actual, o sea, elimina la estructura de list para demostrar la estructura original de ese elemento (aquí este elemento es un vector) files[[1]] glimpse(files[[1]]) @@ -572,7 +572,7 @@ files[["RFID"]] ``` -Los `lists` son tipos de objetos muy útiles para operaciones con bucles anidados. Por ejemplo, si quieres escribir una hoja de cálculo por tipo de sensor y por día, vas a necesitar 1) un bucle para iterar a través de los tipos de sensores y 2) un bucle para iterar a través de días de colección de datos para cada sensor. Puedes usar listas para crear estructuras anidadas de datos que puedes proveer a un bucle anidado, para asegurar que cada capa del bucle 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`): +Los `lists` son tipos de objetos muy útiles para operaciones con bucles anidados. Por ejemplo, si quieres escribir una hoja de cálculo por tipo de sensor y por día, vas a necesitar 1) un bucle para iterar a través de los tipos de sensores y 2) un bucle para iterar a través de días de colección de datos para cada sensor. Puedes usar listas para crear estructuras anidadas de datos que puedes proveer a un bucle anidado, para asegurar que cada capa del bucle se ejecuta de la forma que esperas. Por ejemplo, el `list` que se llama `files` refleja el bucle anidado que necesitas, porque los archivos están ordenados primero por el tipo de sensor (cada elemento del `list`) y luego por día de colección de datos (cada elemento del vector adentro de cada elemento del `list`): ```{r} # Crea un vector de los nombres de los sensores @@ -588,7 +588,7 @@ files <- list( files -# Crea un list de los paths para los archivos de cada sensor. Usarás estos paths adentro de los bucles +# Crea un list de los paths para los archivos de cada sensor. Usarás estos paths dentro de los bucles file_dirs <- list( `RFID` = file.path(path, "Data/RFID"), `IRBB` = file.path(path, "Data/IRBB") @@ -605,7 +605,7 @@ days <- list( days -# Ahora necesitas crear un list de los dataframes que quieres usar para crear los archivos. Como ya sabes cómo filtrar dataframes por día, ese código puede ir adentro de los bucles para minimizar la cantidad de código que escribes. Aquí vas a especificar el dataframe que usarás en las operaciones de filtrar para cada tipo de sensor +# Ahora necesitas crear un list de los dataframes que quieres usar para crear los archivos. Como ya sabes cómo filtrar dataframes por día, ese código puede ir adentro de los bucles para minimizar la cantidad de código que escribes. Aquí vas a especificar el dataframe que usarás en las operaciones de filtro para cada tipo de sensor dats <- list( `RFID` = sim_dats_rfid, `IRBB` = sim_dats_irbb @@ -617,13 +617,13 @@ glimpse(dats) Cuando hayas establecido las estructuras de los datos para informar la operación del bucle, puedes escribir el bucle anidado mismo. Este bucle anidado es una estructura compleja, y por ende es útil probar el bucle con valores determinados de cada variable de iteración (`x` y `y` abajo). -Después de escribir este bucle pero antes de ejecutar la estructura completa del bucle, deberías de probar el código adentro de cada capa del bucle. Para lograr esto, puedes inicializar los valores de 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). +Después de escribir este bucle pero antes de ejecutar la estructura completa del bucle, deberías de probar el código adentro de cada capa del bucle. Para lograr esto, puedes inicializar los valores de las variables de iteración y luego correr el código adentro de cada bucle (pero sin ejecutar el bucle entero). Esta forma de revisar el bucle es equivalente a congelar el bucle en el tiempo, para que puedas ver el resultado del código para una sola iteración (abajo vas a ver la primera iteración para cada capa del bucle cuando ambos `x` y `y` tiene el valor numérico de uno). -Para lograr este tipo de chequeo, deberías de ejecutar el código para "congelar" los 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. +Para lograr este tipo de chequeo, deberías de ejecutar el código para "congelar" las variables de iteración en la primera iteración de cada bucle ( o sea, inicializar `x` y `y` con el valor de uno). Luego deberías de ejecutar el código dentro de cada bucle, empezando con la creación de `days_tmp`, luego las operaciones de indexar y filtrar el `dataframe`, y luego filtrar los nombres de los archivos. No deberías de ejecutar las líneas con `lapply()` porque quieres evitar ejecutar los bucles completos hasta que estés segura que el código adentro de cada bucle funciona de la forma que esperas. -*Nota importante*: Arriba aprendiste que los 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 los variables de iteración que inicialices afuera del bucle se van a respetar. +*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): +Si observas el código dentro de cada bucle, deberías de ver que entre el bucle exterior y el bucle interior, vas a usar el nombre del sensor para la primera iteración del bucle exterior ("RFID") para determinar los días para las iteraciones del bucle interior (en `days_tmp`). Adentro del bucle interior, vas a filtrar el `list` de `dataframes` por sensor, y luego por los días que quieres por sensor. Luego vas a indexar el nombre del archivo para el tipo de sensor actual y el día actual con una combinación de indexar la lista de nombres de los archivos con uno o dos pares de corchetes. Abajo, las líneas que abren y cierran los bucles mismos están comentados para guiar tu chequeo (o sea para guiar cuales líneas de código deberías de ejecutar): ```{r eval = FALSE} # Congela las variables de iteración para el chequeo @@ -645,13 +645,13 @@ y <- 1 # 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 + # Para obtener el dataframe del tipo de sensor actual, puedes usar "x" adentro de dos pares de corchetes para extraer el dataframe del list dats[[x]] %>% - # Para filtrar el dataframe por el día actual puedes usar y para indexar el vector temporal de días (para extraer un solo elemento de este vector) + # 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 el variable y para indexar este vector con un par de corchetes para acceder el nombre de archivo correcto para esta iteración + # 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] @@ -665,20 +665,20 @@ y <- 1 ``` -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: +Ahora deberías de tener una mejor idea sobre cómo cada bucle opera a través de diferentes estructuras de datos para realizar la tarea que quieres (en este caso, escribir una hoja de cálculo por tipo de sensor y por día). Más adelante puedes modificar la estructura entera de los bucles para reemplazar las líneas que escribiste y realizar el chequeo con las operaciones finales que quieres realizar: ```{r eval = FALSE} # El bucle exterior: empieza con iterar a través de los sensores invisible(lapply(1:length(sensors), function(x){ # Para obtener los días correctos para el tipo de sensor actual, deberías de indexar el list nombrado de días - # Este paso de indexar es importante para que el bucle interior ejecute correctamente + # Este paso de indexar es importante para que el bucle interior se ejecute correctamente days_tmp <- days[[sensors[x]]] # El bucle interior: itera a través de días para cada tipo de sensor para escribir una hoja de cálculo por tipo de sensor y día lapply(1:length(days_tmp), function(y){ - # Usa el variable x para acceder el dataframe para el tipo de sensor actual y luego usar el variable y para filtrar este dataframe por el día actual + # Usa la variable "x" para acceder el dataframe para el tipo de sensor actual y luego usa la variable "y" para filtrar este dataframe por el día actual dats[[x]] %>% # Filtra el dataframe por el día actual dplyr::filter(day == days_tmp[y]) %>% @@ -701,4 +701,4 @@ 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. \ No newline at end of file +En este tutorial aprendiste más sobre cómo filtrar `dataframes` y guardar estos objetos como hojas de cálculo, y cómo usar bucles (loops) de una capa y estructuras de bucles anidados. En el siguiente tutorial vas a usar las hojas de cálculo de los datos simulados de RFID y sensores infrarrojo que creaste para empezar a procesar y analizar datos con las funciones de ABISSMAL. \ No newline at end of file diff --git a/R/vignettes/Tutorial_04_GuardarDatos.html b/R/vignettes/Tutorial_04_GuardarDatos.html index c099883..19d6af1 100644 --- a/R/vignettes/Tutorial_04_GuardarDatos.html +++ b/R/vignettes/Tutorial_04_GuardarDatos.html @@ -2003,10 +2003,10 @@

    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 el variable de iteración, o el variable que va +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 el 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
    +
    # 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
    @@ -2020,22 +2020,22 @@ 

    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 del variable de iteración (uno y dos, +de uno que contiene el valor dla variable de iteración (uno y dos, respectivamente).

    -

    El variable de iteración, o x, no existe como un objeto +

    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 el variable de iteración no afectará otras líneas de +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.

    -

    El variable de iteración de una función puede ser otras letras del +

    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 del variable de iteración es que puedes usarlo +

    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 @@ -2342,7 +2342,7 @@

    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” los variables de iteración en la primera iteración de cada +“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 @@ -2351,11 +2351,11 @@

    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 los variables de +

    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 los variables de iteración que inicialices afuera del bucle se van a +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 @@ -2394,7 +2394,7 @@

    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 el variable y para indexar este vector con un par de corchetes para acceder el nombre de archivo correcto para esta iteración + # 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] @@ -2421,7 +2421,7 @@

    # 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 el variable x para acceder el dataframe para el tipo de sensor actual y luego usar el variable y para filtrar este dataframe por el día actual + # 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]) %>% From 65fee8608ce2c4fad3df450cf68c293d22910954 Mon Sep 17 00:00:00 2001 From: lastralab Date: Fri, 26 Apr 2024 15:50:58 -0400 Subject: [PATCH 68/69] [PCT-472] edited tutorial 05 --- R/vignettes/README.md | 2 +- ...utorial_05_ProcesarDatos_CrearGraficas.Rmd | 110 +++++++++--------- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/R/vignettes/README.md b/R/vignettes/README.md index 929ec83..9f02dc7 100644 --- a/R/vignettes/README.md +++ b/R/vignettes/README.md @@ -36,7 +36,7 @@ Nuestro objetivo es diseminar habilidades de programación en R en un contexto b 4. Guardar los datos simulados y aprender cómo escribir datos a archivos físicos en tu computadora, manipular `dataframes` con R base y el `tidyverse`, y escribir bucles (`loops`) sencillos y complejos -5. Trabajar en el `pipeline` de procesar datos con ABISSMAL mientras aprendes cómo acceder y usar funciones customizadas, y también cómo visualizar marcas de tiempo en una figura de código de barras +5. Trabajar en el `pipeline` de procesar datos con ABISSMAL mientras aprendes cómo acceder y usar funciones customizadas, y también cómo visualizar marcas de tiempo en una gráfica de barras 6. Terminar el `pipeline` de análisis de datos de ABISSMAL mientras se emplean las habilidades de programación que practicaste en los tutoriales anteriores, y al final vas a crear una figura de código de barras más compleja y refinada diff --git a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd index 9c351af..6760e8a 100644 --- a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd +++ b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd @@ -22,7 +22,7 @@ Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por T

    Resumen del tutorial y objetivos de aprendizaje

    -En este quinto tutorial, vas a empezar a usar las detecciones simuladas de movimientos de animales en el `pipeline` de análisis de datos de ABISSMAL, incluyendo combinar los datos originales a través de días y procesar o limpiar los datos originales. También vas a crear gráficas de los datos procesados. Vas a continuar a usar habilidades que aprendiste en los tutoriales anteriores, y vas a aprender nuevas habilidades cómo: +En este quinto tutorial, vas a empezar a usar las detecciones simuladas de movimientos de animales en el `pipeline` de análisis de datos de ABISSMAL, incluyendo combinar los datos originales a través de días y procesar o limpiar los datos originales. También vas a crear gráficas de los datos procesados. Vas a continuar usando habilidades que aprendiste en los tutoriales anteriores, y vas a aprender nuevas habilidades cómo: 1. Acceder funciones customizadas 2. Usar funciones customizadas @@ -43,28 +43,28 @@ path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Inicializa un objeto con

    Cargar las funciones de ABISSMAL

    -Las funciones customizadas de R en ABISSMAL están guardadas en archivos físicos (extensión .R) adentro de la versión local del repositorio en tu computadora (que descargaste en el primer tutorial). Para poder usar las funciones de ABISSMAL, vas a necesitar cargar los archivos físicos de R para que las funciones estén disponibles en tu ambiente global. En el código abajo, vas a usar la función `source()` para cargar tres de las cinco funciones primarias de ABISSMAL, y también un archivo que contiene funciones de apoyo: +Las funciones customizadas de R en ABISSMAL están guardadas en archivos físicos (extensión .R) adentro de la versión local del repositorio en tu computadora (que descargaste en el primer tutorial). Para poder usar las funciones de ABISSMAL, vas a necesitar cargar los archivos físicos de R para que las funciones estén disponibles en tu ambiente global. En el código de abajo, vas a usar la función `source()` para cargar tres de las cinco funciones primarias de ABISSMAL, y también un archivo que contiene funciones de apoyo: ```{r} # Carga la función que combina los datos originales source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/combine_raw_data.R") -# Carga la función que detecta eventos de posa en los datos originales +# Carga la función que detecta eventos de pose en los datos originales source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_perching_events.R") # Carga la función que procesa los datos originales source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/preprocess_detections.R") -# Carga un archivo con funciones de apoyo que cada función arriba requiere +# Carga un archivo con funciones de apoyo que cada función anterior que requiera source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") ```

    Accede información sobre las funciones de ABISSMAL

    -Después de ejecutar las líneas de código 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. +Después de ejecutar las líneas de código anteriores, deberías de ver que una colección entera de funciones ahora está disponible en tu ambiente global (revisa la pestaña de `Environment`). Las funciones que empiezan con `check_` son funciones de apoyo. Si haces scroll hacia abajo, puedes ver que tres de las funciones primarias de ABISSMAL (`combine_raw_data`, `detect_perching_events`, `preprocess_detections`) también están disponibles en tu ambiente global. En la columna al lado derecho de los nombres de las funciones también podrás ver algo de información sobre los argumentos de cada función. -Para obtener más información sobre cada una de las tres funciones primarias, puedes hacer clic en el ícono blanco cuadrado a la mera derecha de cada función en la pestaña de `Environment`, o ejecutar el código `View(nombre_de_la_funcion)`. Este comando debería de abrir el archivo de la función actual en una pestaña nueva adentro de tu panel de fuente. En el archivo de cada función, vas ver líneas de documentación que empiezan con los símbolos "`#@", luego el nombre de la función y una descripción, y luego una descripción de cada argumento (parámetro) para la función. Si haces scroll hacia abajo, podrás ver una sección con detalles sobre la función misma, incluyendo la información que devuelve. Esta documentación está escrita 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. +Para obtener más información sobre cada una de las tres funciones primarias, puedes hacer clic en el ícono blanco cuadrado a la mera derecha de cada función en la pestaña de `Environment`, o ejecutar el código `View(nombre_de_la_funcion)`. Este comando debería de abrir el archivo de la función actual en una pestaña nueva adentro de tu panel de fuente. En el archivo de cada función, vas ver líneas de documentación que empiezan con los símbolos "`#@", luego el nombre de la función y una descripción, y luego una descripción de cada argumento (parámetro) para la función. Si haces scroll hacia abajo, podrás ver una sección con detalles sobre la función misma, incluyendo la información que devuelve. Esta documentación está escrita solo en inglés por el momento. Después de las líneas de documentación verás el código de la función misma.

    Combinar los datos originales

    @@ -76,7 +76,7 @@ Vas a proveerle información a la función `combine_raw_data()` a través de los * `path` es tu directorio general de trabajo -* `data_dir` es la carpeta que contiene datos adentro de tu directorio de trabajo +* `data_dir` es la carpeta que contiene archivos dentro de tu directorio de trabajo * `out_dir` es la carpeta donde quieres guardar la hoja de cálculo de los datos originales combinados. La función creará esta carpeta si no existe en tu computadora @@ -97,7 +97,7 @@ list.files(file.path(path, "Data/raw_combined"), pattern = ".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: +Puedes proveer el archivo de datos combinados de RFID ("combined_raw_data_RFID.csv") a R para revisar la estructura de esta hoja de cálculo: ```{r} rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) @@ -106,11 +106,11 @@ glimpse(rfid_data) ``` -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`). +R lee esos datos y crea un objeto `dataframe`. Deberías de poder ver que hay columnas nuevas creadas por la función, como la columna `data_type`. Para esta hoja de cálculo, las columnas `sensor_id` y `data_type` contienen la misma información, pero es útil tener columnas separadas para poder estar al tanto de la identidad única del sensor y el tipo de sensor cuando usas múltiples sensores del mismo tipo (por ejemplo, dos pares de sensores infrarrojo tendrán números de identidad únicos en la columna de `sensor_id`). -La función `combine_raw_data()` también 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. +La función `combine_raw_data()` también crea una columna nueva de marcas de tiempo en formato `POSIXct` para los siguientes pasos de procesar y analizar datos, pero a la vez mantiene la columna original de marcas de tiempo. Por consiguiente, la función añadió columnas para indicar la etapa de procesar datos y la fecha en que combinaste los datos originales. Finalmente, si revisas las carpetas con los datos originales de RFID, verás que las hojas de cálculo originales por día se preservaron y no fueron ni eliminados ni sobrescritos. -También puedes ejecutar `combine_raw_data()` con los datos originales de múltiples sensores a la vez 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: +También puedes ejecutar `combine_raw_data()` con los datos originales de múltiples sensores a la vez proveyendo un vector con las etiquetas de estos sensores al argumento `sensores`. Los datos para cada tipo de sensor se guardarán en hojas de cálculo separadas, y así evitas tener que escribir el mismo código varias veces para ejecutar `combine_raw_data()` para múltiples sensores: ```{r} combine_raw_data(sensors = c("RFID", "IRBB"), path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") @@ -124,18 +124,18 @@ list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") ``` -

    Detectar eventos de posar

    +

    Detectar eventos de pose

    -Puedes usar los datos originales combinados de sensores diferentes en las siguientes funciones de ABISSMAL para empezar a hacer inferencias de comportamiento de los datos de detección de movimiento. Por ejemplo, puedes detectar eventos de 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. +Puedes usar los datos originales combinados de sensores diferentes en las siguientes funciones de ABISSMAL para empezar a hacer inferencias de comportamiento de los datos de detección de movimiento. Por ejemplo, puedes detectar eventos de pose en los datos originales de RFID con la función `detect_perching_events()`. Puedes leer más sobre cada argumento en el archivo de R que contiene esta función. ```{r} detect_perching_events(file_nm = "combined_raw_data_RFID.csv", threshold = 2, run_length = 2, sensor_id_col_nm = "sensor_id", timestamps_col_nm = "timestamp_ms", PIT_tag_col_nm = "PIT_tag", rfid_label = "RFID", general_metadata_cols = c("chamber_id", "sensor_id"), path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", out_file_prefix = "perching_events", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") ``` -`detect_perching_events()` puede operar en sólo un archivo y tipo de sensor a la vez. La función automáticamente crea una carpeta que se llama "processed" (para datos procesados) y guardará un archivo de `.csv` 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). +`detect_perching_events()` puede operar en sólo un archivo y tipo de sensor a la vez. La función automáticamente crea una carpeta que se llama "processed" (para datos procesados) y guardará un archivo de `.csv` dentro de esa carpeta si efectivamente pudo detectar eventos de pose usando el umbral temporal actual (`threshold`, en segundos), y la duración de secuencias de detección actual (`run_length`, en número de detecciones). -Cuando creamos datos simulados en los últimos tutoriales, simulaste eventos de posar en los datos de RFID. Pudiste recuperar estos eventos de posar usando `detect_perching_events()`? +Cuando creamos datos simulados en los últimos tutoriales, simulaste eventos de pose en los datos de RFID. Pudiste recuperar estos eventos de pose usando `detect_perching_events()`? ```{r} perching <- read.csv(file.path(path, "Data", "processed", "perching_events_RFID.csv")) @@ -144,13 +144,13 @@ glimpse(perching) ``` -`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: +`detect_perching_events()` identificó un total de seis eventos de pose, que es el mismo número que simulaste en el tutorial anterior (dos eventos de pose por día a través de tres días). Puedes revisar los valores dentro del `dataframe` para ver más información sobre estos eventos de pose: ```{r} -# Las marcas de tiempo cuando cada evento de posar empezó +# Las marcas de tiempo cuando cada pose empezó perching$perching_start -# Las marcas de tiempo cuando cada evento de posar terminó +# Las marcas de tiempo cuando cada pose terminó perching$perching_end # La etiqueta única de PIT que contiene información sobre la identidad del individuo que estuvo posando en la antena de RFID @@ -165,13 +165,13 @@ 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. +La información de arriba te dice que hubo dos eventos de pose a las 8:00 cada día, y dos eventos de pose a las 11:30 cada día (como esperábamos). La etiqueta de PIT para cada individuo se detectó una vez por día, por ende, cada individuo realizó un evento de pose cada día. -Detectar eventos de 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. +Detectar eventos de pose no es un requisito en el `pipeline` de análisis de ABISSMAl, pero puede ser un paso útil para obtener la mayor cantidad de información que puedes de los datos originales antes de filtrar las detecciones en el siguiente paso de procesar o limpiar los datos originales.

    Procesar los datos originales

    -Cuando hayas detectado los eventos de 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. +Cuando hayas detectado los eventos de pose en los datos originales, puedes seguir con procesar o limpiar los datos originales con la función `preprocess_detections()`. Los datos originales a veces contienen múltiples detecciones separadas por poco tiempo (como las detecciones de RFID cuando un individuo está posando en la antena), y estas múltiples detecciones pueden causar ruido cuando tratas de hacer inferencias de comportamiento con datos colectados por múltiples sensores. Cuando le provees "thin" al argumento `mode`, `preprocess_detections()` elimina detecciones separadas por un periodo corto de tiempo (usando el valor del umbral temporal en segundos para el argumento `thin_threshold`), y devuelve datos filtrados de detecciones que todavía representan eventos discretos de movimiento. `preprocess_detections()` opera en un solo tipo de sensor a la vez: ```{r} @@ -187,7 +187,7 @@ list.files(file.path(path, "Data/processed")) ``` -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: +Puedes darle este archivo a R para que lo lea y vea su estructura. Deberías de poder ver menos filas en este `dataframe` comparado con la hoja de cálculo de los datos originales: ```{r} rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) @@ -209,13 +209,13 @@ glimpse(irbb_pp) ``` -

    Visualizar datos de RFID en una gráfica de código de barras

    +

    Visualizar datos de RFID en una gráfica de barras

    Ahora que combinaste y procesaste los datos originales por sensor, es hora de visualizar estos conjuntos de datos diferentes. Hacer gráficas mientras escribes código es importante para generar figures de alta calidad para publicaciones y presentaciones y también para revisar tu proceso de analizar datos. -En el código 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. +En el código de abajo, vas a aprender cómo usar funciones del paquete `ggplot2` para hacer una gráfica de barras con los datos originales y procesados de RFID. -Puedes empezar 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`. +Puedes empezar dándole a R los datos originales y procesados de RFID, los eventos de posar de RFID, y convertir las marcas de tiempo al formato `POSIX`. ```{r} rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) %>% @@ -224,14 +224,14 @@ rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFI 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 + # La expresión "-desc()" dentro de la función arrange() indica que las marcas de tiempo se ordenarán de menos a más recientes dplyr::arrange(-desc(timestamp_ms)) -# Deberías de ver que la columna timestamp_ms con las marcas de tiempo está en el formato "dttm", significando que la conversión a formato POSIX se realizó bien +# Deberías de ver que la columna timestamp_ms con las marcas de tiempo está en el formato "dttm", significando que la conversión a formato POSIX se realizó correctamente glimpse(rfid_raw) rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) %>% - # Tienes que convertir las marcas de tiempo al formato POSIX cada vez que los datos se leen a R para hacer gráficas + # Tienes que convertir las marcas de tiempo al formato POSIX cada vez que los datos se le dan a leer a R para hacer gráficas dplyr::mutate( timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) ) %>% @@ -240,7 +240,7 @@ rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv" glimpse(rfid_pp) rfid_perch <- read.csv(file.path(path, "Data/processed/perching_events_RFID.csv")) %>% - # Tienes que convertir las marcas de tiempo de inicio y final de cada evento de posar al formato POSIX cada vez que los datos se leen a R para hacer gráficas + # Tienes que convertir las marcas de tiempo de inicio y final de cada evento de pose al formato POSIX cada vez que los datos se dan a R para hacer gráficas dplyr::mutate( perching_start = as.POSIXct(format(as.POSIXct(perching_start, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")), perching_end = as.POSIXct(format(as.POSIXct(perching_end, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) @@ -272,9 +272,9 @@ glimpse(rfid_combined) ``` -Vas a construir la gráfica con estos datos usando funciones de `ggplot2`, un paquete que es parte del `tidyverse` pero que también se puede instalar y usar afuera del `tidyverse`. Puedes revisar este [enlace](https://ggplot2.tidyverse.org/) que tiene más recursos (en inglés) para aprender cómo usar la notación de `ggplot` para hacer diferentes tipos de gráficas. Estos recursos incluyen secciones de tres libros diferentes con ejercicios para practicar hacer gráficas sencillas o complejas, y también un curso en línea y un seminario en línea. Puedes encontrar otros recursos en español en línea, como esta [guia para `ggplot2`](https://raw.githubusercontent.com/rstudio/cheatsheets/main/translations/spanish/data-visualization_es.pdf). +Vas a construir la gráfica con estos datos usando funciones de `ggplot2`, un paquete que es parte del `tidyverse` pero que también se puede instalar y usar fuera del `tidyverse`. Puedes revisar este [enlace](https://ggplot2.tidyverse.org/) que tiene más recursos (en inglés) para aprender cómo usar la notación de `ggplot` para hacer diferentes tipos de gráficas. Estos recursos incluyen secciones de tres libros diferentes con ejercicios para practicar hacer gráficas sencillas o complejas, y también un curso en línea y un seminario en línea. Puedes encontrar otros recursos en español también, como esta [guia para `ggplot2`](https://raw.githubusercontent.com/rstudio/cheatsheets/main/translations/spanish/data-visualization_es.pdf). -El paquete de `ggplot2` tiene una notación única para construir gráficas, en 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 `+`. +El paquete de `ggplot2` tiene una notación única para construir gráficas, en la cual empiezas haciendo la gráfica llamando la función `ggplot()` y luego añades características añadiendo capas de otras funciones de `ggplot2` con el símbolo de `+`. Si llamas `ggplot()`, verás que la función inmediatamente dibuja una gráfica vacía en tu panel de `Plots` (gráficas) en RStudio. ```{r} @@ -290,9 +290,9 @@ 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. +Necesitarás añadir otras funciones estéticas a esta capa fundamental de la gráfica para poder ver tus datos. Las funciones que usarás para añadir detalles estéticos a la gráfica vacía dependerán del tipo de gráfica que quieres crear. En este ejemplo, vas a generar una gráfica barras, en la cual cada marca de tiempo está representada por una línea vertical delgada. Las gráfica barras pueden ser gráficas útiles cuando trabajas con marcas de tiempo, porque la información más importante se contiene en una dimensión (el tiempo en el eje "x"). Si fueras a resumir el número de marcas de tiempo grabado cada día, sería mejor hacer una gráfica de líneas. -Puedes añadir la función `geom_segment()` como la siguiente capa encima de la capa fundamental de la gráfica. `geom_segment()` facilita añadir línea a una gráfica, y las líneas pueden comunicar información en una o dos dimensiones (por su ancho en el eje x y su altura en el eje y). +Puedes añadir la función `geom_segment()` como la siguiente capa encima de la capa fundamental de la gráfica. `geom_segment()` facilita añadir línea a una gráfica, y las líneas pueden comunicar información en una o dos dimensiones (por su ancho en el eje "x" y su altura en el eje "y"). ```{r} ggplot(data = rfid_combined) + @@ -305,7 +305,7 @@ ggplot(data = rfid_combined) + ``` -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. +En el código anterior, `geom_segment()` añade una línea vertical a la gráfica para cada detección en el `dataframe` completo. Usando el argumento `color` adentro de `geom_segment()`, y proveyendo el nombre de la columna que contiene las etiquetas de los conjuntos de datos, le comunicaste a la función que las líneas deberían de tener colores que corresponden al conjunto particular de datos. El argumento `color` tiene que estar adentro de la función `aes()` (que controla la estética de esta capa de información) para que esta asignación de colores se realice por el conjunto de datos. Los colores de las líneas se asignarán automáticamente por `ggplot` usando los colores por defecto del paquete, pero puedes cambiar estos colores usando la función `scale_color_manual()`: ```{r} @@ -324,11 +324,11 @@ ggplot(data = rfid_combined) + 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: +Las funciones de `ggplot` usan un tipo de datos que se llaman `factors` para determinar automáticamente la estética de la gráfica, como el método de asignar colores que usaste arriba con `geom_segment()`. Las columnas (o vectores) en formato `factor` se ven como columnas de tipo `character` (en que cada fila contiene una secuencia de caracteres), pero R guarda los valores únicos de cada columna como números enteros y luego guarda los valores únicos de las secuencias de caracteres en una propiedad que se llama `levels` ("niveles" o "categorías"). Puedes cambiar el orden en que los valores únicos de una columna en formato `factor` se añaden a la gráfica cuando cambias el orden de los `levels` de la columna: ```{r} # Cambia la columna dataset al tipo de datos "factor" -# Cuando especificas que el valor de "raw" ("original") 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 +# Cuando especificas que el valor de "raw" ("original") vaya primero en el argumento de levels, estás reorganizando los niveles de la columna para que este valor salga primero en la leyenda rfid_combined <- rfid_combined %>% dplyr::mutate( dataset = factor(dataset, levels = c("raw", "pre-processed")) @@ -337,7 +337,7 @@ rfid_combined <- rfid_combined %>% # La columna de dataset ahora es tipo "fct" o "factor" glimpse(rfid_combined) -# Los niveles de la columna ahora están ordenados para que el valor "raw" venga primero, en vez de estar en orden alfabético +# Los niveles de la columna ahora están ordenados para que el valor "raw" vaya primero, en vez de estar en orden alfabético levels(rfid_combined$dataset) ``` @@ -357,7 +357,7 @@ ggplot(data = rfid_combined) + ``` -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: +En la gráfica que acabas de hacer, puede resultar difícil distingüir entre las líneas para cada conjunto de datos. Puedes usar la función `facet_wrap()` para dividir los conjuntos de datos en paneles diferentes: ```{r} ggplot(data = rfid_combined) + @@ -375,13 +375,13 @@ ggplot(data = rfid_combined) + ``` -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. +Acabas de crear paneles diferentes dentro de esta gráfica y cada panel contiene información de un solo conjunto de datos. Con este cambio estructural también alineaste los paneles en el mismo eje "x" para que sea más fácil de comparar patrones temporales. -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: +Hasta este punto es difícil ver cómo los dos conjuntos de datos (originales y procesados) son diferentes. Puedes filtrar el `dataframe` con funciones del `tidyverse` para visualizar solo las primeras dos detecciones para cada conjunto de datos: ```{r} ggplot(data = rfid_combined %>% - # Crea grupos por las categorías o levels en la columna dataset + # Crea grupos para las categorías o levels en la columna dataset group_by(dataset) %>% # Selecciona las primeras dos filas para cada grupo slice(1:2) %>% @@ -396,19 +396,19 @@ ggplot(data = rfid_combined %>% 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 + # El símbolo de ~ significa "por", así que ahora estás creando un panel por cada valor único (o categoría) en la columna dataset facet_wrap(~ dataset, nrow = 2, strip.position = "left") ``` Ahora deberías de ver que la segunda marca de tiempo en los datos originales se eliminó del conjunto de datos procesados (fue filtrado usando el umbral temporal en `preprocess_detections`). -Luego puedes añadir los datos de eventos de 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. +Luego puedes añadir los datos de eventos de pose en el `dataframe` `rfid_perch`, que no combinaste con los otros conjuntos de datos. A diferencia de `rfid_raw` y `rfid_pp`, este conjunto de datos tiene dos columnas de marcas de tiempo que contienen información sobre el inicio y el fin de cada evento de pose. Puedes añadir este conjunto de datos a la gráfica volviendo a llamar la función `geom_segment()`. Usarás esta capa de `geom_segment()` para añadir líneas que contienen información temporal sobre cuándo empezaron los eventos de pose y cuándo terminaron. ```{r} ggplot(data = rfid_combined) + - # Añade una línea vertical para cada marca de tiempo + # Añade una línea vertical por cada marca de tiempo geom_segment( aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), linewidth = 0.3 @@ -419,7 +419,7 @@ ggplot(data = rfid_combined) + # 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 + # Añade los eventos de pose geom_segment( data = rfid_perch, aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5), @@ -429,9 +429,9 @@ ggplot(data = rfid_combined) + ``` -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. +En el código anterior con respecto a `geom_segment()`, especificaste que querías añadir otro conjunto de datos a la gráfica cuando usaste el argumento `data`. Los argumentos `x` y `y` determinan donde va a empezar cada línea en ambos ejes de la gráfica, respectivamente. También vas a tener que especificar donde quieres que cada línea termina en cada eje. En el eje x, indicaste que quieres que la línea empiece y termine cuando los eventos de pose empezaron y terminaron, proveyendo a la columna `perching_start` al argumento `x` y `perching_end` al argumento `xend`. En el eje "y", los números que usaste para los argumentos `y` y `yend` determinan donde las líneas para los eventos de pose se van a dibujar, que en este caso es justamente arriba de las líneas para los otros conjuntos de datos. Las líneas para los eventos de pose se dibujaron como otra capa de información encima de cada panel de la gráfica por defecto. -Puedes hacer unos cambios a la gráfica para que sea más fácil de interpretar. Puedes cambiar la posición de la leyenda usando el argumento `legend.position` adentro de la función general de `theme()`. Abajo puedes guardar la gráfica adentro de un objeto, para que no tengas que escribir todo el código de la gráfica de nuevo cuando quieres añadirle más información. +Puedes hacer unos cambios a la gráfica para que sea más fácil de interpretar. Puedes cambiar la posición de la leyenda usando el argumento `legend.position` adentro de la función general de `theme()`. Abajo puedes guardar la gráfica adentro de un objeto, para que no tengas que escribir otra vez el código de la gráfica cada vez que quieres añadirle más información. ```{r} gg <- ggplot(data = rfid_combined) + @@ -444,10 +444,10 @@ gg <- ggplot(data = rfid_combined) + 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 + # El símbolo de ~ significa "por", así que estás ahora creando un panel por cada valor único (o categoría) en la columna dataset facet_wrap(~ dataset, nrow = 2, strip.position = "left") + - # Añade los eventos de posar + # Añade los eventos de pose geom_segment( data = rfid_perch, aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5), @@ -463,7 +463,7 @@ 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). +Ahora puedes hacer ajustes menores para seguir mejorando la gráfica, incluyendo cambiar los títulos de los ejes para que sean más informativos, cambiar el color de fondo a blanco, y eliminar el texto en el eje "y" así como las líneas en el eje "y", porque este eje no contiene información para interpretación de los datos (o sea, la altura de cada línea no contiene información para interpretación). ```{r} gg <- gg + @@ -471,13 +471,13 @@ 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 + # 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 + # Usa funciones de estética para eliminar el texto en el eje "y" y también las líneas en este eje theme( axis.text.y = element_blank(), axis.ticks.y = element_blank(), @@ -488,7 +488,7 @@ 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. +Puedes guardar las gráficas que creas en R como archivos físicos. Abajo usarás la función `ggsave()` para escribir la gráfica que hiciste arriba y guardarlo en tu computadora. ```{r} gg @@ -498,6 +498,6 @@ ggsave(file.path(path, "raw_processed_perching.tiff"), width = 8, height = 6, un ``` -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. +Puedes continuar con modificaciones a este archivo para crear una figura de alta calidad para una publicación. Por ejemplo, puedes cambiar el tamaño final del archivo (`width` o "el ancho", `height` o "la altura"), así como también la resolución en píxeles (`dpi`). Incluso puedes cambiar el tamaño de texto en cada eje o del título de cada eje, o la posición de la leyenda mientras determinas el tamaño final de la imagen. -En el siguiente tutorial vas a continuar analizando datos con ABISSMAL y vas a crear una gráfica de código de barras más compleja y refinada. \ No newline at end of file +En el siguiente tutorial vas a continuar analizando datos con ABISSMAL y vas a crear una gráfica de barras más compleja y refinada. \ No newline at end of file From 3bbff3089472170ee1e75d0d7d47130bc69cd9bb Mon Sep 17 00:00:00 2001 From: lastralab Date: Fri, 26 Apr 2024 16:34:22 -0400 Subject: [PATCH 69/69] [PCT-472] edited tutorial 06 --- R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd | 154 +++++++++--------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd index 1483ea2..e6c68e6 100644 --- a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd +++ b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd @@ -22,7 +22,7 @@ Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por T

    Resumen del tutorial y objetivos de aprendizaje

    -En este sexto y último tutorial, vas a terminar de usar las detecciones simuladas de movimientos de animales el `pipeline` de procesar y analizar datos de ABISSMAL. Vas a detectar `clusters` ("cúmulos") de detecciones que representan eventos de movimientos distintos y luego vas a anotar inferencias de comportamiento de estos eventos de movimiento. También vas a crear gráficas para visualizar las inferencias de comportamiento. Vas a continuar a usar habilidades que aprendiste en los tutoriales anteriores, y también vas a aprender cómo crear visualizaciones más complejas con `ggplot()`. +En este sexto y último tutorial, vas a terminar de usar las detecciones simuladas de movimientos de animales en el `pipeline` de procesar y analizar datos de ABISSMAL. Vas a detectar `clusters` ("cúmulos") de detecciones que representan eventos de movimientos distintos y luego vas a anotar inferencias de comportamiento de estos eventos de movimiento. También vas a crear gráficas para visualizar las inferencias de comportamiento. Vas a continuar a usar habilidades que aprendiste en los tutoriales anteriores, y también vas a aprender cómo crear visualizaciones más complejas con `ggplot()`.

    Cargar paquetes e inicializar el `path` de tu directorio de trabajo

    @@ -47,14 +47,14 @@ 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 +# Carga un archivo con funciones de apoyo que cada función de arriba requiera source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") ```

    Termina el `pipeline` de ABISSMAL

    -Aquí usarís la función de ABISSMAL que se llama `detect_clusters()` para identificar `clusters` de detecciones a través de los tipos de sensores (detecciones de diferentes sensores que fueron grabados juntos en el tiempo). +Aquí usarás la función de ABISSMAL que se llama `detect_clusters()` para identificar `clusters` de detecciones a través de los tipos de sensores (detecciones de diferentes sensores que fueron grabados juntos en el tiempo). ```{r} # El argumento run_length, o lo largo de cada serie de detecciones que ocurrieron juntos en el tiempo (cluster) tiene que ser 1 para poder detectar clusters con un largo de 2 detecciones @@ -87,7 +87,7 @@ glimpse(scored_clusters) ``` -Cuantos eventos de entrada y salida se anotaron por día? +Cuántos eventos de entrada y salida se anotaron por día?

    Datos ausentes en R

    @@ -102,22 +102,22 @@ is.na(x) ``` -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). +En el código de arriba, creaste un vector que se llama `x` que tiene dos valores de `NA`. La función `is.na()` revisa si cada elemento de `x` es equivalente a `NA`, y devuelve `TRUE` cuando se cumple esa condición (o sea, cuando encuentra un dato ausente). -Como `is.na()` es una frase condicional, también puedes usar otros símbolos especiales que son relevantes 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`: +Como `is.na()` es una frase condicional, también puedes usar otros símbolos especiales que son relevantes para las frases condicionales, como el símbolo de `!`, que sirve para invertir una frase condicional. Por ejemplo, en el código de abajo, cuando añades `!` antes de `is.na()`, estás preguntando si cada elemento de `x` *no* es equivalente a `NA`: ```{r} !is.na(x) ``` -Como puedes ver, añadir el `!` en frente del `is.na()` resulta en valores binarios invertidos comparado con usar solo `is.na()`, y ahora cada valor que antes era `TRUE` se convirtió 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`. +Como puedes ver, añadir el `!` en frente del `is.na()` resulta en valores binarios invertidos comparado con usar solo `is.na()`, y ahora cada valor que antes era `TRUE` se convirtió en `FALSE`. A la par, la habilidad de invertir la frase condicional de `is.na()`, y el resultado binario que devuelve `is.na()` son propiedades muy útiles para encontrar y filtrar las filas de un `dataframe`. Por ejemplo, la función `dplyr::filter()` eliminará una fila cada vez que encuentra un valor de `FALSE` en la columna actual y en cambio, no eliminará una fila cada vez que encuentra un valor de `TRUE`. O sea, si quieres eliminar files que contienen valores de `NA` para una columna actual, añadirías `!is.na(name_of_column)` adentro de `dplyr::filter()`, que debería de devolver `FALSE` cada vez que encuentra una fila con `NA`, y eliminará esa fila como parte de la operación de filtrar. -

    Cuenta eventos por día

    +

    Contar eventos por día

    -Ahora puedes escribir código para contar el número de eventos de entrada y salida que ABISSMAL anotó por día. Vas a necesitar 1) crear una columna nueva con la información sobre el día para cada marca de tiempo, 2) eliminar filas con datos ausentes para la información anotada de la dirección del movimiento (la columna `direction_scored`, porque esta información no se puede anotar para algunos movimientos), 3) agrupar el `dataframe` por día y por la dirección anotada, y luego 4) contar el número de filas por grupo. +Ahora puedes escribir código para contar el número de eventos de entrada y de salida que ABISSMAL anotó por día. Vas a necesitar 1) crear una columna nueva con la información sobre el día para cada marca de tiempo, 2) eliminar filas con datos ausentes para la información anotada de la dirección del movimiento (la columna `direction_scored`, porque esta información no se puede anotar para algunos movimientos), 3) agrupar el `dataframe` por día y por la dirección anotada, y luego 4) contar el número de filas por grupo. ```{r} scored_clusters %>% @@ -125,9 +125,9 @@ scored_clusters %>% 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) + # Aquí estas usando la función is.na() que devolverá TRUE cuando encuentra un valor ausente (NA) en la columna actual. Colocando el símbolo de ! antes de is.na(), estás invirtiendo la frase condicional y también el resultado, así que todos los valores TRUE se convertirán a FALSE. Por ende, dplyr::filter eliminará todas las filas que devuelven el valor de FALSE en esta expresión (o sea, todas las filas con valores ausentes en la columna direction_scored con información sobre la dirección anotada de movimiento) dplyr::filter(!is.na(direction_scored)) %>% - # Agrupa el dataframe 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) + # Agrupa el dataframe de ambas columnas para las cuales quieres contar filas (eventos). Aquí quieres saber el número de entradas y salidas (categorías en la columna direction_scored) por día (categorías en la columna "day") group_by(day, direction_scored) %>% # Luego puedes resumir los datos. El número de filas aquí es el número de entradas o salidas anotadas por día dplyr::summarise( @@ -136,9 +136,9 @@ scored_clusters %>% ``` -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. +Cuatro entradas y salidas se anotaron por día. Cómo se compara este resultado con el número de entradas y salidas que esperabas por día? Si regresas a los tutoriales anteriores (el tercer y el cuarto tutorial) donde creaste los datos originales simulados, deberías de poder ver que empezaste por simular dos entradas y dos salidas por día en los datos para el sistema de RFID y los sensores de infrarrojo. Luego añadiste dos entradas y dos salidas más por día cuando simulaste fallas de detección del sistema de RFID (es decir, estos fueron movimientos capturados solamente por los sensores infrarrojo). `score_clusters` detectó el número correcto de entradas y salidas por día. -Ahora puedes revisar los eventos de 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. +Ahora puedes revisar los eventos de pose, filtrando todas las filas que no se anotaron como eventos de pose (o sea, filas con valores de `NA` en la columna "perching_PIT_tag" que contiene información sobre las etiquetas de PIT). Luego puedes seleccionar solo las columnas que sí contienen información que es útil revisar, como las identidades de las etiquetas PIT, y también las marcas de tiempo para el inicio y fin de cada pose. ```{r} scored_clusters %>% @@ -149,9 +149,9 @@ scored_clusters %>% ``` -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"). +Como aprendiste en el tercer y el cuarto tutorial, el primer evento de pose por día se realizó por el primer individuo (con la etiqueta de PIT "1357aabbcc"), y el segundo evento de pose por día fue realizado por el segundo individuo (con la etiqueta de PIT "2468zzyyxx"). -Cuantos eventos de movimiento que no fueron eventos de posar fueron asignados a cada individuo? +Cuántos eventos de movimiento que no fueron eventos de pose fueron asignados a cada individuo? ```{r} scored_clusters %>% @@ -159,8 +159,8 @@ scored_clusters %>% 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) + # Usa una frase condicional con is.na() para retener solo las filas que tienen códigos de las etiquetas de PIT que no fueron asociados con eventos de pose + # Aquí estás combinando dos frases condicionales para poder buscar filas en la columna individual_initiated (el individuo que inició el movimiento) que tienen códigos de etiquetas PIT, pero que también no tienen una etiqueta de sensor en la columna perching_sensor (o sea, eventos de movimiento que no fueron eventos de pose, sino entradas o salidas) dplyr::filter(!is.na(individual_initiated) & is.na(perching_sensor)) %>% group_by(day, individual_initiated) %>% dplyr::summarise( @@ -169,17 +169,17 @@ scored_clusters %>% ``` -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. +Como puedes ver, cuatro eventos de movimiento que no fueron eventos de pose se asignaron al primer individuo (con la etiqueta PIT "1357aabbcc") en cada día, que es exactamente lo que simulaste en el tercer y el cuarto tutorial. Más movimientos que no fueron eventos de pose se detectaron a través de estos días también, pero como esos eventos no fueron capturados por la antena de RFID (o sea fueron fallas simuladas de la antena de RFID), estos movimientos fueron capturados por sólo los sensores infrarrojo que no pueden grabar información sobre la identidad de los individuos. -

    Construye una gráfica compleja de código de barras

    +

    Construye una gráfica de barras compleja

    -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. +Ahora puedes visualizar los resultados finales. En el código de abajo vas a aprender cómo crear una gráfica de barras que es más compleja de lo que viste en el tutorial anterior, pero que será más fácil de interpretar. Para esta gráfica sería útil poder visualizar tres tipos de inferencias de comportamiento o tipos de información a través del tiempo: la dirección de movimiento (cuando esté disponible), la identidad del individuo (cuando esté disponible), y los eventos de posar. -Puedes empezar con construir la gráfica 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. +Puedes empezar con construir la gráfica añadiendo líneas verticales para los eventos que no fueron eventos de pose. El color de cada línea indicará la identidad del individuo, incluyendo cuando la información no estaba disponible. El tipo de línea va a contener información sobre la dirección de movimiento, y también cuando la información no se pudo anotar. -Para poder asignar colores y tipos de líneas de la forma que quieres en la gráfica, vas a necesitar modificar el `dataframe` de los resultados finales para convertir los `NAs` en las dos columnas asociadas con estos detalles de estética para convertir los datos ausentes en información útil. Por ejemplo, cuando no hay información sobre la identidad del individuo, sería muy útil convertir los valores asociados de `NA` a un valor como "unassigned" ("no asignado"). En el código abajo vas a usar la función `is.na()` 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. +Para poder asignar colores y tipos de líneas de la forma que quieres en la gráfica, vas a necesitar modificar el `dataframe` de los resultados finales para convertir los `NAs` en las dos columnas asociadas con estos detalles de estética para convertir los datos ausentes en información útil. Por ejemplo, cuando no hay información sobre la identidad del individuo, sería muy útil convertir los valores asociados de `NA` a un valor como "unassigned" ("no asignado"). En el código abajo vas a usar la función `is.na()` dentro de frases condicionales de `ifelse()` para convertir los valores de `NA` dentro de las columnas "individual_initiated" y "direction_scored" para poder tener información útil para la gráfica que vas a hacer más adelante. -Primero puedes practicar usar `is.na()` 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`). +Primero puedes practicar usar `is.na()` dentro de `ifelse()` para crear un vector nuevo. En el código siguiente, vas a proveer la frase condicional que quieres probar (aquí vas a probar si la columna que contiene las etiquetas PIT del individuo que inició el movimiento tiene valores de `NA`), el valor que quieres añadir al vector si la condición se cumple (el valor "unassigned" cuando no hay información sobre la etiqueta PIT), y luego el valor que quieres añadir si la condición no se cumple (en este caso es devolver la etiqueta PIT en la columna individual_initiated si el valor actual no es `NA`). ```{r} # is.na() devuelve TRUE cuando encuentra valores de NA adentro de un vector (o columna) y FALSE cuando el valor actual no es NA @@ -190,7 +190,7 @@ ifelse(test = is.na(scored_clusters$individual_initiated), yes = "unassigned", n ``` -Ahora puedes usar `is.na()` adentro de frases de `ifelse()` para modificar columnas en el `dataframe`. +Ahora puedes usar `is.na()` dentro de frases de `ifelse()` para modificar columnas en el `dataframe`. ```{r} scored_clusters_gg <- scored_clusters %>% @@ -198,8 +198,8 @@ scored_clusters_gg <- scored_clusters %>% # 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 + # Repite este proceso para la columna direction_scored pero con un valor diferente (aquí "not_scored" significa que la dirección no se pudo anotar) + # En la frase condicional de abajo añadiste is.na(perching_sensor) (después del símbolo de &) para sólo convertir los valores de NA en la columna de direction_scored si tampoco fueron anotados como eventos de pose en la columna perching_sensor direction_scored = ifelse(is.na(direction_scored) & is.na(perching_sensor), "not scored", direction_scored) ) %>% # Luego vas a convertir cada una de estas columnas al tipo factor y ordenar los levels (categorías) para crear la gráfica de la forma que queremos (por ejemplo, los valores de "unassigned" y "not scored" deberían de ser los últimos valores en la leyenda) @@ -208,34 +208,34 @@ scored_clusters_gg <- scored_clusters %>% 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 +# Revisa los cambios que hiciste arriba usando la función distinct() para ver que todos los valores únicos de cada columna fueron modificados correctamente +# Los valores de NA en la columna de direction_scored son esperados porque se refieren a los eventos de pose scored_clusters_gg %>% distinct(individual_initiated, direction_scored) ``` -Este resultado se ve bien. Deberías de ver valores de `NA` en el `dataframe` pero están en la columna "direction_scored" y asociados con los eventos de posar, que vas a añadir a la gráfica en otra capa diferente de código más adelante. +Este resultado se ve bien. Deberías de ver valores de `NA` en el `dataframe` pero están en la columna "direction_scored" y asociados con los eventos de pose, que vas a añadir a la gráfica en otra capa diferente de código más adelante. -Puedes usar este `dataframe` modificado para crear la gráfica. En el código 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: +Puedes usar este `dataframe` modificado para crear la gráfica. En el código de abajo, vas a añadir líneas con colores asignados por la identidad de individuos, y los tipos de línea van a representar la dirección de movimiento. Primero vas a especificar los detalles estéticos de la gráfica: ```{r} -# Los colores están en el mismo orden que los levels (categorías) de la columna individual_initiated, así que el color naranjo va a representar la etiqueta PIT "1357aabbcc" +# Los colores están en el mismo orden que los levels (categorías) de la columna individual_initiated, así que el color naranja va a representar la etiqueta PIT "1357aabbcc" levels(scored_clusters_gg$individual_initiated) cols <- c("orange", "darkgreen", "black") -# Los tipos de línea están en el mismo orden que los 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) +# Los tipos de línea están en el mismo orden que los niveles de la columna direction_scored, así que el valor "dotted" ("línea punteada") va a representar "not scored" (cuando la dirección no se pudo anotar) levels(scored_clusters_gg$direction_scored) ltys <- c("solid", "longdash", "dotted") ``` -Luego puedes añadir líneas a la gráfica por 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. +Luego puedes añadir líneas a la gráfica por cada identidad de individuo. Aquí estás dividiendo las llamadas de `geom_segment()` por los `levels` (categorías o valores únicos) en la columna individual_initiated. Solo añadiste líneas para el primer individuo y todos los movimientos que no fueron eventos de pose y que no fueron asignados a ese individuo porque después de revisar los resultados finales, sabrás que ningún movimiento (que no fue evento de pose) fueron asignados al segundo individuo. ```{r} ggplot() + - # Añade una línea vertical para cada evento que no fue un evento de posar y que fue asignado al primer individuo + # Añade una línea vertical para cada evento que no fue un evento de pose y que fue asignado al primer individuo geom_segment( data = scored_clusters_gg %>% dplyr::filter(individual_initiated == "1357aabbcc"), @@ -244,7 +244,7 @@ ggplot() + 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 + # Añade una línea vertical para cada evento que no fue un evento de pose y que no fue asignado a uno de los dos individuos geom_segment( data = scored_clusters_gg %>% dplyr::filter(individual_initiated == "unassigned"), @@ -256,14 +256,14 @@ ggplot() + # Añade los tipos de línea customizados a la gráfica scale_linetype_manual(values = ltys) + - # Elimina el título del eje y + # 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 + # Usa estas funciones de estética para eliminar el texto y los rayos del eje "y" + # Añade un argumento para cambiar la posición de la leyenda dentro de la gráfica theme( axis.text.y = element_blank(), axis.ticks.y = element_blank(), @@ -272,11 +272,11 @@ ggplot() + ``` -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()`. +Para hacer esta gráfica, organizaste las líneas verticales para separar los movimientos asignados al primer individuo y los movimientos que no fueron asignados. Esta separación vertical entre estos dos conjuntos de datos facilita las comparaciones visuales de variación en los patrones temporales de los movimientos. Creaste esta separación vertical al cambiar los valores que usaste para `y` y `yend` en la segunda capa de `geom_segment()` para que esas líneas empezarían más arriba que las líneas de la primera capa de `geom_segment()`. -Puedes hacer más modificaciones que ayudarí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 hacer más modificaciones que ayudarán a la interpretación de esta gráfica. En primer lugar, las etiquetas de los paneles que están al lado izquierdo se pueden cambiar para demostrar el día general de colección de datos (como "Day 1" para el primer día) en vez de la fecha. El texto en en eje "x" también se puede cambiar para demostrar sólo el tiempo (o sea, eliminar la información sobre el mes y el día), y también puedes añadir más etiquetas (por ejemplo, una etiqueta cada media hora). -Puedes empezar 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. +Puedes empezar modificando el `dataframe` para añadir la información sobre el día de colección de datos. Para ello, vas a crear una columna nueva usando frases condicionales a través de la función `ifelse()` porque sólo hay tres días de colección de datos con etiquetas que tienes que cambiar. ```{r} # Crea una columna nueva en los datos originales para la fecha de colección de datos @@ -292,7 +292,7 @@ scored_clusters_gg2 <- scored_clusters_gg %>% day_label = ifelse(day == 3, "Day 3", day_label) ) -# Se ve bien +# Se ve bien? glimpse(scored_clusters_gg2) scored_clusters_gg2 %>% @@ -306,7 +306,7 @@ Ahora puedes actualizar el código para incluir las etiquetas nuevas de las fech # 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 + # Añade una línea vertical para cada evento que no fue un evento de pose y que fue asignado al primer individuo geom_segment( data = scored_clusters_gg2 %>% dplyr::filter(individual_initiated == "1357aabbcc"), @@ -315,7 +315,7 @@ ggplot(data = scored_clusters_gg2) + 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 + # Añade una línea vertical para cada evento que no fue un evento de pose y que no fue asignado a ninguno de los dos individuos geom_segment( data = scored_clusters_gg2 %>% dplyr::filter(individual_initiated == "unassigned"), @@ -327,13 +327,13 @@ ggplot(data = scored_clusters_gg2) + # Añade los tipos de línea customizados a la gráfica scale_linetype_manual(values = ltys) + - # Elimina el título del eje y + # 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 + # 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(), @@ -341,14 +341,14 @@ ggplot(data = scored_clusters_gg2) + legend.position = "top" ) + - # Crea paneles en la gráfica por día, aquí usarás las etiquetas nuevas de día + # Crea paneles en la gráfica por día, aquí usarás las etiquetas nuevas del día facet_wrap(~ day_label, nrow = 3, strip.position = "left") ``` -Ahora que moviste la información 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. +Ahora que moviste la información del día de colección de datos a las etiquetas de los paneles de la gráfica, pero necesitas componer el texto en el eje "x". La gráfica será más fácil de interpretar si puedes alinear las marcas de tiempo por hora y por minuto para una comparación directa a través de los diferentes días. -Para alinear las marcas de tiempo a través de 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). +Para alinear las marcas de tiempo a través de los días, necesitas actualizar el formato de las columnas que contienen las marcas de tiempo. El código para convertir las marcas de tiempo a un formato diferente es anidado y repetitivo pero la conversión se realizará correctamente. Cuando le comunicas a R que deberías de convertir las marcas de tiempo a un formato con horas, minutos, y segundos solamente, R va a añadir un año, un mes, y un día por defecto antes de la marca de tiempo (lo más común es que usará la fecha actual). Este comportamiento es esperado y no es un error, más bien facilita que las marcas de tiempo se alinean de la forma correcta a través de los días (paneles) en la gráfica (porque R considera todas las marcas de tiempo como si ocurrieron en un solo día). ```{r} scored_clusters_gg3 <- scored_clusters_gg2 %>% @@ -357,17 +357,17 @@ scored_clusters_gg3 <- scored_clusters_gg2 %>% 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) +# Deberías de ver que un año, mes, y día nuevos fueron adjuntados a las marcas de tiempo modificadas, y este resultado es esperado (arriba) glimpse(scored_clusters_gg3) ``` -Ahora puedes actualizar el código 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: +Ahora puedes actualizar el código para crear la gráfica cambiando la estética del eje "x" usando la función `scale_x_datetime()` para especificar que quieres etiquetas cada media hora en este eje. También vas a añadir un título para el eje "x" y eliminar la cuadrícula en el eje "y" adentro de cada panel: ```{r} gg <- ggplot(data = scored_clusters_gg3) + - # Añade una línea vertical para cada evento que no fue un evento de posar y que fue asignado al primer individuo + # Añade una línea vertical para cada evento que no fue un evento de pose y que fue asignado al primer individuo geom_segment( data = scored_clusters_gg3 %>% dplyr::filter(individual_initiated == "1357aabbcc"), @@ -376,7 +376,7 @@ gg <- ggplot(data = scored_clusters_gg3) + 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 + # Añade una línea vertical para cada evento que no fue un evento de pose y que no fue asignado a ninguno de los individuos geom_segment( data = scored_clusters_gg3 %>% dplyr::filter(individual_initiated == "unassigned"), @@ -388,33 +388,33 @@ gg <- ggplot(data = scored_clusters_gg3) + # Añade los tipos de línea customizados a la gráfica scale_linetype_manual(values = ltys) + - # Elimina el título del eje y + # 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 + # Usa estas funciones de estética para eliminar el texto y los rayos del eje "y" + # Añade un argumento para cambiar la posición de la leyenda dentro de la gráfica theme( axis.text.y = element_blank(), axis.ticks.y = element_blank(), legend.position = "top" ) + - # Crea paneles en la gráfica por día, aquí usarás las etiquetas nuevas de día + # Crea paneles en la gráfica por día, aquí usarás las etiquetas nuevas para "día" facet_wrap(~ day_label, nrow = 3, strip.position = "left") + - # Cambia la estética de las etiquetas del eje x + # 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 + # 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 + # 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() @@ -424,12 +424,12 @@ 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: +Todavía falta un tipo de información importante que necesitas añadir a esta gráfica: los eventos de pose. Vas a añadir esta información con otra capa de `geom_segment()` pero ahora vas a crear líneas cortas y anchas para que aparezcan más como puntos en la gráfica: ```{r} gg <- gg + - # Añade los eventos de 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 + # Añade los eventos de pose como líneas con orillas redondeadas, y puedes añadir colores que indican la identidad del individuo a través de la columna individual_initiated geom_segment( data = scored_clusters_gg3 %>% dplyr::filter(!is.na(perching_sensor)), @@ -437,14 +437,14 @@ gg <- gg + 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() + # Añade los colores customizados para estos eventos de pose. Los colores también aplican a las líneas de movimientos por individuo que no fueron eventos de pose y que añadiste en capas anteriores de geom_segment() scale_color_manual(values = cols) gg ``` -Ahora deberías de ver que una leyenda de color aparece arriba de la gráfica con la adición de esta capa adicional de `geom_segment()`. Ambas de las leyendas se pueden mejorar. Puedes modificar la gráfica 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()`. +Ahora deberías de ver que una leyenda de color aparece arriba de la gráfica con la adición de esta capa adicional de `geom_segment()`. Ambas de las leyendas se pueden mejorar. Puedes modificar la gráfica actualizando el título de cada leyenda, aumentando el tamaño de texto de cada leyenda, y reduciendo el espacio blanco entre las leyendas y la gráfica. Para modificar los títulos de cada leyenda, vas a usar las funciones `guides()` y `guide_legend()`. Para aumentar el tamaño de texto en la leyenda, vas a usar el argumento `legend.text` adentro de la función `theme()`, y para reducir el espacio blanco entre la gráfica y las leyendas, vas a usar el argumento `legend.margin` adentro de la función `theme()`. ```{r} # Ve más información sobre la función que controla los margenes (espacio blanco) alrededor de la leyenda @@ -478,14 +478,14 @@ ggsave(file.path(path, "behavioral_inferences.tiff"), width = 8, height = 6, uni ``` -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. +Puedes continuar con modificar estéticas menores a este archivo para crear una figura de mayor calidad para una publicación. Por ejemplo, puedes cambiar el tamaño final de la imagen (`width`, `height`), tanto como la resolución (`dpi`). Puedes también cambiar el tamaño de texto en cada eje y los títulos de cada eje, o la posición de la leyenda mientras determinas el tamaño final de la imagen en el archivo. Abajo está todo el código que escribiste para la gráfica final, en una forma más condensada y reorganizada: ```{r eval = FALSE} ggplot(data = scored_clusters_gg3) + - # Añade una línea vertical para cada evento que no fue un evento de posar y que fue asignado al primer individuo + # Añade una línea vertical para cada evento que no fue un evento de pose y que fue asignado al primer individuo geom_segment( data = scored_clusters_gg3 %>% dplyr::filter(individual_initiated == "1357aabbcc"), @@ -494,7 +494,7 @@ ggplot(data = scored_clusters_gg3) + 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 + # Añade una línea vertical para cada evento que no fue un evento de posar y que no fue asignado a ninguno de los dos individuos geom_segment( data = scored_clusters_gg3 %>% dplyr::filter(individual_initiated == "unassigned"), @@ -503,7 +503,7 @@ ggplot(data = scored_clusters_gg3) + 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 + # Añade los eventos de pose como líneas con orillas redondeadas, y añade colores que indican la identidad del individuo a través de la columna individual_initiated geom_segment( data = scored_clusters_gg3 %>% dplyr::filter(!is.na(perching_sensor)), @@ -514,7 +514,7 @@ ggplot(data = scored_clusters_gg3) + # 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() + # Añade los colores customizados para estos eventos de pose. Los colores también aplican a las líneas de movimientos por individuo que no fueron eventos de pose y que añadiste en capas anteriores de geom_segment() scale_color_manual(values = cols) + # Modifica los títulos de cada leyenda @@ -523,27 +523,27 @@ ggplot(data = scored_clusters_gg3) + color = guide_legend(title = "Individual") ) + - # Añade un título para el eje x + # Añade un título para el eje "x" xlab("Time of day (HH:MM)") + - # Elimina el título del eje y + # Elimina el título del eje "y" ylab("") + - # Cambia la estética de las etiquetas del eje x + # 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 + # Crea paneles en la gráfica por día, aquí usarás las etiquetas nuevas para "día" facet_wrap(~ day_label, nrow = 3, strip.position = "left") + # Usa esta función para convertir el fondo de la gráfica a blanco y negro theme_bw() + - # Usa estas funciones de estética para eliminar el texto y los rayos del eje y - # Añade un argumento para cambiar la posición de la leyenda adentro de la gráfica - # Puedes quitar la cuadrícula en el eje y (mayor y menor) adentro de cada panel + # Usa estas funciones de estética para eliminar el texto y los rayos del eje "y" + # Añade un argumento para cambiar la posición de la leyenda dentro de la gráfica + # Puedes quitar la cuadrícula en el eje "y" (mayor y menor) dentro de cada panel # Aumenta el tamaño de texto de cada leyenda y reduce el espacio blanco entre la gráfica y las leyendas theme( axis.text.y = element_blank(), @@ -557,4 +557,4 @@ ggplot(data = scored_clusters_gg3) + ``` -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. \ No newline at end of file +Acabas de completar el tutorial final del `pipeline` de procesar y analizar datos de ABISSMAL. También practicaste tus habilidades de programar y tus habilidades de la ciencia de datos en un contexto biológico. Muy bien hecho! Nos ayudaría mucho si puedes completar la forma de Google para una evaluación de estos tutoriales una vez que los hayas terminado. Tus respuestas nos ayudarán a mejorar estos tutoriales en el futuro. Un enlace a la forma de Google estará disponible en el archivo README en el path "R/vignettes/README.md" de este repositorio \ No newline at end of file