#Click this code chunk. IF you get an error and it prompts you to install rmarkdown package, click Install

Code Source & Author: Christopher Beirne, the Wildlife Coexistence Lab, UBC, and the WildCam Network; Adapted by Kelly Klingler

This RMD script runs through the steps for installing the necessary packages and importing the 4 dataframe files. First, we will conduct a standardized data error check before beginning the analysis.

#install packages #lubridate package is critical for dealing with dates and times

#install.packages("rmarkdown") #installs the package
library("rmarkdown") #you must also load it using the library function
#install.packages("lubridate")
library(lubridate)
## 
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
## 
##     date, intersect, setdiff, union

#Find your path and import the practice dataset

pro <- read.csv("C:/Users/Joel Bowersox/OneDrive - Paul Smith's College/fws332_wildlife_data/data/example_data/proj.csv", header=T)
img <- read.csv("C:/Users/Joel Bowersox/OneDrive - Paul Smith's College/fws332_wildlife_data/data/example_data/img.csv", header=T)
dep <- read.csv("C:/Users/Joel Bowersox/OneDrive - Paul Smith's College/fws332_wildlife_data/data/example_data/dep.csv", header=T)
cam <- read.csv("C:/Users/Joel Bowersox/OneDrive - Paul Smith's College/fws332_wildlife_data/data/example_data/cam.csv", header=T)

#Your dataframes should appear in the upper right hand environment window.

Q1 Open each of the dataframes and write a note below about what each dataframe contains and what types of data are present (hint: click the blue drop down arrow will give you lots of information about each of the variables!) CAM_dataframe: Project_ID=AlgarRestorationProject Cameras=20 Camera_Model=Reconyx_Hyperfire_2

DEP_dataframe: Place_Name Latitude Longitude Start_Date End_Date

IMG_dataframe Image_Name File_Name Identifier Animal_Scientific_Name

PRO_dataframe Project_Objectives Project_ Admin Data_Citations About the dataset:

#Error Checking The most important part of analyzing camera trap data is checking and exploring your data!

#Step 1 Check Deployment dates Lets convert the date columns from character strings to date objects. We will use the ymd() function to make sure all of the dates are in the year-month-day format

# start dates
dep$start_date <- ymd(dep$start_date)

# end dates
dep$end_date   <- ymd(dep$end_date)

Q2 What are those $ doing again? Which data frame is being manipulated here? Allow for extraction of elements by name from a named list. The start and end dates

Now let’s make a new column in the deployment data called days, and calculate the interval for all the deployments:

dep$days <- interval(dep$start_date, dep$end_date)/ddays(1)

We should then check the range of dates the cameras were active for. Things to look out for are:

Lets look at the range of values we have:

summary(dep$days)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##    15.0   139.0   221.0   182.9   231.0   597.0       1

#Q3 What is the output above saying? Talk it out with a friend and give it your best guess based on what you know about dataframe syntax the least value, first quarter, median value, third quarter,highest value and average value

#Let’s install a package that will allow us to create interactive tables to view the data. This willl help us find out where this NA issue is.

#install.packages("kableExtra")
library("kableExtra")

Create the table!

dep[is.na(dep$days)==T,] %>% 
  kbl() %>% 
  kable_styling(full_width = T) %>% 
  kableExtra::scroll_box(width = "100%")
project_id deployment_id placename longitude latitude start_date end_date bait_type bait_description feature_type feature_type_methodology camera_id camera_name quiet_period camera_functioning sensor_height height_other sensor_orientation orientation_other plot_treatment plot_treatment_description detection_distance subproject_name subproject_design event_name event_description event_type recorded_by days
3 AlgarRestorationProject ALG027_2019-04-03 ALG027 -112.4735 56.3328 2019-04-03 NA None NA HumanUse NA NA NA 1 Camera Functioning 100 NA NA NA NA NA NA Restoration NA NA NA NA NA NA

#This camera was stolen! Located in a human use area. Okay good now we know where that’s coming from and it isn’t a larger data issue.

Q4 Next, we need to make sure our dates are formatted correctly in the other dataframes….which of the 4 dataframes have dates?

We need to convert the img$timestamp column. The ymd lubridate format is required for a date which looks like 2018-04-13 13:51:01. Essentially the code below is taking the timestamp column in the img data frame and converting it from hours-minutes-seconds (hms) to year-month-day (ymd).

