library(sula)
Complete or incomplete trips
Identify trips with large gaps and classify the trip as complete or incomplete.
Intro
When tracking animals, there may be times when they enter areas with no reception.
If a large part of a trip takes place in such areas, you should consider whether to include this data in your calculations.
For instance, long gaps could lead to an underestimation of the actual distance traveled by the animal.
In this post, I’ll walk you through the steps I used to determine whether trips are complete or incomplete.
This post has three parts:
- Create a gap
- Calculate gaps
- Identify if our trips are complete or incomplete
Data
For the exercises, test data is from masked boobies.
To access the data you have to install the package sula: devtools::install_github(“MiriamLL/sula”)
Use the function select from tidyverse to keep the columns ID, latitude, longitude, date, time, and trip number.
library(tidyverse)
<-GPS_preparado %>% select(IDs,Latitude,Longitude,DateGMT,TimeGMT,trip_number) Data_1original
Creating a gap
In this part we would eliminate some locations to create an artificial gap
library(sf)
Select a location with “low reception” and remove all the locations to create the gaps
<-data.frame(Longitude=-109.4,Latitude=-27.6)
This_area
<-function(central_point=central_point, buffer_km=buffer_km){
create_buffer<- sp::SpatialPoints(cbind(central_point$Longitude,central_point$Latitude))
central_spatial::proj4string(central_spatial)= sp::CRS("+init=epsg:4326")
sp<- sp::spTransform(central_spatial, sp::CRS("+init=epsg:4326"))
central_spatial <-sf::st_as_sf(central_spatial)
central_spatial<-buffer_km*1000
buffer_dist<-sf::st_buffer(central_spatial, buffer_dist)
central_bufferreturn(central_buffer)
}
<-create_buffer(central_point=This_area,buffer_km=20)
This_buffer
<- Data_1original
Data_2spatial ::coordinates(Data_2spatial) <- ~Longitude + Latitude
sp::proj4string(Data_2spatial) = sp::CRS("+init=epsg:4326")
sp<-sf::st_as_sf(Data_2spatial)
Data_2spatial <-sapply(sf::st_intersects(Data_2spatial ,This_buffer), function(z) if (length(z)==0) NA_integer_ else z[1])
Data_3creategap$trip <- as.numeric(Data_3creategap)
Data_1original<- Data_1original %>%
Data_4withgap filter(is.na(trip)==TRUE)
Plot the area with low reception
<-ggplot()+
Plot_locationsgeom_point(data = Data_4withgap,aes(x=Longitude,y = Latitude),color="#6a994e",size=0.5)+
scale_x_continuous(labels = function(x) paste0(-x, '\u00B0')) +
scale_y_continuous(labels = function(x) paste0(-x, '\u00B0')) +
xlab('Longitude')+ylab('Latitude')+
theme(
panel.background = element_rect(fill = '#edf2f4'),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),legend.position='none',
panel.border = element_rect(colour = "black", fill=NA, size=1.5))+
geom_sf(data=This_buffer,colour='blue', fill='transparent', linetype='dashed')+
geom_text(data=This_area, aes(x=Longitude,y=Latitude,label="No reception"),hjust=+0.46, vjust=0, size=3)
Plot_locations
Calculate gaps
A column in the format POSIXct is needed for this example.
<- Data_4withgap %>%
Data_4withgap mutate(dt= as.POSIXct(strptime(paste0(DateGMT," ",TimeGMT), format="%d/%m/%Y %H:%M:%S")))
Checking gaps in time on one individual trip.
<-Data_4withgap %>% filter(IDs=='GPS09') %>% filter(trip_number=='trip_2') Data_5oneindividual
<-Data_5oneindividual %>%
Data_5oneindividualmutate(times_lag=lag(dt))%>%
mutate(time_dif=as.numeric(difftime(dt, times_lag, units = "mins")))%>%
mutate(time_dif_mins = round(time_dif,2))
This trip has a large gap: 4 - 57.70.
range(Data_5oneindividual$time_dif_mins,na.rm=TRUE)
Create a loop to calculate gaps separated by individuals and trips.
<-function (this_data = this_data,
calculate_gapscolumn_datetime = column_datetime,
column_separator = column_separator){
# to standardize the column names
$column_separator<- this_data[[column_separator]]
this_data$column_datetime <- this_data[[column_datetime]]
this_data<- split(this_data, this_data$column_separator)
trips_list <- list()
gaps_list for (i in seq_along(trips_list)) {
<- trips_list[[i]]
trip_df <-trip_df %>%
trip_dfmutate(times_lag=lag(dt))%>%
mutate(time_dif=as.numeric(difftime(dt, times_lag, units = "mins")))%>%
mutate(time_dif_mins = round(time_dif,2))
<- trip_df}
gaps_list[[i]] <- do.call("rbind", gaps_list)
gaps_df return(gaps_df)
}
For this, would help to have an unique ID, which helps separate the individual and their trips.
<- Data_4withgap %>%
Data_5uniqueIds mutate(unique_id=paste0(IDs,'_',trip_number))
To run the function, provide the data frame, the information for separating, and the name of the column where the date and time are provided.
<-calculate_gaps(this_data=Data_5uniqueIds,
Data_6uniqueIdscolumn_separator='unique_id',
column_datetime='dt')
Complete or incomplete
By using the function summarise, we can see which trips have large gaps.
<-Data_6uniqueIds %>%
Data_7gapsgroup_by(unique_id)%>%
summarise(min_gap=min(time_dif_mins,na.rm = TRUE),
max_gap=max(time_dif_mins,na.rm = TRUE))
Based on the gaps during the trips, the trips could be classified as complete or incomplete.
For example, here an incomplete trip would be any trip that had a gap of more than 30 minutes.
<- Data_7gaps%>%
Data_7gaps mutate(trip_class = case_when(max_gap <= 30 ~ 'Complete',
TRUE ~ 'Incomplete'))
Use this table to identify those trips that were incomplete.
<-unique((subset(Data_7gaps,Data_7gaps$trip_class=='Incomplete')$unique_id)) unique_incomplete
Use the information above, to add the classification on the tracking locations.
<- Data_6uniqueIds %>%
Data_8gaps mutate(trip_class = case_when(unique_id %in% unique_incomplete ~ 'Incomplete',
TRUE ~ 'Complete'))
Plot incomplete trips.
<-ggplot()+
Plot_tripsgeom_point(data = Data_8gaps,aes(x=Longitude,y = Latitude,color=trip_class),size=0.5)+
scale_x_continuous(labels = function(x) paste0(-x, '\u00B0')) +
scale_y_continuous(labels = function(x) paste0(-x, '\u00B0')) +
xlab('Longitude')+ylab('Latitude')+
theme(
panel.background = element_rect(fill = '#edf2f4'),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),legend.position='none',
panel.border = element_rect(colour = "black", fill=NA, size=1.5)
+
)geom_sf(data=This_buffer,colour='blue', fill='transparent', linetype='dashed')+
geom_text(data=This_area, aes(x=Longitude,y=Latitude,label="No reception"),hjust=+0.46, vjust=0, size=3)+
theme(
legend.background = element_rect(colour = "transparent", fill = "transparent"),
legend.position.inside = c(0.22,0.15))+
scale_colour_manual(name='trip',values = c("#6a994e", "#fca311"))+
guides(color = guide_legend(override.aes = list(size = 2)))
Plot_trips
Further reading
Another way to check for gaps, using only time in this post.