4. Fantastic Groups and how to use them

Groupings are a big emphasis in sftrack. Structurally they are built with in the same vein as sfc and sfg classes in sf.

To begin an s_group is a singular grouping. Its whats stored at the row level. A c_grouping is a collection of s_groups and exists at the column level. Groups also have an active_group argument, which turns on and off certain groups for analysis and plotting purposes.

We start by looking at the structure of a c_grouping

library("sftrack")
data('raccoon', package = 'sftrack')
#raccoon <- read.csv(system.file('extdata/raccoon_data.csv', package='sftrack'))
group_list = list(id = raccoon$animal_id, month = as.POSIXlt(raccoon$timestamp)$mon+1)

cg1 <- make_c_grouping(x=group_list, active_group=c('id','month'))
str(cg1)
## c_grouping object 
## List of 445
##  $ :List of 2
##   ..$ id   : chr "TTP-058"
##   .. [list output truncated]
##   ..- attr(*, "class")= chr "s_group"
##   [list output truncated]
##  - attr(*, "active_group")= chr [1:2] "id" "month"
##  - attr(*, "sort_index")= Factor w/ 4 levels "TTP-041_1","TTP-041_2",..: 3 3 3 3 3 3 3 3 3 3 ...
cg1[[1]]
## $id
## [1] "TTP-058"
## 
## $month
## [1] "1"

A grouping contains group related information. The id of the subject/sensor is the lowest level of grouping for the data. Any additional grouping variables are optional.

A c_grouping is simply a collection of s_groups. The s_group is where the grouping data is stored, and can be modified at the row level. The s_groups main job is to store row level grouping information and maintain consistency of the grouping variables.

Single groups (s_group)

An s_group is the grouping variables for a single row of data.

You can make an s_group object using make_s_group(), and giving it a list with the group variables named. In this example we have a single sensor named ‘TTP_058’ from a raccoon, and an additional grouping variable of month (entered as its numeric interpretation).

All grouping information is converted and stored as a character in the s_group.

singlegroup <- make_s_group(list(id='TTP_058', month = 4))
str(singlegroup)
## List of 2
##  $ id   : chr "TTP_058"
##  $ month: chr "4"
##  - attr(*, "class")= chr "s_group"

Because s_groups are simply lists, you can edit individual elements in an s_group

singlegroup 
## $id
## [1] "TTP_058"
## 
## $month
## [1] "4"
singlegroup[1] <- 'CJ15'
singlegroup$month <- '5'
str(singlegroup)
## List of 2
##  $ id   : chr "CJ15"
##  $ month: chr "5"
##  - attr(*, "class")= chr "s_group"

Column groupings (c_grouping)

C_groupings are a collection of s_groups with the same grouping names and an ‘active_group’ which is a subset of all the available group names.

Similarly to s_group you can make a c_grouping with make_s_group. The argument x takes a list where each element is a vector indicating the named groupings as well as a vector of the active groups.

group_list <- list(id = rep(1:2,10), year = rep(2020, 10))
cg <- make_c_grouping(x=group_list, active_group=c('id','year'))
str(cg)
## c_grouping object 
## List of 20
##  $ :List of 2
##   ..$ id  : chr "1"
##   .. [list output truncated]
##   ..- attr(*, "class")= chr "s_group"
##   [list output truncated]
##  - attr(*, "active_group")= chr [1:2] "id" "year"
##  - attr(*, "sort_index")= Factor w/ 2 levels "1_2020","2_2020": 1 2 1 2 1 2 1 2 1 2 ...

You can also make a c_grouping by concatenating multiple s_groups. All s_groups must have the same names or an error is returned.

a <- make_s_group(list(id = 1, year = 2020))
b <- make_s_group(list(id = 1, year = 2021))
c <- make_s_group(list(id = 2, year = 2020))
cg <- c(a, b , c)
## 1_2020 & 1_2021 & 2_2020 has only one relocation
summary(cg)
##                 1_2020                 1_2021                 2_2020 
##                      1                      1                      1 
## active_group: id, year 
##                      0