img$timestamp <- ymd_hms(img$timestamp)

Let’s do a quick check to see if all the dates parsed correctly. First check the range:

range(img$timestamp)
## [1] "2018-04-08 04:22:13 UTC" "2019-12-16 12:41:43 UTC"

Q5 What years are represented in this practice dataset? 2018&2019

And check for NA’s:

table(is.na(img$timestamp))
## 
## FALSE 
## 15290

Yay! There are no NAs (remember, this is a logical data type so it’s returning FALSE if no NAs exist across all of our observations. If there were some NAs, we would see a TRUE statement)

Step 2 Basic trapping summaries

Now that our camera trap data are loaded into R, we can very quickly find out summary information about the dataset. These can easily feed directly into the methods section of your report/paper.

First let’s count the number of unique locations:

# Count the number of camera locations
paste(length(unique(dep$placename)), "locations"); paste(length(unique(dep$deployment_id)), "deployments");paste(nrow(img), "image labels"); paste(nrow(img[img$is_blank == TRUE,]), "blanks")
## [1] "38 locations"
## [1] "114 deployments"
## [1] "15290 image labels"
## [1] "2633 blanks"

Notice something about the code above. A # was used but this time it was within the R code chunk. Rather than telling R to create a new heading, this tells R to ignore what comes after it if it’s in an R code chunk. # are a way to make comments or note within the code and tells R to ignore it

#Step 3 Error checks

Lets start with the fundamental error checks required with a new data set:

Camera locations Deployment date checks Image and deployment matching Taxonomy Diel time

Camera locations A common mistake in camera trap data sets is that the locations are not where they are supposed to be. The safest way to check your data is to plot them!

#install.packages("leaflet")
library("leaflet")
m <- leaflet() %>%             # call leaflet
        addTiles() %>%         # add the default basemap
        addCircleMarkers(      # Add circles for stations
          lng=dep$longitude, lat=dep$latitude) 
m                              # return the map

Note - Leaflet is best used using tidyverse ‘pipe’ notation - %>%. It allows you to add successive operations in the order that the elements occur.If the code above does not work, you may need to install tidyverse

#fyi, this might take a couple of minutes

#install.packages("tidyverse")
library("tidyverse")
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr   1.1.4     ✔ readr   2.1.5
## ✔ forcats 1.0.0     ✔ stringr 1.5.1
## ✔ ggplot2 3.5.1     ✔ tibble  3.2.1
## ✔ purrr   1.0.2     ✔ tidyr   1.3.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter()     masks stats::filter()
## ✖ dplyr::group_rows() masks kableExtra::group_rows()
## ✖ dplyr::lag()        masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

Anyway, back to the map! This is great! We can zoom in using the +/_ in the top left hand corner, and the default basemap is OpenStreetMap. The camera stations all appear to be in the right place (we don’t have any in the Atlantic).

But wouldn’t it be great to see the dep$placename when we click over a symbol? Run the following:

m <- leaflet() %>%             
        addTiles() %>%         
        addCircleMarkers(      
          lng=dep$longitude, lat=dep$latitude,
          popup=paste(dep$placename)) # include a popup with the placename!
m                 

Okay, but a map isn’t really useful until have have some satellite imagery, right? Easy:

m <- leaflet() %>%             
        addProviderTiles(providers$Esri.WorldImagery) %>% #Add Esri Wrold imagery         
        addCircleMarkers(      
          lng=dep$longitude, lat=dep$latitude,
          popup=paste(dep$placename)) # include a popup with the placename!
m                              

Q5 Zoom in - what can you tell me about where the stations are located? Can you spot any differences between the stations? Clue: look for lines on the landscape. Roads and Powerlines

#Making Corrections As you can see, we have one deployment location that is a long way from the others. It almost looks like it belongs to another project?! Let’s take a look at all of the deployments from that location (ALG069):

dep[dep$placename=="ALG069",c("deployment_id", "placename", "longitude", "latitude")]
##         deployment_id placename longitude latitude
## 100 ALG069_2018-04-07    ALG069 -113.5075 56.49352
## 101 ALG069_2018-11-14    ALG069 -112.5075 56.49352
## 102 ALG069_2019-04-02    ALG069 -112.5075 56.49352

It looks like there is a typo in one of the coordinates: -112.5075 -> -113.5075. Let’s correct it:

dep$longitude[dep$placename=="ALG069"] <- -112.5075

Let’s create a map that shows camera location by habitat type # viriidis package generates colors for plots

#install.packages("viridis")
library("viridis")
## Loading required package: viridisLite
# First, set a single categorical variable of interest from station covariates for summary graphs. If you do not have an appropriate category use "project_id".
category <- "feature_type"

# We first convert this category to a factor with discrete levels
dep[,category] <- factor(dep[,category])
# then use the turbo() function to assign each level a color
col.cat <- turbo(length(levels(dep[,category])))
# then we apply it to the dataframe
dep$colours <- col.cat[dep[,category]]

m <- leaflet() %>%
  addProviderTiles(providers$Esri.WorldImagery, group="Satellite") %>%  
  addTiles(group="Base") %>%     # Include a basemap option too
  addCircleMarkers(lng=dep$longitude, lat=dep$latitude,
                   # Co lour the markers depending on the 'feature type'
                   color=dep$colours,
                   # Add a popup of the placename and feature_type together 
                   popup=paste(dep$placename, dep[,category])) %>%
  
  # Add a legend explaining what is going on
  addLegend("topleft", colors = col.cat,  labels = levels(dep[,category]),
                   title = category,
                   labFormat = labelFormat(prefix = "$"),
                   opacity = 1) %>%
  
  # add a layer control box to toggle between the layers
  addLayersControl(
                    baseGroups = c("Satellite", "Base"))

m

If you click on a point you will see it’s corresponding placename and feature_type - so you can find the problem data. You can also check your treatment categories using the key. If you zoom in, all the “offline” locations should be >100m away from a linear features, the other on top of them.

Q6 Based on the color coded map above, what do you notice about the research design? Did they place cameras randomly or not? Do certain colors or habitat types cluster together?

Next, we will check the distance between camera pairs

Sometimes the coordinates of a camera stations are accidentally repeated in the deployment data, which can actually be very hard to see on a map as the points will overlay perfectly. The way we check this is to calculate the pairwise distance between all of the unique deployments in the project

#install.packages("sf")
library("sf")
## Linking to GEOS 3.12.2, GDAL 3.9.3, PROJ 9.4.1; sf_use_s2() is TRUE

In the following code block we make first use of the simple features (sf) package - tools which make spatial operations which you would normally perform in ArcMap very easy (e.g. plotting and manipulating polygons).

# create a list of all the non-duplicated placenames
camera_locs <- dep %>% 
  dplyr::select(placename, latitude, longitude) %>% 
  unique() %>% # remove duplicated rows (rows where the placename and coordinates match)
  st_as_sf(coords = c("longitude", "latitude"), crs = "+proj=longlat") # Convert to `sf` format
# Check that there are no duplicated stations
camera_locs[duplicated(camera_locs$placename)==T,]
## Simple feature collection with 0 features and 1 field
## Bounding box:  xmin: NA ymin: NA xmax: NA ymax: NA
## Geodetic CRS:  +proj=longlat
## [1] placename geometry 
## <0 rows> (or 0-length row.names)

f it returns a list of <0 rows>, we don’t have duplicates with different coordinates. Phew!

#install.packages("usedist")
library("usedist")
# distance matrix for all cameras
camera_dist <- st_distance(camera_locs) %>% 
                  as.dist() %>% 
                  usedist::dist_setNames(as.character(camera_locs$placename)) %>% 
                  as.matrix()


#Make temporary camera_dist_mins by  converting diagonals/zeros to 999999 so we can avoid the zeros when using which.min function to find nearest cameras
camera_dist_mins <- camera_dist + diag(999999,dim(camera_dist)[1])

#Create new empty dataframe for appending results to
camera_dist_list <- data.frame(focal_cam = character(),nearest_cam = character(), dist = double())

#Cycle through each column of camera_dist_mins
for (i in (1:dim(camera_dist_mins)[1])) 
  {

    #Get index of minimum value of column i
    t <- which.min(camera_dist_mins[,i])

    #Combine relevant data into new_row
    new_row <- data.frame(colnames(camera_dist_mins)[i],names(t),camera_dist_mins[t,i])

    #Append the new_row to the accumulated results dataframe
    camera_dist_list[nrow(camera_dist_list) + 1,] = new_row

  }
