Skip to contents
library(animovement)
library(tibble)
library(ggplot2)
library(dplyr, warn.conflicts = FALSE)
library(readxl)
library(here)
#> here() starts at /home/runner/work/animovement/animovement
here::i_am("vignettes/articles/calculate-kinematics.Rmd")
#> here() starts at /home/runner/work/animovement/animovement

Calculate kinematics

When we work with movement data, we are often interested in more than just where an animal is; we’re interested in how fast it moves, where it is heading etc. calculate_kinematics calculates a range of kinematic variables:

  • distance: The distance the animal moved since the last observation (simply calculated using Pythagoras’ theorem)
  • v_translation: The translational velocity, like what you see on a speedometer in a car.
  • direction: The direction (in radians) the animal is heading - where the arrow on the compass is heading.
  • rotation: Difference from direction of the last observation.
  • v_rotation: The rotational velocity (in rad/s).
# Augment all data in list
df_kinematics <- df_smooth |>
  calculate_kinematics()
glimpse(df_kinematics)
#> Rows: 37,039
#> Columns: 12
#> $ uid           <chr> "2UL50GHAQRV61BE7E2DZ", "2UL50GHAQRV61BE7E2DZ", "2UL50GH…
#> $ time          <dbl> 0.00000000, 0.01666667, 0.03333333, 0.05000000, 0.066666…
#> $ keypoint      <chr> "centroid", "centroid", "centroid", "centroid", "centroi…
#> $ x             <dbl> NA, NA, NA, 0.002538071, 0.012690355, 0.025380711, 0.038…
#> $ y             <dbl> NA, NA, NA, 0.005076142, 0.022842640, 0.060913706, 0.109…
#> $ distance      <dbl> NA, NA, NA, NA, 0.02046258, 0.04013043, 0.04986518, 0.05…
#> $ v_translation <dbl> NA, NA, NA, NA, 1.2277550, 2.4078256, 2.9919111, 3.03424…
#> $ a_translation <dbl> NA, NA, NA, NA, NA, 7.080424e+01, 3.504513e+01, 2.539884…
#> $ direction     <circular> NA, NA, NA, NA, 1.0516502, 1.2490458, 1.3134726, 1.…
#> $ rotation      <circular> NA, NA, NA, NA, NA, 1.973956e-01, 6.442684e-02, -4.…
#> $ v_rotation    <circular> NA, NA, NA, NA, NA, -1.184373e+01, -3.865610e+00, 2…
#> $ a_rotation    <circular> NA, NA, NA, NA, NA, NA, 478.6874, 406.7352, -174.79…

Clean kinematics

df_kinematics_clean <- df_kinematics |>
  clean_kinematics()

Alternatively, you can perform your own data cleaning methods.

df_kinematics_alternative <- df_kinematics |>
  filter(v_translation > 0.25 & v_rotation < 200)

Whatever way you choose is all up to you, but do make sure you do not just calculate summaries without knowing what’s in it.

Kinematics are much more prone to small values change. We assess these in two ways:

library(patchwork)
a <- df_kinematics_clean |>
  ggplot(aes(x, y, colour = time)) +
  geom_path() +
  scale_colour_viridis_c()
b <- df_kinematics_clean |>
  ggplot(aes(time, direction)) +
  geom_line()
c <- df_kinematics_clean |>
  ggplot(aes(time, v_translation)) +
  geom_line()
d <- df_kinematics_clean |>
  # filter(time < 20) |>
  # filter(v_translation > 0) |>
  ggplot(aes(time, v_rotation)) +
  geom_line()
e <- df_kinematics_clean |>
  ggplot(aes(abs(v_translation))) +
  geom_histogram()
f <- df_kinematics_clean |>
  ggplot(aes(abs(v_rotation))) +
  geom_histogram()
(a + b) / (c + d) / (e + f)
#> Warning: Removed 1 row containing missing values or values outside the scale range
#> (`geom_line()`).
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
#> Warning: Removed 66 rows containing non-finite outside the scale range
#> (`stat_bin()`).


library(performance)
df_kinematics_clean |>
  na.omit() |>
  select(rotation) |>
  performance::check_outliers()
#> 90 outliers detected: cases 37, 327, 332, 380, 383, 388, 473, 474, 475,
#>   523, 764, 807, 907, 1306, 1350, 1364, 1479, 1777, 1802, 1838, 1855,
#>   1888, 1982, 1985, 1987, 2003, 2125, 2126, 2276, 2415, 2486, 2890, 3319,
#>   3380, 3396, 3513, 3569, 3590, 3687, 4080, 4099, 4104, 4236, 4284, 4318,
#>   4369, 4481, 4482, 4529, 4637, 4724, 4725, 4765, 4767, 4810, 4840, 4856,
#>   4891, 4892, 4893, 4906, 4972, 5019, 5102, 5103, 5146, 5272, 5298, 5317,
#>   5318, 5335, 5337, 5370, 5374, 5376, 5384, 5398, 5406, 5519, 5523, 5531,
#>   5533, 5534, 5538, 5543, 5564, 5575, 5598, 5609, 5642.
#> - Based on the following method and threshold: mahalanobis (10.828).
#> - For variable: rotation.
pl_direction <- df_kinematics_clean |>
  # filter(time < 20) |>
  ggplot(aes(x = direction, y = v_translation)) +
  stat_density_2d(
    geom = "tile",
    aes(fill = after_stat(density)),
    n = c(40, 10),
    contour = F
  ) +
  scale_fill_viridis_c() +
  # scale_fill_gradientn(colours=rev(rainbow(32)[1:23])) +
  coord_polar(
    direction = -1,
    start = -pi / 2
  ) +
  # scale_x_continuous(limits = c(0,2*pi)) +
  theme_minimal()

max_coord <- max(abs(c(df_kinematics_clean$x, df_kinematics_clean$y)), na.rm = TRUE)
pl_path <- df_kinematics_clean |>
  ggplot(aes(x, y, colour = v_translation)) +
  geom_path() +
  coord_fixed() +
  scale_x_continuous(limits = c(-max_coord, max_coord)) +
  scale_y_continuous(limits = c(-max_coord, max_coord)) +
  scale_colour_viridis_c() +
  theme_minimal()

pl_path + pl_direction +
  plot_layout(guides = "collect")