You’ll notice sftrack warns if any grouping combination only has one relocation. This may be relevant for different kinds of analysis where you need more than one point to consider a movement.

You can also combine c_grouping together with c(). All names and active_groups must be the same.

cg_combine <- c(cg,cg)
summary(cg_combine)
##                 1_2020                 1_2021                 2_2020 
##                      2                      2                      2 
## active_group: id, year 
##                      0

You can also edit groupings like a list, but you must replace it with an s_group object.

cg[1]
## [[1]]
## $id
## [1] "1"
## 
## $year
## [1] "2020"
cg[1] <- make_s_group(list(id=3,year=2019))
## 1_2021 & 2_2020 & 3_2019 has only one relocation
cg[1]
## [[1]]
## $id
## [1] "3"
## 
## $year
## [1] "2019"

And the group names must match the ones in the c_grouping or an error is returned

# Try to add an s_group with a month field when the original group had year instead 
try( cg[1] <- make_s_group(list(id=3,month=2019)) )
## Error in check_group_names(ret) : Group names do not match

Selecting a grouping

As c_groupings are stored as lists, it can be difficult to refer to a single group or combination of groupings. This is where the sort_index can come in handy. The sort_index is a factor of the combined active_group variables for each row where paste(id, name1,name2,…,sep=’_’)). The sort index is remade every time the active group changes, and therefore can be used to subset. You can access the sort index using group_labels().

group_list <- list(id = rep(1:2,10), year = rep(2020, 10))
cg <- make_c_grouping(x=group_list, active_group=c('id','year'))
group_labels(cg)[1:10]
##  [1] 1_2020 2_2020 1_2020 2_2020 1_2020 2_2020 1_2020 2_2020 1_2020 2_2020
## Levels: 1_2020 2_2020
# Subsetting a particular sensor from our raccoon data

data('raccoon', package = 'sftrack')
raccoon$month <- as.POSIXlt(raccoon$timestamp)$mon+1

raccoon$time <- as.POSIXct(raccoon$timestamp, tz='EST')
coords = c('longitude','latitude')
group = list(id = raccoon$animal_id, month = as.POSIXlt(raccoon$timestamp)$mon+1)
time = 'time'

my_sftraj <- as_sftraj(data = raccoon, coords = coords, group = group, time = time)
head(my_sftraj[group_labels(my_sftraj) %in% c('TTP-058_1'), ])
## Sftraj with 6 features and 12 fields (3 empty geometries) 
## Geometry : "geometry" (XY, crs: NA) 
## Timestamp : "time" (POSIXct in UTC) 
## Grouping : "sft_group" (*id*, *month*) 
## -------------------------------
##   animal_id latitude longitude           timestamp height hdop vdop fix month
## 1   TTP-058       NA        NA 2019-01-19 00:02:30     NA  0.0  0.0  NO     1
## 2   TTP-058 26.06945 -80.27906 2019-01-19 01:02:30      7  6.2  3.2  2D     1
## 3   TTP-058       NA        NA 2019-01-19 02:02:30     NA  0.0  0.0  NO     1
## 4   TTP-058       NA        NA 2019-01-19 03:02:30     NA  0.0  0.0  NO     1
## 5   TTP-058 26.06769 -80.27431 2019-01-19 04:02:30    858  5.1  3.2  2D     1
## 6   TTP-058 26.06867 -80.27930 2019-01-19 05:02:30    350  1.9  3.2  3D     1
##                  time               sft_group                       geometry
## 1 2019-01-19 00:02:30 (id: TTP-058, month: 1)                    POINT EMPTY
## 2 2019-01-19 01:02:30 (id: TTP-058, month: 1)     POINT (-80.27906 26.06945)
## 3 2019-01-19 02:02:30 (id: TTP-058, month: 1)                    POINT EMPTY
## 4 2019-01-19 03:02:30 (id: TTP-058, month: 1)                    POINT EMPTY
## 5 2019-01-19 04:02:30 (id: TTP-058, month: 1) LINESTRING (-80.27431 26.06...
## 6 2019-01-19 05:02:30 (id: TTP-058, month: 1) LINESTRING (-80.2793 26.068...

