3 - Manipulating a track table

Simon Garnier

library(trackdf)
data("tracks")

3.1 - Creating track tables

Under the hood, a track table is just a simple data frame of class data.frame, tibble or data.table with a few extra bells and whistles. This means that you can manipulate a track table same way you would a data frame of any of these three classes.

To create a track table based on data.frame, do:

t_df <- track(x = tracks$x, y = tracks$y, t = tracks$t, id = tracks$id,
  proj = "+proj=longlat", tz = "Africa/Windhoek", table = "df")
head(t_df)
## Track table [6 observations]
## Number of tracks:  1 
## Dimensions:  2D 
## Geographic:  TRUE 
## Projection:  +proj=longlat 
## Table class:  data frame
##   id                   t        x         y
## 1  1 2015-09-10 07:00:00 15.76468 -22.37957
## 2  1 2015-09-10 07:00:01 15.76468 -22.37957
## 3  1 2015-09-10 07:00:04 15.76468 -22.37958
## 4  1 2015-09-10 07:00:05 15.76468 -22.37958
## 5  1 2015-09-10 07:00:08 15.76467 -22.37959
## 6  1 2015-09-10 07:00:09 15.76467 -22.37959

To create a track table based on tibble, do:

t_tbl <- track(x = tracks$x, y = tracks$y, t = tracks$t, id = tracks$id,
  proj = "+proj=longlat", tz = "Africa/Windhoek", table = "tbl")
t_tbl 
## Track table [7194 observations]
## Number of tracks:  2 
## Dimensions:  2D 
## Geographic:  TRUE 
## Projection:  +proj=longlat 
## Table class:  tibble
## # A tibble: 7,194 × 4
##    id    t                       x     y
##    <chr> <dttm>              <dbl> <dbl>
##  1 1     2015-09-10 07:00:00  15.8 -22.4
##  2 1     2015-09-10 07:00:01  15.8 -22.4
##  3 1     2015-09-10 07:00:04  15.8 -22.4
##  4 1     2015-09-10 07:00:05  15.8 -22.4
##  5 1     2015-09-10 07:00:08  15.8 -22.4
##  6 1     2015-09-10 07:00:09  15.8 -22.4
##  7 1     2015-09-10 07:00:10  15.8 -22.4
##  8 1     2015-09-10 07:00:11  15.8 -22.4
##  9 1     2015-09-10 07:00:12  15.8 -22.4
## 10 1     2015-09-10 07:00:13  15.8 -22.4
## # … with 7,184 more rows

To create a track table based on data.table, do:

t_dt <- track(x = tracks$x, y = tracks$y, t = tracks$t, id = tracks$id,
  proj = "+proj=longlat", tz = "Africa/Windhoek", table = "dt")
t_dt
## Track table [7194 observations]
## Number of tracks:  2 
## Dimensions:  2D 
## Geographic:  TRUE 
## Projection:  +proj=longlat 
## Table class:  data table
##       id                   t        x         y
##    1:  1 2015-09-10 07:00:00 15.76468 -22.37957
##    2:  1 2015-09-10 07:00:01 15.76468 -22.37957
##    3:  1 2015-09-10 07:00:04 15.76468 -22.37958
##    4:  1 2015-09-10 07:00:05 15.76468 -22.37958
##    5:  1 2015-09-10 07:00:08 15.76467 -22.37959
##   ---                                          
## 7190:  2 2015-09-10 07:59:55 15.76246 -22.37763
## 7191:  2 2015-09-10 07:59:56 15.76245 -22.37763
## 7192:  2 2015-09-10 07:59:57 15.76244 -22.37762
## 7193:  2 2015-09-10 07:59:58 15.76243 -22.37762
## 7194:  2 2015-09-10 07:59:59 15.76243 -22.37761

3.2 - Basic manipulations

You can check whether an object is a track table as follows:

is_track(t_df)
## [1] TRUE
is_track(t_tbl)
## [1] TRUE
is_track(t_dt)
## [1] TRUE

You can check whether a track table contains geographic coordinates or not as follows:

is_geo(t_df)
## [1] TRUE
is_geo(t_tbl)
## [1] TRUE
is_geo(t_dt)
## [1] TRUE

3.3 - Accessing data

You can access the different parts (rows, columns, elements) of a track table as follows:

head(t_df$id)
## [1] "1" "1" "1" "1" "1" "1"
head(t_tbl$id)
## [1] "1" "1" "1" "1" "1" "1"
head(t_dt$id)
## [1] "1" "1" "1" "1" "1" "1"
head(t_df[["t"]])
## [1] "2015-09-10 07:00:00 CAT" "2015-09-10 07:00:01 CAT"
## [3] "2015-09-10 07:00:04 CAT" "2015-09-10 07:00:05 CAT"
## [5] "2015-09-10 07:00:08 CAT" "2015-09-10 07:00:09 CAT"
head(t_tbl[["t"]])
## [1] "2015-09-10 07:00:00 CAT" "2015-09-10 07:00:01 CAT"
## [3] "2015-09-10 07:00:04 CAT" "2015-09-10 07:00:05 CAT"
## [5] "2015-09-10 07:00:08 CAT" "2015-09-10 07:00:09 CAT"
head(t_dt[["t"]])
## [1] "2015-09-10 07:00:00 CAT" "2015-09-10 07:00:01 CAT"
## [3] "2015-09-10 07:00:04 CAT" "2015-09-10 07:00:05 CAT"
## [5] "2015-09-10 07:00:08 CAT" "2015-09-10 07:00:09 CAT"
head(t_df[1])
##   id
## 1  1
## 2  1
## 3  1
## 4  1
## 5  1
## 6  1
t_tbl[1]
## # A tibble: 7,194 × 1
##    id   
##    <chr>
##  1 1    
##  2 1    
##  3 1    
##  4 1    
##  5 1    
##  6 1    
##  7 1    
##  8 1    
##  9 1    
## 10 1    
## # … with 7,184 more rows
t_dt[1]
## Track table [1 observations]
## Number of tracks:  1 
## Dimensions:  2D 
## Geographic:  TRUE 
## Projection:  +proj=longlat 
## Table class:  data table
##    id                   t        x         y
## 1:  1 2015-09-10 07:00:00 15.76468 -22.37957
t_df[1, ]
## Track table [1 observations]
## Number of tracks:  1 
## Dimensions:  2D 
## Geographic:  TRUE 
## Projection:  +proj=longlat 
## Table class:  data frame
##   id                   t        x         y
## 1  1 2015-09-10 07:00:00 15.76468 -22.37957
t_tbl[1, ]
## Track table [1 observations]
## Number of tracks:  1 
## Dimensions:  2D 
## Geographic:  TRUE 
## Projection:  +proj=longlat 
## Table class:  tibble
## # A tibble: 1 × 4
##   id    t                       x     y
##   <chr> <dttm>              <dbl> <dbl>
## 1 1     2015-09-10 07:00:00  15.8 -22.4
t_dt[1, ]
## Track table [1 observations]
## Number of tracks:  1 
## Dimensions:  2D 
## Geographic:  TRUE 
## Projection:  +proj=longlat 
## Table class:  data table
##    id                   t        x         y
## 1:  1 2015-09-10 07:00:00 15.76468 -22.37957
t_df[1, 1]
## [1] "1"
t_tbl[1, 1]
## # A tibble: 1 × 1
##   id   
##   <chr>
## 1 1
t_dt[1, 1]
##    id
## 1:  1

Note that the results varies depending on the underlying data frame class.

By combining the commands above with the <- operator, you can easily modify the content of a track table. For example:

t_df$id[t_df$id == "1"] <- "0"
head(t_df)
## Track table [6 observations]
## Number of tracks:  1 
## Dimensions:  2D 
## Geographic:  TRUE 
## Projection:  +proj=longlat 
## Table class:  data frame
##   id                   t        x         y
## 1  0 2015-09-10 07:00:00 15.76468 -22.37957
## 2  0 2015-09-10 07:00:01 15.76468 -22.37957
## 3  0 2015-09-10 07:00:04 15.76468 -22.37958
## 4  0 2015-09-10 07:00:05 15.76468 -22.37958
## 5  0 2015-09-10 07:00:08 15.76467 -22.37959
## 6  0 2015-09-10 07:00:09 15.76467 -22.37959
t_df[t_df[, 1] == "0", 1] <- "1"
head(t_df)
## Track table [6 observations]
## Number of tracks:  1 
## Dimensions:  2D 
## Geographic:  TRUE 
## Projection:  +proj=longlat 
## Table class:  data frame
##   id                   t        x         y
## 1  1 2015-09-10 07:00:00 15.76468 -22.37957
## 2  1 2015-09-10 07:00:01 15.76468 -22.37957
## 3  1 2015-09-10 07:00:04 15.76468 -22.37958
## 4  1 2015-09-10 07:00:05 15.76468 -22.37958
## 5  1 2015-09-10 07:00:08 15.76467 -22.37959
## 6  1 2015-09-10 07:00:09 15.76467 -22.37959