summary(camera_dist_list$dist)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    1103    1540    1978    2210    2578    5263

Q7 Interpret the values above. What is the largest, average, and minimum distance between two cameras? largest is 5263, average is 2210, minimun is 1103 # Nenxt we, need to check whether all the images have a deployment associated with them.

This code returns TRUE if it is and FALSE if it isn’t. We can then summarize this with table()

table(unique(img$placename) %in% unique(dep$placename))
## 
## TRUE 
##   38

We have 38 TRUE’s, which means all the images have deployment data.Phew!

Let’s check that all the placenames also have image data:

# check all the placenames in deployments are represented in the images data
table(unique(dep$placename)  %in% unique(img$placename))
## 
## TRUE 
##   38

If you see any FALSE observations - you either have image data or deployments missing. Go back and fix your raw data!

#Next, let’s check individual camera activity

#install.packages("plotly")
library("plotly")
## 
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
## 
##     last_plot
## The following object is masked from 'package:stats':
## 
##     filter
## The following object is masked from 'package:graphics':
## 
##     layout

In the following plot, black dots denote start and end dates, lines denote periods where a camera is active. Each unique placename gets its own row on the plot - you can hover over the lines to get the deployment_id.

We will use a loop to build the different elements… you don’t need to understand the code itself, this is pretty intricate so just focus on how to interpret the output.

# Call the plot
p <- plot_ly()

# We want a separate row for each 'placename' - so lets turn it into a factor
dep$placename <- as.factor(dep$placename)

# loop through each place name
for(i in seq_along(levels(dep$placename)))
  {
      #Subset the data to just that placename
      tmp <- dep[dep$placename==levels(dep$placename)[i],]
      # Order by date
      tmp <- tmp[order(tmp$start_date),]
      # Loop through each deployment at that placename
      for(j in 1:nrow(tmp))
      {
        # Add a line to 'p'
        p <- add_trace(p, 
                       #Use the start and end date as x coordinates
                       x = c(tmp$start_date[j], tmp$end_date[j]), 
                       #Use the counter for the y coordinates
                       y = c(i,i), 
                       # State the type of chart
                       type="scatter",
                       # make a line that also has points
                       mode = "lines+markers", 
                       # Add the deployment ID as hover text
                       hovertext=tmp$deployment_id[j], 
                       # Color it all black
                       color=I("black"), 
                       # Suppress the legend
                       showlegend = FALSE)
      }
      
  }
# Add a categorical y axis
 p <- p %>%   layout(yaxis = list(

      ticktext = as.list(levels(dep$placename)), 

      tickvals = as.list(1:length(levels(dep$placename))),

      tickmode = "array"))


p

Q8 What do the gaps signify? Can you see any issues? No Data was collected at that time, inconsistent gaps

Sometimes you will see a deployment a long way to the left or right of the plot, this is usually a date error.

Corrections We checked the original datasheets for ALG036 and found out that the deployment end date for ALG036_2019-04-04 was incorrectly entered. It was written as 2020 not 2019. Let’s correct it:

dep$end_date[dep$deployment_id=="ALG036_2019-04-04"] <- ymd("2019-11-21") 
                                    #remember to format it as a date object

Detection check nce we are happy that are cameras were functioning when we expected them to be, we now need to check if all of our labelled images fall within the associated deployment periods. To do this we build on the previous plot above, but also add in the image data over the top. This plot can get very messy, so we divide it into sections of ten deployments.

As before, black lines show an active camera. Red dots show an image detections at that time.

We only show the output of the first 10 deployments, but you should do this for every single deployment you have!

Note - the code below is complex, you don’t have to understand it all at this moment in time. Happy to walk through it with you if you’d like though!

#install.packages("dplyr")
library("dplyr")
# Make a separate plot for each 20 stations For each 20 stations
# To do this make a plot dataframe
tmp <- data.frame("deployment_id"=unique(dep$deployment_id), "plot_group"=ceiling(1:length(unique(dep$deployment_id))/20))

dep_tmp <- left_join(dep,tmp, by="deployment_id")