You can also subset by entering the group label of the group itself in either the c_grouping or the sftrack/sftraj object:

head(cg['1_2020'])
## [[1]]
## $id
## [1] "1"
## 
## $year
## [1] "2020"
## 
## 
## [[2]]
## $id
## [1] "1"
## 
## $year
## [1] "2020"
## 
## 
## [[3]]
## $id
## [1] "1"
## 
## $year
## [1] "2020"
## 
## 
## [[4]]
## $id
## [1] "1"
## 
## $year
## [1] "2020"
## 
## 
## [[5]]
## $id
## [1] "1"
## 
## $year
## [1] "2020"
## 
## 
## [[6]]
## $id
## [1] "1"
## 
## $year
## [1] "2020"
sub <- my_sftraj['TTP-058_1',]
print(sub,5, 3)
## Sftraj with 207 features and 12 fields (64 empty geometries) 
## Geometry : "geometry" (XY, crs: NA) 
## Timestamp : "time" (POSIXct in UTC) 
## Grouping : "sft_group" (*id*, *month*) 
## -------------------------------
##   animal_id latitude longitude ...               sft_group
## 1   TTP-058       NA        NA ... (id: TTP-058, month: 1)
## 2   TTP-058 26.06945 -80.27906 ... (id: TTP-058, month: 1)
## 3   TTP-058       NA        NA ... (id: TTP-058, month: 1)
## 4   TTP-058       NA        NA ... (id: TTP-058, month: 1)
## 5   TTP-058 26.06769 -80.27431 ... (id: TTP-058, month: 1)
##                         geometry                time
## 1                    POINT EMPTY 2019-01-19 00:02:30
## 2     POINT (-80.27906 26.06945) 2019-01-19 01:02:30
## 3                    POINT EMPTY 2019-01-19 02:02:30
## 4                    POINT EMPTY 2019-01-19 03:02:30
## 5 LINESTRING (-80.27431 26.06... 2019-01-19 04:02:30

You can refer to the levels of the sort index using group_names, this returns levels() of the sort index.

group_names(cg)
## [1] "1_2020" "2_2020"

Active group

The active_group is a simple yet powerful feature. It dictates how your data is grouped for essentially all calculations. It can also be changed on the fly. You can view and change the active group of a column grouping with active_group(). Once changed, it recalculates the sort_index and in some cases recalculates the geometries.

Active groups can be changed for any sftrack/straj/c_grouping

# sftrack
active_group(my_sftraj)
## [1] "id"    "month"
summary(my_sftraj, stats = T)
##       group points NAs          begin_time            end_time     length_m
## 1 TTP-041_1    208   0 2019-01-19 00:02:30 2019-01-31 23:02:30 0.0533572052
## 2 TTP-041_2     15   0 2019-02-01 00:02:30 2019-02-01 23:02:07 0.0001556664
## 3 TTP-058_1    207   0 2019-01-19 00:02:30 2019-01-31 23:02:30 0.1779383524
## 4 TTP-058_2     15   0 2019-02-01 00:02:30 2019-02-01 23:02:30 0.0115635197
# change the active group to id only
active_group(my_sftraj) <- c('id')
active_group(my_sftraj)
## [1] "id"
summary(my_sftraj, stats = T)
##     group points NAs          begin_time            end_time   length_m
## 1 TTP-041    223   0 2019-01-19 00:02:30 2019-02-01 23:02:07 0.05351287
## 2 TTP-058    222   0 2019-01-19 00:02:30 2019-02-01 23:02:30 0.18950187
# column groupings work the same way
active_group(cg)
## [1] "id"   "year"
active_group(cg) <- 'id'
active_group(cg)
## [1] "id"