3.4 - Accessing and modifying the coordinates’ projection

You can access the projection of a track table as follows.

projection(t_df)
## Coordinate Reference System:
##   User input: +proj=longlat 
##   wkt:
## GEOGCRS["unknown",
##     DATUM["World Geodetic System 1984",
##         ELLIPSOID["WGS 84",6378137,298.257223563,
##             LENGTHUNIT["metre",1]],
##         ID["EPSG",6326]],
##     PRIMEM["Greenwich",0,
##         ANGLEUNIT["degree",0.0174532925199433],
##         ID["EPSG",8901]],
##     CS[ellipsoidal,2],
##         AXIS["longitude",east,
##             ORDER[1],
##             ANGLEUNIT["degree",0.0174532925199433,
##                 ID["EPSG",9122]]],
##         AXIS["latitude",north,
##             ORDER[2],
##             ANGLEUNIT["degree",0.0174532925199433,
##                 ID["EPSG",9122]]]]

You can modify in place the projection of a track table as follows. This will automatically convert the coordinates in the appropriate projection system.

projection(t_df) <- "+proj=somerc +lat_0=46.9524056 +lon_0=7.43958333 +ellps=bessel +x_0=2600000 +y_0=1200000 +towgs84=674.374,15.056,405.346 +units=m +k_0=1 +no_defs"
head(t_df)
## Track table [6 observations]
## Number of tracks:  1 
## Dimensions:  2D 
## Geographic:  TRUE 
## Projection:  +proj=somerc +lat_0=46.9524056 +lon_0=7.43958333 +ellps=bessel +x_0=2600000 +y_0=1200000 +towgs84=674.374,15.056,405.346 +units=m +k_0=1 +no_defs 
## Table class:  data frame
##   id                   t       x        y
## 1  1 2015-09-10 07:00:00 4927487 -9217299
## 2  1 2015-09-10 07:00:01 4927487 -9217299
## 3  1 2015-09-10 07:00:04 4927487 -9217301
## 4  1 2015-09-10 07:00:05 4927487 -9217302
## 5  1 2015-09-10 07:00:08 4927486 -9217304
## 6  1 2015-09-10 07:00:09 4927485 -9217305
projection(t_df) <- "+proj=longlat" 
head(t_df)
## Track table [6 observations]
## Number of tracks:  1 
## Dimensions:  2D 
## Geographic:  TRUE 
## Projection:  +proj=longlat 
## Table class:  data frame
##   id                   t        x         y
## 1  1 2015-09-10 07:00:00 15.76468 -22.37957
## 2  1 2015-09-10 07:00:01 15.76468 -22.37957
## 3  1 2015-09-10 07:00:04 15.76468 -22.37958
## 4  1 2015-09-10 07:00:05 15.76468 -22.37958
## 5  1 2015-09-10 07:00:08 15.76467 -22.37959
## 6  1 2015-09-10 07:00:09 15.76467 -22.37959

If you prefer not to modify the original object, you can create a new one with the new projection as follows.

t_df_new_proj <- project(t_df, "+proj=somerc +lat_0=46.9524056 +lon_0=7.43958333 +ellps=bessel +x_0=2600000 +y_0=1200000 +towgs84=674.374,15.056,405.346 +units=m +k_0=1 +no_defs")

head(t_df_new_proj)
## Track table [6 observations]
## Number of tracks:  1 
## Dimensions:  2D 
## Geographic:  TRUE 
## Projection:  +proj=somerc +lat_0=46.9524056 +lon_0=7.43958333 +ellps=bessel +x_0=2600000 +y_0=1200000 +towgs84=674.374,15.056,405.346 +units=m +k_0=1 +no_defs 
## Table class:  data frame
##   id                   t       x        y
## 1  1 2015-09-10 07:00:00 4927487 -9217299
## 2  1 2015-09-10 07:00:01 4927487 -9217299
## 3  1 2015-09-10 07:00:04 4927487 -9217301
## 4  1 2015-09-10 07:00:05 4927487 -9217302
## 5  1 2015-09-10 07:00:08 4927486 -9217304
## 6  1 2015-09-10 07:00:09 4927485 -9217305

