Pairwise combine the rows of a grouped R dataframe and apply functions to each pair

Viewed 193

I sampled the ultrasound calling activity of bats with a group of automated, time-synced, and stationary detector microphones. In the resulting dataframe, every row is a recorded call, together with the recording time, the length of the call (a proxy for activity), and the x- and y-coordinate of the device.

I want to correlate detector distance with delta, the call length difference between each two detectors to assess the influence of spatial distance on detector performance. For this, I need to pairwise combine every dataframe row with all others, and this per timestamp (= group), finally apply two functions computing (i) delta and (ii) the spatial distance between the detectors of each pair. Here is a sample dataframe, consisting of three devices and three timestamps.

bats <- data.frame(time=as.POSIXct(rep(c("2019-05-18 00:00:00","2019-05-18 00:05:00","2019-05-18 00:10:00"),each=3)), device=rep(c("a","b","c"),3), pos.X=rep(c(0,2,4),3), pos.Y=rep(c(5,7,9),3), call.length=rlnorm(9,meanlog=0.5,sd=1))

For one group, I was successful using combn() and raster::pointDistance():

delta <- abs(combn(x=bats$call.length, m=2, FUN=function(x) x[1]-x[2])) # calculate delta

coordinates <- bats[ ,3:4] # make dataframe of coordinates

row.pairs <- combn(x=nrow(bats), m=2) # get indices for row pairs

df_row.pairs <- cbind(x=coordinates[row.pairs[1,],], y=coordinates[row.pairs[2,],]) # make dataframe from the row pairs
distance <- raster::pointDistance(p1=df_row.pairs[2:1], df_row.pairs[4:3], lonlat=FALSE) # calculate distances among coordinates
df <- data.frame(delta,distance=distance) # bind distances and deltas to a dataframe

But trying to extend the code for all time stamps with dplyr::group_by(time) %>% group_map(), I miserably failed.

Tidyverse solutions are especially welcome, but I am very grateful for every suggestion.

1 Answers

If I understood your question correctly, you can join the dataframe with itself (per timestamp) and then calculate the delta and distance:

library(dplyr)
bats %>%
  left_join(bats, by = "time", suffix = c("", "_2")) %>%
  mutate(distance = sqrt((pos.X - pos.X_2) ^ 2 + (pos.Y - pos.Y_2) ^ 2),
         delta = abs(call.length - call.length_2)) 

Result:

# A tibble: 27 x 11
   time                device pos.X pos.Y call.length device_2 pos.X_2 pos.Y_2 call.length_2 distance delta
   <dttm>              <fct>  <dbl> <dbl>       <dbl> <fct>      <dbl>   <dbl>         <dbl>    <dbl> <dbl>
 1 2019-05-18 00:00:00 a          0     5       2.17  a              0       5         2.17      0     0   
 2 2019-05-18 00:00:00 a          0     5       2.17  b              2       7         0.361     2.83  1.81
 3 2019-05-18 00:00:00 a          0     5       2.17  c              4       9         3.49      5.66  1.32
 4 2019-05-18 00:00:00 b          2     7       0.361 a              0       5         2.17      2.83  1.81
 5 2019-05-18 00:00:00 b          2     7       0.361 b              2       7         0.361     0     0   
 6 2019-05-18 00:00:00 b          2     7       0.361 c              4       9         3.49      2.83  3.13
 7 2019-05-18 00:00:00 c          4     9       3.49  a              0       5         2.17      5.66  1.32
 8 2019-05-18 00:00:00 c          4     9       3.49  b              2       7         0.361     2.83  3.13
 9 2019-05-18 00:00:00 c          4     9       3.49  c              4       9         3.49      0     0   
10 2019-05-18 00:05:00 a          0     5       3.79  a              0       5         3.79      0     0   
# ... with 17 more rows
Related