for(i in 1:max(dep_tmp$plot_group))
{  
  # Call the plot
  p <- plot_ly() 
  
  #Subset the data to just that placename
  tmp <- dep_tmp[dep_tmp$plot_group==i,]
  # Order by placename 
  tmp <- tmp[order(tmp$placename),]
  
 
 # Loop through each deployment at that placename
  for(j in 1:nrow(tmp))
    {
        #Subset the image data
        tmp_img <- img[img$deployment_id==tmp$deployment_id[j],]
        
        if(nrow(tmp_img)>0)
        {
         
          p <- add_trace(p, 
                       #Use the start and end date as x coordinates
                       x = c(tmp_img$timestamp), 
                       #Use the counter for the y coordinates
                       y = rep(j, nrow(tmp_img)), 
                       # State the type of chart
                       type="scatter",
                       # make a line that also has points
                       mode = "markers", 
                       # Add the deployment ID as hover text
                       hovertext=paste(tmp_img$genus,tmp_img$species), 
                       # Color it all black
                       marker = list(color = "red"), 
                       # Suppress the legend
                       showlegend = FALSE)
        }
        
       # Add a line to 'p'
        p <- add_trace(p, 
                       #Use the start and end date as x coordinates
                       x = c(tmp$start_date[j], tmp$end_date[j]), 
                       #Use the counter for the y coordinates
                       y = c(j,j), 
                       # State the type of chart
                       type="scatter",
                       # make a line that also has points
                       mode = "lines", 
                       # Add the deployment ID as hover text
                       hovertext=tmp$deployment_id[j], 
                       # Color it all black
                       color=I("black"), 
                       # Suppress the legend
                       showlegend = FALSE)
      }
  # Add custom y axis labels  
  p <- p %>%   layout(yaxis = list(

      ticktext = as.list(tmp$deployment_id), 

      tickvals = as.list(1:nrow(tmp)),

      tickmode = "array"))
  
  print(p)
      
  
} 

Q9 We have now plotted the images (red dots) over the deployments (black lines). What would a problem look like? some camera were collecting a lot more data than others

If you look at the deployment ALG029_2019-04-02 you will see that has what has happened here. We checked the datasheets and realised that the camera’s timestamp was set to the incorrect month when the deployment began (2019-05-02 instead of 2019-04-02).

# We set the wrong date for the camera collecting images in deployment 
#":"ALG029_2019-04-02"

# We established that the deployment was 30 days out (as there are 30 days in April)
# So we add 30 days to all of the images in that deployment.
img[img$deployment_id=="ALG029_2019-04-02",]$timestamp <-
                   img[img$deployment_id=="ALG029_2019-04-02",]$timestamp - days(30)
# Easy!

#Taxonomy Check

Dealing with taxonomy in camera trap data sets can be a nightmare, particularly if your data labeling software does not give standardized lists of species (e.g. you are manually sorting images into folders). A species list is also something which is often produced for the appendix of a report or paper.

Let us start with building a list of our taxonomic classifications:

# First define vector of the headings you want to see (we will use this trick a lot later on)
taxonomy_headings <- c("class", "order", "family", "genus", "species", "common_name")

# Subset the image data to just those columns
tmp<- img[,colnames(img)%in% taxonomy_headings]
# Remove duplicates
tmp <- tmp[duplicated(tmp)==F,]

# Create an ordered species list
sp_list  <- tmp[order(tmp$class, tmp$order, tmp$family, tmp$genus, tmp$species),]

# Create a column to the species list with genus and species pasted together
sp_list$sp <- paste(sp_list$genus, sp_list$species, sep=".")

# View the species list using kableExtra
sp_list %>%
  kbl(row.names=F) %>%
  kable_styling(full_width = T) %>% 
  kableExtra::scroll_box(width = "100%", height = "250px")