3.5 - Tidyverse

Track tables are compatible with the functions from the tidyverse (with one notable exception, see below). For instance, you can use all the dplyr verbs to filter, mutate, group, etc., a track table, the same way you would do with a tibble or a data.frame.

if (requireNamespace("dplyr", quietly = TRUE)) {
  library(dplyr)
  
  t_df %>%
    filter(., t < as.POSIXct("2015-09-10 07:01:00", tz = "Africa/Windhoek")) %>%
    head(.)
}
## Track table [6 observations]
## Number of tracks:  1 
## Dimensions:  2D 
## Geographic:  TRUE 
## Projection:  +proj=longlat 
## Table class:  data frame
##   id                   t        x         y
## 1  1 2015-09-10 07:00:00 15.76468 -22.37957
## 2  1 2015-09-10 07:00:01 15.76468 -22.37957
## 3  1 2015-09-10 07:00:04 15.76468 -22.37958
## 4  1 2015-09-10 07:00:05 15.76468 -22.37958
## 5  1 2015-09-10 07:00:08 15.76467 -22.37959
## 6  1 2015-09-10 07:00:09 15.76467 -22.37959
if (requireNamespace("dplyr", quietly = TRUE)) {
  library(dplyr)
  
  t_df %>%
    group_by(., id) %>%
    summarize(., x = mean(x),
              y = mean(y))
}
## # A tibble: 2 × 3
##   id        x     y
##   <chr> <dbl> <dbl>
## 1 1      15.8 -22.4
## 2 2      15.8 -22.4

bind_rows is a notable exception to the rule above. Indeed, when combining two data frames, only the attributes of the first data frame passed to bind_rows are retained in the output data frame. As a result, combining with bind_rows two track tables with different projections will ignore the projection of the second track table and assume it is the same as the first one. To avoid this issue, trackdf provides a special version of bind_rows called bind_tracks that checks first that the dimensions, geographic status, and projections of the input track tables are compatible with each other.

With data.frame based track tables:

if (requireNamespace("dplyr", quietly = TRUE)) {
  t_df1 <- dplyr::filter(t_df, id == "1")
  t_df2 <- dplyr::filter(t_df, id == "2")
} else {
  t_df1 <- t_df[t_df$id == "1", ]
  t_df2 <- t_df[t_df$id == "2", ]
}

head(bind_tracks(t_df1, t_df2))

With tibble based track tables:

if (requireNamespace("dplyr", quietly = TRUE)) {
  t_tbl1 <- dplyr::filter(t_tbl, id == "1")
  t_tbl2 <- dplyr::filter(t_tbl, id == "2")
} else {
  t_tbl1 <- t_tbl[t_tbl$id == "1", ]
  t_tbl2 <- t_tbl[t_tbl$id == "2", ]
}

bind_tracks(t_tbl1, t_tbl2)

With data.table based track tables:

if (requireNamespace("dplyr", quietly = TRUE)) {
  t_dt1 <- dplyr::filter(t_dt, id == "1")
  t_dt2 <- dplyr::filter(t_dt, id == "2")
} else {
  t_dt1 <- t_dt[t_dt$id == "1", ]
  t_dt2 <- t_dt[t_dt$id == "2", ]
}

bind_tracks(t_dt1, t_dt2)

3.4 - Plotting

You can use any plotting method accepting a data.frame, a tibble or a data.table to represent the data in a track table.

With base R:

plot(y ~ x, data = t_dt[t_dt$id == "1"], type = "l", col = "red", 
     xlim = range(t_dt$x), lwd = 2, asp = 1)
lines(y ~ x , data = t_dt[t_dt$id == "2"], col = "blue",  lwd = 2)

With [ggplot2]{https://ggplot2.tidyverse.org/}

if (requireNamespace("ggplot2", quietly = TRUE)) {
  ggplot2::ggplot(data = t_dt) +
    ggplot2::aes(x = x, y = y, color = id) + 
    ggplot2::geom_path() +
    ggplot2::coord_map()
}