class order family genus species common_name sp
Aves Galliformes Phasianidae Tympanuchus phasianellus NA Tympanuchus.phasianellus
Aves Gruiformes Gruidae Grus canadensis NA Grus.canadensis
Aves Passeriformes Corvidae Corvus corax NA Corvus.corax
Aves Passeriformes Corvidae Perisoreus canadensis NA Perisoreus.canadensis
Aves Strigiformes Strigidae Strix nebulosa NA Strix.nebulosa
Mammalia Artiodactyla Cervidae Alces alces NA Alces.alces
Mammalia Artiodactyla Cervidae Cervus canadensis NA Cervus.canadensis
Mammalia Artiodactyla Cervidae Odocoileus virginianus NA Odocoileus.virginianus
Mammalia Artiodactyla Cervidae Rangifer tarandus NA Rangifer.tarandus
Mammalia Carnivora Canidae Canis latrans NA Canis.latrans
Mammalia Carnivora Canidae Canis lupus NA Canis.lupus
Mammalia Carnivora Canidae Vulpes vulpes NA Vulpes.vulpes
Mammalia Carnivora Felidae Lynx canadensis NA Lynx.canadensis
Mammalia Carnivora Mustelidae Lontra canadensis NA Lontra.canadensis
Mammalia Carnivora Mustelidae Martes americana NA Martes.americana
Mammalia Carnivora Ursidae Ursus americanus NA Ursus.americanus
Mammalia Lagomorpha Leporidae Lepus americanus NA Lepus.americanus
Mammalia Lagomorpha Leporidae Oryctolagus cuniculus NA Oryctolagus.cuniculus
Mammalia Primates Hominidae Homo sapiens NA Homo.sapiens
Mammalia Rodentia Sciuridae Tamiasciurus hudsonicus NA Tamiasciurus.hudsonicus
NA NA NA blank NA .blank
NA NA NA spp. NA .spp.
NA NA NA Canachites canadensis NA Canachites.canadensis
NA NA NA Unknown unknown NA Unknown.unknown
NA NA NA Weasel spp. NA Weasel.spp.

In this case, we actually have an external list of common names which we will use to update our img dataframe to make sure our naming is consistent across all species.

# Import the dataframe
tmp <- read.csv("C:/Users/Joel Bowersox/OneDrive - Paul Smith's College/fws332_wildlife_data/data/example_data/common_names.csv")

# Join it with the existing species list
sp_list$common_name <- NULL
sp_list <- left_join(sp_list, tmp)
## Joining with `by = join_by(sp)`

Then let’s write our species list into our data folder. YOu can check that this new data file exists by leaving R for a second and looking at your course directory

# Note we use the project_id from from project data frame to name the file - that way we won't overwrite it if we run things with a different project. 
write.csv(sp_list, paste0("C:/Users/Joel Bowersox/OneDrive - Paul Smith's College/fws332_wildlife_data/data/example_data/",pro$project_id[1],"_raw_species_list.csv"))

Then lets update the common_name column in our img dataframe to reflect the updated common names.

We will do this using a left_join(), an operation which is invaluable when programming in R.

It uses a specified “key” variable to merge two dataframes in this case we will use the ‘sp’ column.

# first remove the common_name column
img$common_name <- NULL

# add an sp column to the img dataframe - remember the genus and species columns are not pasted together yet
img$sp <- paste(img$genus, img$species, sep=".")

# Next we do the 'left_join'
img <- left_join(img, sp_list[, c("sp", "common_name")], by="sp")

#Diel Activity Check Sometimes when setting up a camera trap, you can input the time incorrectly. This is actually very hard to detect unless you happen to be looking for it. The way we check is to plot the detections for each species by the 24 hour clock. If we get detections of nocturnal species in the day, or vice versa, it suggests there may be a problem.

Caveat A diurnal species active at night doesn’t mean there is actually a problem, camera traps have revealed that many animals are active when we thought they were not!

Cool note Researchers are increasingly using this information to determine a species “availability” for detection! More on that in the density and activity chapters.

For any species detected more than 10 times, we will plot when they were detected:

# First lets convert our timestamp to decimal hours
img$hours <- hour(img$timestamp) + minute(img$timestamp)/60 + second(img$timestamp)/(60*60)

# Count all of the captures
tmp <- img %>% group_by(common_name) %>% summarize(count=n())

yform <- list(categoryorder = "array",
              categoryarray = tmp$common_name)

fig <- plot_ly(x = img$hours, y = img$common_name,type="scatter",
               height=1000, text=img$deployment_id, hoverinfo='text',
               mode   = 'markers',
               marker = list(size = 5,
                             color = 'rgba(50, 100, 255, .2)',
                             line = list(color = 'rgba(0, 0, 0, 0)',
                                         width = 0))) %>% 
              layout(yaxis = yform)
fig
## Warning: Ignoring 2976 observations

Q10 Can you see any exclusively nocturnal species? Exclusively diurnal? Snowshoe Hares seem to only be out when it is dark out, much of every other species found will be out during the day

Congratulations - you have thoroughly error checked your camera data! We may find more errors in the data exploration and analysis RMD, so stay vigilant!