| Title: | An R package providing core data structures for movement data |
|---|---|
| Description: | An R package providing core data structures for movement data. |
| Authors: | Mikkel Roald-Arbøl [aut, cre, cph] (ORCID: <https://orcid.org/0000-0002-9998-0058>) |
| Maintainer: | Mikkel Roald-Arbøl <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.8.0.9009 |
| Built: | 2026-10-14 18:53:46 UTC |
| Source: | https://github.com/animovement/anicore |
Row-wise angle between direction vectors, as used for joint angles.
With no axis, the included angle acos(u . v / |u||v|), in
[0, pi].
With an axis, both vectors are projected onto the plane perpendicular
to it, and the angle turns from u to v about the axis by the
right-hand rule, in [-pi, pi]. For 2D vectors, c(0, 0, 1) gives the
signed angle from x toward y.
Vectors need not be unit length. A zero vector, or one parallel to the
axis, gives NA.
angle_between(u, v, axis = NULL)angle_between(u, v, axis = NULL)
u, v
|
Numeric vectors of length 2 or 3, or matrices or data frames with 2 or 3 columns and one row per vector. A single vector is recycled. |
axis |
|
Numeric vector of angles in radians.
angle_between(c(1, 0), c(0, 1)) angle_between(c(1, 0), c(0, 1), axis = c(0, 0, 1)) angle_between(c(1, 0), c(0, 1), axis = c(0, 0, -1)) # One angle per row angle_between(rbind(c(1, 0, 0), c(0, 1, 0)), c(0, 0, 1))angle_between(c(1, 0), c(0, 1)) angle_between(c(1, 0), c(0, 1), axis = c(0, 0, 1)) angle_between(c(1, 0), c(0, 1), axis = c(0, 0, -1)) # One angle per row angle_between(rbind(c(1, 0, 0), c(0, 1, 0)), c(0, 0, 1))
Angular computations work in radians; a frame declares the unit its angles
are stored in (unit_angle). angle_to_rad() reads a frame's angles into
radians, and angle_from_rad() writes radians back out in the frame's
unit, so a function that computes new angles can return them in the unit
the frame declares.
These convert values and leave the frame alone. To convert a frame's
columns and record the new unit, use convert_unit_angle(). Calling that on
new columns computed in radians would do nothing in a "deg" frame, since
it converts from the unit the frame already declares.
angle_to_rad(x, unit) angle_from_rad(x, unit)angle_to_rad(x, unit) angle_from_rad(x, unit)
x |
Numeric vector of angles, or of angular rates. |
unit |
The angular unit: an aniframe or anievent, whose |
A frame that declares no angular unit ("none") is read as radians:
radians are the unit angles are computed in, and such a frame has no other
unit to return them in.
Numeric vector, the same length as x.
Other angle utilities:
deg_to_rad(),
rad_to_deg(),
unwrap_angle(),
wrap_angle()
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) af <- set_metadata(af, unit_angle = "deg") # Compute in radians, return in the frame's unit angle_from_rad(pi / 2, af) # And read the frame's angles for computing angle_to_rad(c(0, 90, 180), af) # With the unit read once, inside mutate() angle_from_rad(c(0, pi), "deg")af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) af <- set_metadata(af, unit_angle = "deg") # Compute in radians, return in the frame's unit angle_from_rad(pi / 2, af) # And read the frame's angles for computing angle_to_rad(c(0, 90, 180), af) # With the unit read once, inside mutate() angle_from_rad(c(0, pi), "deg")
Creates a specialised data frame for behavioural events in long format:
one row per bout (state event) or instant (point event). Like
anipoint(), the class inherits the shared aniframe substrate —
metadata, accessors, dplyr methods — but holds event-bout records
rather than per-frame movement data.
anievent( ..., metadata = list(), variables_what = NULL, variables_when = NULL, .rows = NULL, .name_repair = c("check_unique", "unique", "universal", "minimal") )anievent( ..., metadata = list(), variables_what = NULL, variables_when = NULL, .rows = NULL, .name_repair = c("check_unique", "unique", "universal", "minimal") )
... |
Name-value pairs to create columns in the data frame. |
metadata |
Optional list of metadata. |
variables_what |
Character vector of identity columns that together
define a unique entity. When |
variables_when |
Character vector of temporal columns. When
|
.rows |
Number of rows (passed to tibble). |
.name_repair |
How to repair column names (passed to tibble). |
Mandatory columns: channel, type, label, start, stop. A
"channel" is one mutually-exclusive categorical track of behaviour —
at any moment, a subject can only have one label active per
channel. type is "state" (durative, stop > start) or "point"
(instant, start == stop). Identity columns (e.g. individual,
subject, track) are optional and declared via variables_what.
A modifiers list-column may carry per-event modifier values — each
cell a character vector (matching the BORIS export format, where one
event can have zero or more modifier values selected from the
ethogram).
An anievent object.
anievent( individual = c(1L, 1L, 1L), channel = c("behaviour", "behaviour", "call"), label = c("REM", "wake", "alarm"), start = c(3, 14, 4.5), stop = c(9, 19, 4.5) )anievent( individual = c(1L, 1L, 1L), channel = c("behaviour", "behaviour", "call"), label = c("REM", "wake", "alarm"), start = c(3, 14, 4.5), stop = c(9, 19, 4.5) )
Creates a specialized data frame for movement data with columns defining entity identity, timepoints, and spatial position.
anipoint( ..., metadata = list(), variables_what = NULL, variables_when = NULL, variables_where = NULL, index = NULL, .rows = NULL, .name_repair = c("check_unique", "unique", "universal", "minimal") )anipoint( ..., metadata = list(), variables_what = NULL, variables_when = NULL, variables_where = NULL, index = NULL, .rows = NULL, .name_repair = c("check_unique", "unique", "universal", "minimal") )
... |
Name-value pairs to create columns in the data frame. |
metadata |
Optional list of metadata. |
variables_what |
Character vector of identity columns that together
define a unique entity, and which the frame is grouped by. If |
variables_when |
Character vector of temporal columns that together
define a unique timepoint. If |
variables_where |
The spatial columns that together define
position. Either a plain character vector of column names, in which
case the name is taken to be the axis role, or a vector named by axis
role — |
index |
Length-one character vector naming the column the frame is
indexed by — the position of each row within its temporal context.
It is never a grouping variable. If
|
.rows |
Number of rows (passed to tibble). |
.name_repair |
How to repair column names (passed to tibble). |
An anipoint object (tibble with anipoint class).
anipoint( individual = rep(1:2, each = 25), time = rep(1:10, 5), x = rnorm(50), y = rnorm(50) ) # Custom variables anipoint( track = rep(1:3, each = 10), trial = 1, time = rep(1:10, 3), x = rnorm(30), y = rnorm(30), variables_what = "track", variables_when = "trial" ) # Indexed by a column that isn't called `time` anipoint( individual = 1L, frame = 1:10, x = rnorm(10), y = rnorm(10), index = "frame" )anipoint( individual = rep(1:2, each = 25), time = rep(1:10, 5), x = rnorm(50), y = rnorm(50) ) # Custom variables anipoint( track = rep(1:3, each = 10), trial = 1, time = rep(1:10, 3), x = rnorm(30), y = rnorm(30), variables_what = "track", variables_when = "trial" ) # Indexed by a column that isn't called `time` anipoint( individual = 1L, frame = 1:10, x = rnorm(10), y = rnorm(10), index = "frame" )
An anistructure describes how the levels of one identity or temporal
variable relate: the keypoints of a skeleton, or the players of a team.
It has three parts, each optional beyond the first:
points — the members, levels of the variable it is attached to.
segments — directed links between two points, with an optional
expected length. Direction matters for joint angles: run limbs
proximal to distal, and paired segments (left to right) the same way.
joints — one measured angle each, between an ordered pair of
segments (a, b), with optional limits min, max and rest in
radians. The angle is 0 when the two segments point the same way. In
2D it is signed, turning from a to b. In 3D it is the included angle,
or, when axis is given ("x", "y", "z" or a segment name), the
signed angle about it. Several angles on the same pair of segments —
flexion and abduction, say — are several joints with different axes.
Lengths and limits are recorded, not enforced: checks compare the data against them.
A structure is a template: it records no data and no variable until it
is attached to a frame with set_structure(), so one structure can be
reused across frames.
anistructure( points = NULL, segments = NULL, joints = NULL, root = NA_character_, source = NA_character_, citation = NA_character_, license = NA_character_ )anistructure( points = NULL, segments = NULL, joints = NULL, root = NA_character_, source = NA_character_, citation = NA_character_, license = NA_character_ )
points |
Character vector of point names. Defaults to the segment endpoints. |
segments |
A data frame with |
joints |
A data frame with |
root |
The point the structure hangs from when positions are
rebuilt from segments. |
source, citation, license
|
Provenance of the structure. |
An anistructure.
set_structure(), example_structure()
anistructure( segments = data.frame( segment = c("thigh", "shin"), from = c("hip", "knee"), to = c("knee", "ankle") ), joints = data.frame(joint = "knee", a = "thigh", b = "shin", min = 0, max = 2.5), root = "hip" ) # A team: points only, or points with links between them anistructure(points = c("gk", "lb", "cb", "rb")) anistructure(segments = list(c("lb", "cb"), c("cb", "rb")))anistructure( segments = data.frame( segment = c("thigh", "shin"), from = c("hip", "knee"), to = c("knee", "ankle") ), joints = data.frame(joint = "knee", a = "thigh", b = "shin", min = 0, max = 2.5), root = "hip" ) # A team: points only, or points with links between them anistructure(points = c("gk", "lb", "cb", "rb")) anistructure(segments = list(c("lb", "cb"), c("cb", "rb")))
Strict cast — the input must already be in canonical anievent
shape: one row per bout or instant, with the columns channel,
type, label, start, and stop. Identity columns travel via
variables_what. An optional modifiers list-column may carry
per-event modifier values (each cell a character vector, matching
the BORIS export format).
as_anievent( data, metadata = list(), variables_what = NULL, variables_when = NULL ) ## S3 method for class 'anievent' as_anievent( data, metadata = list(), variables_what = NULL, variables_when = NULL ) ## S3 method for class 'aniframe' as_anievent( data, metadata = list(), variables_what = NULL, variables_when = NULL ) ## S3 method for class 'data.frame' as_anievent( data, metadata = list(), variables_what = NULL, variables_when = NULL )as_anievent( data, metadata = list(), variables_what = NULL, variables_when = NULL ) ## S3 method for class 'anievent' as_anievent( data, metadata = list(), variables_what = NULL, variables_when = NULL ) ## S3 method for class 'aniframe' as_anievent( data, metadata = list(), variables_what = NULL, variables_when = NULL ) ## S3 method for class 'data.frame' as_anievent( data, metadata = list(), variables_what = NULL, variables_when = NULL )
data |
A data frame with the required columns. |
metadata |
Optional list of metadata. |
variables_what |
Character vector of identity columns. When
|
variables_when |
Character vector of temporal columns. When
|
To encode per-frame data (factor / logical / character columns)
into the bout shape, use to_anievent() instead.
An anievent object.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) try(as_anievent(af))af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) try(as_anievent(af))
Computes the angle of each joint of a structure from the directions of
its two segments a and b, with angle_between():
In 2D, the signed angle turning from a to b, in [-pi, pi],
positive from x toward y. get_angle_direction() says how that
looks on screen.
In 3D with no joint axis, the included angle, in [0, pi].
In 3D with an axis ("x", "y", "z" or a segment name), the
signed angle about it after projecting both segments onto the plane
perpendicular to it.
The angle is 0 when the two segments point the same way. The segment key
is replaced by a joint key, the angle is in the frame's unit_angle,
and confidence is the lower of the two segments'.
A joint frame cannot be converted back: it keeps no lengths and, in 3D, no rotation about the segments themselves.
as_anijoint(data, structure = NULL)as_anijoint(data, structure = NULL)
data |
An |
structure |
For an anipoint, the structure to use; see
|
An anijoint.
angle_between(), anistructure()
af <- example_anipoint(n_obs = 3, n_individuals = 1) |> set_structure(example_structure()) as_anijoint(af)af <- example_anipoint(n_obs = 3, n_individuals = 1) |> set_structure(example_structure()) as_anijoint(af)
Convert a data frame to anipoint
as_anipoint( data, metadata = list(), variables_what = NULL, variables_when = NULL, variables_where = NULL, index = NULL, root = NULL )as_anipoint( data, metadata = list(), variables_what = NULL, variables_when = NULL, variables_where = NULL, index = NULL, root = NULL )
data |
A data frame with movement data. |
metadata |
A list of metadata to attach to the anipoint. |
variables_what |
Character vector of identity columns that together
define a unique entity, and which the frame is grouped by. If |
variables_when |
Character vector of temporal columns that together
define a unique timepoint. If |
variables_where |
The spatial columns that together define
position. Either a plain character vector of column names, in which
case the name is taken to be the axis role, or a vector named by axis
role — |
index |
Length-one character vector naming the column the frame is
indexed by — the position of each row within its temporal context.
It is never a grouping variable. If
|
root |
|
An anipoint object
df <- data.frame( time = 1:3, individual = 'a', keypoint = 'centroid', x = c(0, 1, 2), y = c(0, 1, 0) ) as_anipoint(df)df <- data.frame( time = 1:3, individual = 'a', keypoint = 'centroid', x = c(0, 1, 2), y = c(0, 1, 0) ) as_anipoint(df)
Re-expresses the positions of a structure's points as its segments. Each
row is one segment at one time: its length and the unit vector ux,
uy (and uz in 3D) pointing from the segment's from point to its
to point, in the frame's axes and units.
The structure's variable (usually keypoint) is replaced by a segment
key. Structures over that variable other than structure are dropped;
those over the remaining keys are kept. A confidence column becomes the
lower of the two endpoints' values; other undeclared columns are
dropped.
as_anipoint() rebuilds positions from the segments, anchored at the
root point's trajectory. That is useful after editing the segments — for
example holding each segment's length constant — since every point but
the root then moves to agree with them.
as_anisegment(data, structure = NULL)as_anisegment(data, structure = NULL)
data |
A Cartesian 2D or 3D anipoint. |
structure |
Name of the structure to use. May be omitted when only one structure has segments. |
An anisegment.
anistructure(), set_structure()
af <- example_anipoint(n_obs = 3, n_individuals = 1) |> set_structure(example_structure()) seg <- as_anisegment(af) seg # Hold each segment's length constant, then rebuild the positions rigid <- seg |> dplyr::mutate(length = stats::median(length)) rebuilt <- as_anipoint(rigid, root = af)af <- example_anipoint(n_obs = 3, n_individuals = 1) |> set_structure(example_structure()) seg <- as_anisegment(af) seg # Hold each segment's length constant, then rebuild the positions rigid <- seg |> dplyr::mutate(length = stats::median(length)) rebuilt <- as_anipoint(rigid, root = af)
The difference to_angle - from_angle, wrapped to (-pi, pi] so that it is
the shorter way round the circle rather than the arithmetic difference. This
is the primitive the circular summaries are built on.
circ_difference(from_angle, to_angle)circ_difference(from_angle, to_angle)
from_angle |
A numeric vector of angles, in radians. |
to_angle |
A numeric vector of angles, in radians. |
Numeric vector of signed angular differences in (-pi, pi], positive
anticlockwise.
Other circular statistics:
circ_mad(),
circ_mean(),
circ_median(),
circ_sd(),
circ_successive_difference()
# a tenth of a turn, not nine tenths circ_difference(0.1, 6.1) circ_difference(c(0, pi / 2), c(pi / 2, 0))# a tenth of a turn, not nine tenths circ_difference(0.1, 6.1) circ_difference(c(0, pi / 2), c(pi / 2, 0))
The median of the angular distances from the circular median.
circ_mad(x, na_rm = TRUE)circ_mad(x, na_rm = TRUE)
x |
A numeric vector of angles, in radians, in any range. |
na_rm |
A logical value (default |
A single number in radians, in [0, pi], or NA_real_ when there is
nothing to summarise.
Other circular statistics:
circ_difference(),
circ_mean(),
circ_median(),
circ_sd(),
circ_successive_difference()
circ_mad(c(0.1, 0.2, 6.2))circ_mad(c(0.1, 0.2, 6.2))
The mean direction: the angle of the vector sum of the unit vectors pointing along each observation.
circ_mean(x, na_rm = TRUE)circ_mean(x, na_rm = TRUE)
x |
A numeric vector of angles, in radians, in any range. |
na_rm |
A logical value (default |
The mean is signed, in (-pi, pi], the range the suite uses for every
direction (see wrap_angle()), so it can be compared directly with the
angles it summarises. Wrap it with wrap_angle(x, "2pi") for [0, 2*pi).
A single angle in (-pi, pi], or NA_real_ when there is nothing to
summarise.
Other circular statistics:
circ_difference(),
circ_mad(),
circ_median(),
circ_sd(),
circ_successive_difference()
# 10 degrees; an arithmetic mean would say 190 rad_to_deg(circ_mean(deg_to_rad(c(350, 30)))) # -10 degrees, in the range of the angles themselves rad_to_deg(circ_mean(deg_to_rad(c(-30, 10))))# 10 degrees; an arithmetic mean would say 190 rad_to_deg(circ_mean(deg_to_rad(c(350, 30)))) # -10 degrees, in the range of the angles themselves rad_to_deg(circ_mean(deg_to_rad(c(-30, 10))))
Fisher's circular median: the direction minimising the summed angular distance to every observation. Where two directions tie, their mean direction is returned.
circ_median(x, na_rm = TRUE)circ_median(x, na_rm = TRUE)
x |
A numeric vector of angles, in radians, in any range. |
na_rm |
A logical value (default |
The median is signed, in (-pi, pi], the range the suite uses for every
direction (see wrap_angle()), so it can be compared directly with the
angles it summarises. Wrap it with wrap_angle(x, "2pi") for [0, 2*pi).
A single angle in (-pi, pi], or NA_real_ when there is nothing to
summarise.
Other circular statistics:
circ_difference(),
circ_mad(),
circ_mean(),
circ_sd(),
circ_successive_difference()
# a little below zero, not a little below 2 * pi circ_median(c(-0.1, -0.2, 0.05)) # unaffected by where the circle is cut circ_median(c(0.1, 0.2, 6.2)) circ_median(c(0.1, 0.2, 6.2) + pi)# a little below zero, not a little below 2 * pi circ_median(c(-0.1, -0.2, 0.05)) # unaffected by where the circle is cut circ_median(c(0.1, 0.2, 6.2)) circ_median(c(0.1, 0.2, 6.2) + pi)
Computed from the mean resultant length as sqrt(-2 * log(R)), so it grows
without bound as the angles spread out rather than saturating at pi.
circ_sd(x, na_rm = TRUE)circ_sd(x, na_rm = TRUE)
x |
A numeric vector of angles, in radians, in any range. |
na_rm |
A logical value (default |
A single non-negative number in radians, 0 when every angle is the
same, or NA_real_ when there is nothing to summarise.
Other circular statistics:
circ_difference(),
circ_mad(),
circ_mean(),
circ_median(),
circ_successive_difference()
circ_sd(c(0.1, 0.2, 0.15)) # identical angles have no spread circ_sd(rep(1.3, 5))circ_sd(c(0.1, 0.2, 0.15)) # identical angles have no spread circ_sd(rep(1.3, 5))
Applies circ_difference() along a vector, comparing each angle with the one
lag positions before it — the turn from one heading to the next, rather
than the difference between two angles you name. Unlike base::diff() the
result is the same length as x, padded with NA at the start, so it can be
used inside dplyr::mutate().
circ_successive_difference(x, lag = 1L)circ_successive_difference(x, lag = 1L)
x |
A numeric vector of angles, in radians. |
lag |
A positive integer (default |
A numeric vector the same length as x, in radians. The first lag
entries are NA; the rest are angular differences in (-pi, pi].
Other circular statistics:
circ_difference(),
circ_mad(),
circ_mean(),
circ_median(),
circ_sd()
circ_successive_difference(c(0, pi / 2, pi, 3 * pi / 2)) # crossing zero is a small step, not a large one circ_successive_difference(c(6.2, 0.1))circ_successive_difference(c(0, pi / 2, pi, 3 * pi / 2)) # crossing zero is a small step, not a large one circ_successive_difference(c(6.2, 0.1))
Replaces all Inf and -Inf values with NA in numeric columns of a
data frame. The sibling of convert_nan_to_na(), for sources that mark a
missing observation with an infinity rather than a NaN — TRex is one,
and its own documentation masks np.inf out before plotting.
convert_inf_to_na(data)convert_inf_to_na(data)
data |
A data frame. |
Worth doing at read time rather than later: an Inf propagates through
arithmetic silently, so a single untracked frame turns a mean, a speed or
a bounding box into Inf rather than into a missing value.
A data frame with Inf and -Inf replaced by NA in numeric
columns.
df <- data.frame(x = c(1, Inf, -Inf, 3)) convert_inf_to_na(df)df <- data.frame(x = c(1, Inf, -Inf, 3)) convert_inf_to_na(df)
Replaces all NaN values with NA in numeric columns of a data frame.
convert_nan_to_na(data)convert_nan_to_na(data)
data |
A data frame. |
A data frame with NaN values replaced by NA in numeric columns.
df <- data.frame(x = c(1, NaN, 3)) convert_nan_to_na(df)df <- data.frame(x = c(1, NaN, 3)) convert_nan_to_na(df)
Converts angular columns between radians and degrees and records the new
unit_angle. The spatial angular columns phi and theta and a declared
yaw are always converted; other angular columns are named in cols.
To declare a unit without changing values, use
set_metadata(data, unit_angle = "deg").
convert_unit_angle(data, to_unit, cols = NULL)convert_unit_angle(data, to_unit, cols = NULL)
data |
An anipoint, or an anijoint, whose |
to_unit |
|
cols |
Further numeric angular columns to convert. |
data, converted, with unit_angle updated.
df <- data.frame(time = 1:3, rho = 1:3, phi = c(0, pi / 2, pi)) convert_unit_angle(as_anipoint(df), "deg")df <- data.frame(time = 1:3, rho = 1:3, phi = c(0, pi / 2, pi)) convert_unit_angle(as_anipoint(df), "deg")
Rescales the columns carrying a length and records the new unit_space.
Those are the length axes of the coordinate system — x, y, z on a
Cartesian frame, rho (and z) on a polar, cylindrical or spherical one.
Angular axes are convert_unit_angle()'s. Declared axis_extents are
rescaled with them.
To declare a unit without changing values, use
set_metadata(data, unit_space = "mm").
convert_unit_space(data, to_unit, calibration_factor = NULL)convert_unit_space(data, to_unit, calibration_factor = NULL)
data |
An anipoint, or an anisegment, whose |
to_unit |
Target unit, one of the levels of |
calibration_factor |
Multiplier from the current unit to |
data, rescaled, with unit_space updated.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) # 1 px = 0.5 mm af_mm <- convert_unit_space(af, "mm", calibration_factor = 0.5) convert_unit_space(af_mm, "cm")af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) # 1 px = 0.5 mm af_mm <- convert_unit_space(af, "mm", calibration_factor = 0.5) convert_unit_space(af_mm, "cm")
Rescales the temporal columns — the index of an anipoint, start and
stop of an anievent — and records the new unit_time. An anipoint's
sampling_interval is measured again in the new unit; sampling_rate
is in Hz and is left as it is. Between SI units the factor is derived; to
and from "frame" it is derived from the declared sampling_rate.
Frames count from 0: the first frame is at time 0, so frames are seconds multiplied by the rate, and seconds are frames divided by it. Converting from frames to a time unit keeps nothing extra, as frames are evenly spaced and multiplying by the rate gives them back.
Converting back to frames is also how a wrong rate is put right: convert
to "frame" with the rate the frame declares, declare the right one, and
convert forward again (see the examples). When it is the camera's rate
that was wrong, declare it as source_sampling_rate too, which records
the rate the device recorded at (see set_metadata()).
To declare a unit without changing values, use
set_metadata(data, unit_time = "s").
convert_unit_time(data, to_unit, calibration_factor = NULL) ## S3 method for class 'anipoint' convert_unit_time(data, to_unit, calibration_factor = NULL) ## S3 method for class 'anisegment' convert_unit_time(data, to_unit, calibration_factor = NULL) ## S3 method for class 'anijoint' convert_unit_time(data, to_unit, calibration_factor = NULL) ## S3 method for class 'anievent' convert_unit_time(data, to_unit, calibration_factor = NULL)convert_unit_time(data, to_unit, calibration_factor = NULL) ## S3 method for class 'anipoint' convert_unit_time(data, to_unit, calibration_factor = NULL) ## S3 method for class 'anisegment' convert_unit_time(data, to_unit, calibration_factor = NULL) ## S3 method for class 'anijoint' convert_unit_time(data, to_unit, calibration_factor = NULL) ## S3 method for class 'anievent' convert_unit_time(data, to_unit, calibration_factor = NULL)
data |
An anipoint or anievent. |
to_unit |
Target unit, one of the levels of |
calibration_factor |
Multiplier from the current unit to |
data, rescaled, with unit_time and, for an anipoint,
sampling_interval updated.
Frame numbers that a file records are data; frame numbers computed from
a rate are only nominal. So converting to "frame" uses recorded frame
numbers when the frame has them, and computes them only from regular
sampling.
Recorded frame numbers. If an anipoint has a column named
frame that is not its index, it holds the frame numbers recorded
with the data, and converting to "frame" makes that column the index
again rather than computing frames from the rate. The column it
replaces stays in the frame as an ordinary column, so the times are
not lost. set_index() puts the frame numbers there when it moves
the index from frames to another column, such as recorded timestamps.
Computed frame numbers. Otherwise each time is multiplied by the
rate and rounded to the nearest whole frame. Rather than invent frame
numbers, the conversion refuses when the sampling is irregular, with a
gap between consecutive times of the same keys more than 1% (the
margin validate_anipoint() allows by default) from a whole number of
frames, or when rounding would put two different times of the same
keys on the same frame. A timestamp log, or a sampling_rate that does
not match the data, is refused this way.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) af <- set_metadata(af, sampling_rate = 30) in_seconds <- convert_unit_time(af, "s") in_seconds # The camera was really 25 fps: back to frames, then forward at 25 fps in_seconds |> convert_unit_time("frame") |> set_metadata(sampling_rate = 25, source_sampling_rate = 25) |> convert_unit_time("s") # Times off the frame grid by a little jitter round to whole frames jittered <- dplyr::mutate(in_seconds, time = time + c(1e-4, -1e-4, 0)) convert_unit_time(jittered, "frame")$timeaf <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) af <- set_metadata(af, sampling_rate = 30) in_seconds <- convert_unit_time(af, "s") in_seconds # The camera was really 25 fps: back to frames, then forward at 25 fps in_seconds |> convert_unit_time("frame") |> set_metadata(sampling_rate = 25, source_sampling_rate = 25) |> convert_unit_time("s") # Times off the frame grid by a little jitter round to whole frames jittered <- dplyr::mutate(in_seconds, time = time + c(1e-4, -1e-4, 0)) convert_unit_time(jittered, "frame")$time
Convert degrees to radians
deg_to_rad(x)deg_to_rad(x)
x |
Numeric vector of angles (degrees). |
Numeric vector of angles expressed in radians.
Other angle utilities:
angle_to_rad(),
rad_to_deg(),
unwrap_angle(),
wrap_angle()
deg_to_rad(180) deg_to_rad(c(0, 90, 180))deg_to_rad(180) deg_to_rad(c(0, 90, 180))
Ensure object is an anievent
ensure_is_anievent(x)ensure_is_anievent(x)
x |
An object to test. |
Errors if x is not an anievent; otherwise returns invisibly.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) try(ensure_is_anievent(af))af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) try(ensure_is_anievent(af))
Guard form of is_aniframe(), passed by any animovement frame class.
Functions that need coordinates should guard with
ensure_is_anipoint() instead.
ensure_is_aniframe(x)ensure_is_aniframe(x)
x |
An object to test |
Error if not an aniframe
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) # Passes silently, and errors otherwise ensure_is_aniframe(af) try(ensure_is_aniframe(data.frame(x = 1)))af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) # Passes silently, and errors otherwise ensure_is_aniframe(af) try(ensure_is_aniframe(data.frame(x = 1)))
Ensure object is an anipoint
ensure_is_anipoint(x)ensure_is_anipoint(x)
x |
An object to test |
Error if not an anipoint
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) # Passes silently, and errors otherwise ensure_is_anipoint(af) try(ensure_is_anipoint(data.frame(x = 1)))af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) # Passes silently, and errors otherwise ensure_is_anipoint(af) try(ensure_is_anipoint(data.frame(x = 1)))
Stops with an informative error if data is not in a Cartesian
coordinate system.
ensure_is_cartesian(data)ensure_is_cartesian(data)
data |
An anipoint. |
TRUE, invisibly, if the coordinate system matches; otherwise
an error.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) # Passes silently when the coordinate system matches ensure_is_cartesian(af)af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) # Passes silently when the coordinate system matches ensure_is_cartesian(af)
Stops with an informative error if data is not in a 1-D Cartesian
coordinate system.
ensure_is_cartesian_1d(data)ensure_is_cartesian_1d(data)
data |
An anipoint. |
TRUE, invisibly, if the coordinate system matches; otherwise
an error.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) try(ensure_is_cartesian_1d(af))af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) try(ensure_is_cartesian_1d(af))
Stops with an informative error if data is not in a 2-D Cartesian
coordinate system.
ensure_is_cartesian_2d(data)ensure_is_cartesian_2d(data)
data |
An anipoint. |
TRUE, invisibly, if the coordinate system matches; otherwise
an error.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) ensure_is_cartesian_2d(af)af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) ensure_is_cartesian_2d(af)
Stops with an informative error if data is not in a 3-D Cartesian
coordinate system.
ensure_is_cartesian_3d(data)ensure_is_cartesian_3d(data)
data |
An anipoint. |
TRUE, invisibly, if the coordinate system matches; otherwise
an error.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) try(ensure_is_cartesian_3d(af))af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) try(ensure_is_cartesian_3d(af))
Stops with an informative error if data is not in a cylindrical
coordinate system.
ensure_is_cylindrical(data)ensure_is_cylindrical(data)
data |
An anipoint. |
TRUE, invisibly, if the coordinate system matches; otherwise
an error.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) # Passes silently when the coordinate system matches try(ensure_is_cylindrical(af))af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) # Passes silently when the coordinate system matches try(ensure_is_cylindrical(af))
Stops with an informative error if data is not in a polar
coordinate system.
ensure_is_polar(data)ensure_is_polar(data)
data |
An anipoint. |
TRUE, invisibly, if the coordinate system matches; otherwise
an error.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) # Passes silently when the coordinate system matches try(ensure_is_polar(af))af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) # Passes silently when the coordinate system matches try(ensure_is_polar(af))
Guard form of is_spatial(), for functions that reach coordinates by
iterating variables_where. Aborts naming the offending columns, so
the error points at the metadata mismatch rather than surfacing later
and further away.
ensure_is_spatial(data)ensure_is_spatial(data)
data |
An anipoint object. |
The input data, invisibly.
is_spatial(), validate_anipoint().
af <- anipoint(time = 1:5, x = 1:5, y = 1:5) ensure_is_spatial(af)af <- anipoint(time = 1:5, x = 1:5, y = 1:5) ensure_is_spatial(af)
Stops with an informative error if data is not in a spherical
coordinate system.
ensure_is_spherical(data)ensure_is_spherical(data)
data |
An anipoint. |
TRUE, invisibly, if the coordinate system matches; otherwise
an error.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) # Passes silently when the coordinate system matches try(ensure_is_spherical(af))af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) # Passes silently when the coordinate system matches try(ensure_is_spherical(af))
Generates a synthetic anipoint object with random coordinates for testing and demonstration purposes. The function creates a complete design with all combinations of time points, individuals, keypoints, trials, and sessions.
example_anipoint( n_obs = 50, n_individuals = 3, n_keypoints = 11, n_trials = 1, n_sessions = 1, n_dims = 2 )example_anipoint( n_obs = 50, n_individuals = 3, n_keypoints = 11, n_trials = 1, n_sessions = 1, n_dims = 2 )
n_obs |
Integer. Number of time observations per combination. Default is 50. |
n_individuals |
Integer. Number of individuals to simulate. Default is 3. |
n_keypoints |
Integer. Number of keypoints per individual (max 11). Default is 11. When set to 1, only "centroid" is used. Otherwise, anatomical keypoints are used (head, neck, shoulders, etc.). |
n_trials |
Integer. Number of trials per session. Default is 1. |
n_sessions |
Integer. Number of sessions. Default is 1. |
n_dims |
Integer. Number of spatial dimensions (1, 2, or 3). Default is 2. If 1, only x coordinates are generated. If 2, x and y coordinates are generated. If 3, x, y, and z coordinates are generated. |
An anipoint object containing randomly generated tracking data with
columns for individual, keypoint, time, trial, session, and spatial coordinates
(x, y, and/or z depending on n_dims). The coordinates are drawn from a
standard normal distribution.
# Create a basic example with default parameters (2D) example_anipoint() # Create a 1D example example_anipoint(n_dims = 1) # Create a 3D example example_anipoint(n_dims = 3) # Create a smaller example with 2 individuals and 5 keypoints example_anipoint(n_individuals = 2, n_keypoints = 5) # Create example with multiple trials and sessions example_anipoint(n_obs = 100, n_trials = 3, n_sessions = 2) # Create minimal example with just centroid in 3D example_anipoint(n_keypoints = 1, n_dims = 3)# Create a basic example with default parameters (2D) example_anipoint() # Create a 1D example example_anipoint(n_dims = 1) # Create a 3D example example_anipoint(n_dims = 3) # Create a smaller example with 2 individuals and 5 keypoints example_anipoint(n_individuals = 2, n_keypoints = 5) # Create example with multiple trials and sessions example_anipoint(n_obs = 100, n_trials = 3, n_sessions = 2) # Create minimal example with just centroid in 3D example_anipoint(n_keypoints = 1, n_dims = 3)
example_anipoint()
A spine from abdomen to neck, a head, shoulders, and two legs with
knee joints. Limits are illustrative, not anatomical.
example_structure()example_structure()
An anistructure.
example_structure()example_structure()
The sense of rotation from the x axis to the y axis, as seen from
where the recording was made. atan2(y, x) counts counter-clockwise, so
a frame stored the other way up reports the mirror of the angle a
counter_clockwise frame would give for the same physical heading.
Derived from get_axis_directions() rather than recorded, so it cannot
go on claiming a sense the axes no longer have.
get_angle_direction(data)get_angle_direction(data)
data |
An aniframe or anievent object. |
"clockwise", "counter_clockwise", or "unknown" when the two
axes are not both declared or do not span the view.
get_axis_directions(), get_handedness()
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) # An image-plane frame counts angles clockwise af <- set_axis_directions(af, c(x = "right", y = "down")) get_angle_direction(af)af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) # An image-plane frame counts angles clockwise af <- set_axis_directions(af, c(x = "right", y = "down")) get_angle_direction(af)
The where$position slot. Index by role to write a transformation that
does not care what the columns are called: data[[get_axes(data)[["x"]]]].
get_axes(data)get_axes(data)
data |
An anipoint object. |
Named character vector: names are axis roles (x, y, z,
rho, phi, theta), values are the columns carrying them. Empty
for a frame whose coordinate system is "unknown".
set_variables() to change it.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) get_axes(af) df <- data.frame(time = 1:3, individual = "a", u = c(1, 2, 3), v = c(0, 1, 0)) renamed <- as_anipoint(df, variables_where = c(x = "u", y = "v")) get_axes(renamed) get_metadata(renamed, "coordinate_system")af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) get_axes(af) df <- data.frame(time = 1:3, individual = "a", u = c(1, 2, 3), v = c(0, 1, 0)) renamed <- as_anipoint(df, variables_where = c(x = "u", y = "v")) get_axes(renamed) get_metadata(renamed, "coordinate_system")
Get the direction each axis points
get_axis_directions(data)get_axis_directions(data)
data |
An aniframe or anievent object. |
Named character vector, axis role to direction. Empty when the frame declares none.
set_axis_directions(), reflect_axis()
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) af <- set_axis_directions(af, c(x = "right", y = "up")) get_axis_directions(af)af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) af <- set_axis_directions(af, c(x = "right", y = "up")) get_axis_directions(af)
Derived from the axis roles rather than declared: set_variables() says
which column carries which role, and the system follows from the set of
roles present. It is therefore not writable — see set_variables() to say
what the columns mean, or anispace's map_to_*() functions to convert
the coordinates themselves.
get_coordinate_system(data)get_coordinate_system(data)
data |
An anipoint or anievent object. |
Length-one character vector: one of "cartesian_1d",
"cartesian_2d", "cartesian_3d", "polar", "cylindrical",
"spherical" or "unknown".
get_axes(), is_cartesian(), is_polar()
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) get_coordinate_system(af)af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) get_coordinate_system(af)
Three declared axis directions determine it, and are read in preference to
anything recorded. A frame that states the convention without spelling the
axes out — most 3D recordings — declares it with
set_metadata(data, handedness = "right").
get_handedness(data)get_handedness(data)
data |
An aniframe or anievent object. |
"right", "left", or "unknown" when neither the axes nor the
frame itself says.
get_axis_directions(), get_angle_direction()
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) # Two axes are not enough af <- set_axis_directions(af, c(x = "right", y = "up")) get_handedness(af)af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) # Two axes are not enough af <- set_axis_directions(af, c(x = "right", y = "up")) get_handedness(af)
Exactly one column, of any name, holding the position of each row within
its temporal context: the when$index slot. It is never a grouping column.
An anievent() has none, since a bout spans the start/stop interval.
get_index(data)get_index(data)
data |
An anipoint object. |
Length-one character vector naming the index column.
set_index() to change it, get_variables() for the other
temporal columns.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) get_index(af)af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) get_index(af)
The frame is grouped by its identity keys and temporal context keys:
c(what$keys, when$keys). The index and an anievent's interval are never
grouping columns.
get_keys(data)get_keys(data)
data |
An aniframe. |
Character vector of column names.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) get_keys(af)af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) get_keys(af)
The metadata is stored as a category tree (see
list_default_metadata()), but lookup stays flat: a field is found by
its own name wherever it lives, and a category name returns the whole
category. The variables category is the exception — its slots are
reached through the *_variables_*() accessors, get_index() and
get_axes(), not by flat name.
get_metadata(data, fields = NULL)get_metadata(data, fields = NULL)
data |
An aniframe or anievent object. |
fields |
Field or category names. A field the object does not
carry gives |
The metadata associated with the object.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) names(get_metadata(af)) # A single field can be pulled out by name, wherever it lives get_metadata(af, 'sampling_rate') # A category name returns the whole category names(get_metadata(af, 'space'))af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) names(get_metadata(af)) # A single field can be pulled out by name, wherever it lives get_metadata(af, 'sampling_rate') # A category name returns the whole category names(get_metadata(af, 'space'))
Derived from the index rather than declared, in the unit the index is in – so a frame indexed by frame number has an interval in frames, and one indexed by seconds has it in seconds.
get_sampling_interval(data)get_sampling_interval(data)
data |
An anipoint object. |
Measured per key: identity plus temporal context. The index restarts in each group, so pooling them would measure the restarts rather than the sampling.
Measured again whenever the rows or the index change: by
dplyr::filter(), dplyr::slice(), [, a dplyr::mutate() of the
index or a key, and the other dplyr verbs anicore provides methods for.
A frame that keeps every third row of a 30 Hz recording then reports the
10 Hz spacing it now has, and validate_anipoint() says that its
sampling_rate no longer matches.
Numeric scalar, or NA when the frame is too short to measure.
af <- example_anipoint(n_obs = 5, n_individuals = 2, n_keypoints = 1) get_sampling_interval(af)af <- example_anipoint(n_obs = 5, n_individuals = 2, n_keypoints = 1) get_sampling_interval(af)
Check if object is an anievent
is_anievent(x)is_anievent(x)
x |
An object to test. |
Logical: TRUE if x inherits from anievent.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_anievent(af)af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_anievent(af)
aniframe is the abstract parent of every animovement frame class, so
this tests for the whole family: an anipoint, an
anievent, and any subclass built on them all pass.
Use is_anipoint() to test for the position grain specifically.
is_aniframe(x)is_aniframe(x)
x |
An object to test |
Logical: TRUE if x inherits from aniframe
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_aniframe(af) # A plain data frame is not one is_aniframe(data.frame(x = 1))af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_aniframe(af) # A plain data frame is not one is_aniframe(data.frame(x = 1))
Test whether an object is an anijoint
is_anijoint(x) ensure_is_anijoint(x)is_anijoint(x) ensure_is_anijoint(x)
x |
An object. |
is_anijoint(): logical. ensure_is_anijoint(): errors if not.
af <- example_anipoint(n_obs = 3, n_individuals = 1) |> set_structure(example_structure()) is_anijoint(as_anijoint(af))af <- example_anipoint(n_obs = 3, n_individuals = 1) |> set_structure(example_structure()) is_anijoint(as_anijoint(af))
An anipoint is the position-grain frame: one row per point per
timepoint, with coordinate columns. Use is_aniframe() to test for
the whole animovement frame family instead.
is_anipoint(x)is_anipoint(x)
x |
An object to test |
Logical: TRUE if x inherits from anipoint
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_anipoint(af) # A plain data frame is not one is_anipoint(data.frame(x = 1))af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_anipoint(af) # A plain data frame is not one is_anipoint(data.frame(x = 1))
Test whether an object is an anisegment
is_anisegment(x) ensure_is_anisegment(x)is_anisegment(x) ensure_is_anisegment(x)
x |
An object. |
is_anisegment(): logical. ensure_is_anisegment(): errors if
not.
af <- example_anipoint(n_obs = 3, n_individuals = 1) |> set_structure(example_structure()) is_anisegment(as_anisegment(af))af <- example_anipoint(n_obs = 3, n_individuals = 1) |> set_structure(example_structure()) is_anisegment(as_anisegment(af))
Test whether an object is an anistructure
is_anistructure(x) ensure_is_anistructure(x)is_anistructure(x) ensure_is_anistructure(x)
x |
An object. |
is_anistructure(): logical. ensure_is_anistructure(): errors
if not.
is_anistructure(example_structure())is_anistructure(example_structure())
Returns TRUE if the data frame satisfies any of the 1-D, 2-D or 3-D
Cartesian checks.
is_cartesian(data)is_cartesian(data)
data |
An anipoint. |
A logical value.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_cartesian(af)af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_cartesian(af)
Test for a 1-D Cartesian coordinate system
is_cartesian_1d(data)is_cartesian_1d(data)
data |
An anipoint. |
A logical value.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_cartesian_1d(af)af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_cartesian_1d(af)
Test for a 2-D Cartesian coordinate system
is_cartesian_2d(data)is_cartesian_2d(data)
data |
An anipoint. |
A logical value.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_cartesian_2d(af)af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_cartesian_2d(af)
Test for a 3-D Cartesian coordinate system
is_cartesian_3d(data)is_cartesian_3d(data)
data |
An anipoint. |
A logical value.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_cartesian_3d(af)af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_cartesian_3d(af)
Test whether an anipoint uses a cylindrical coordinate system
is_cylindrical(data)is_cylindrical(data)
data |
An anipoint. |
A logical value.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_cylindrical(af)af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_cylindrical(af)
Test whether an anipoint uses a polar coordinate system
is_polar(data)is_polar(data)
data |
An anipoint. |
A logical value.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_polar(af)af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_polar(af)
Every gap between consecutive observations equal, within tolerance.
Computed from the data each time it is asked rather than recorded,
because dropping rows changes the answer and a stored logical would go
on claiming the old one.
is_sampling_regular(data, tolerance = 1e-06)is_sampling_regular(data, tolerance = 1e-06)
data |
An anipoint object. |
tolerance |
Relative tolerance: a gap counts as equal to the
interval when it differs by no more than |
TRUE, FALSE, or NA when the frame is too short to tell.
af <- example_anipoint(n_obs = 5, n_individuals = 2, n_keypoints = 1) is_sampling_regular(af) # A gap in the recording irregular <- af |> dplyr::filter(time != 3) is_sampling_regular(irregular)af <- example_anipoint(n_obs = 5, n_individuals = 2, n_keypoints = 1) is_sampling_regular(af) # A gap in the recording irregular <- af |> dplyr::filter(time != 3) is_sampling_regular(irregular)
Returns TRUE when variables_where declares at least one column and
every column it names is present and numeric.
is_spatial(data)is_spatial(data)
data |
An anipoint object. |
This is a different question from the one is_cartesian() and its
siblings answer: those test for the presence of particular column
names (x, y, z, …) and never consult the metadata or the column
types. A frame that has lost its x column still satisfies
is_cartesian_1d() on the strength of y alone, while its
variables_where still promises both.
Logical scalar.
ensure_is_spatial(), validate_anipoint().
af <- anipoint(time = 1:5, x = 1:5, y = 1:5) is_spatial(af) # Dropping a declared column breaks the correspondence is_spatial(dplyr::select(af, -x))af <- anipoint(time = 1:5, x = 1:5, y = 1:5) is_spatial(af) # Dropping a declared column breaks the correspondence is_spatial(dplyr::select(af, -x))
Test whether an anipoint uses a spherical coordinate system
is_spherical(data)is_spherical(data)
data |
An anipoint. |
A logical value.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_spherical(af)af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) is_spherical(af)
Returns the default metadata tree. Fields are grouped into categories
(#118) — recording, time, space, variables, structure — with
spec_version at the top level. Access stays flat: a field is read
with get_metadata(data, "sampling_rate") and written with
set_metadata(data, sampling_rate = 30) regardless of its category,
and a category name returns the whole category.
The categories:
recording — provenance: source, source_version,
source_format, source_sampling_rate, filename.
source_sampling_rate is the rate the device recorded at, in Hz, and
no processing changes it; see set_metadata() for how it is filled.
time — unit_time, sampling_rate (declared: the rate of the data
as it is now, which resampling changes), sampling_interval (derived
from the index; read it with get_sampling_interval()),
start_datetime.
space — coordinate_system, reference_frame, handedness,
axis_directions, axis_extents, unit_space, unit_angle, and
euler_sequence / euler_intrinsic: the Euler convention the data's
source uses, for showing and entering a quaternion orientation. The
one category a class can lack: an anievent() has no spatial
component, so its metadata simply has no space (#73).
variables — which columns play which role, as a list of roles each
holding named slots: what$keys (identity), when$index +
when$keys (temporal; an anievent has when$interval instead of an
index), where$position (the axis-role mapping; names are roles,
values are columns), event$state + event$point. The frame groups
by c(what$keys, when$keys) and nothing else. These slots are
reached through get_variables(), get_index() and get_axes(),
never by flat name — keys appears under two roles.
structure — named anistructure()s, each relating the levels of
one variable; see set_structure().
spec_version — named list of semantic version strings, one per
class, versioning the full data contract independently of the
package version.
list_default_metadata(class = c("anipoint", "anievent"))list_default_metadata(class = c("anipoint", "anievent"))
class |
Which class's tree to return: |
A named list: the categories above plus spec_version.
set_metadata(), get_metadata()
names(list_default_metadata()) names(list_default_metadata("anievent"))names(list_default_metadata()) names(list_default_metadata("anievent"))
get_metadata_json() gives a frame's metadata as JSON, which any language
can read. set_metadata_json() restores metadata from that JSON, so that
the round trip gives back exactly what get_metadata() gave.
This is how the metadata travels outside R, for instance in the key-value metadata of a Parquet file written by aniread.
get_metadata_json(data) set_metadata_json(data, json)get_metadata_json(data) set_metadata_json(data, json)
data |
An aniframe. For |
json |
A JSON string from |
get_metadata_json(): a JSON string. set_metadata_json(): data
with the metadata from json.
The JSON has the categories of list_default_metadata() as objects, in
the same order, with spec_version saying which version of the layout it
follows:
A field with a fixed set of values (unit_time, unit_space,
coordinate_system, ...) is its value as a string.
NA is null. source_sampling_rate can also be the string "NaN":
a device with no fixed rate, which JSON has no number for.
start_datetime is an ISO 8601 time in UTC, to the microsecond, with
its time zone beside it as start_timezone (an IANA name, or "" for the
session's own).
axis_directions and axis_extents are objects from axis role to value.
In variables, each role is an object of slots. A slot that maps axis
roles to columns (where$position) is an object; one that lists
columns (what$keys) is an array, even with one column.
structure is an object of structures. Each has points (an array),
segments and joints (arrays of row objects), and root, source,
citation, license and variable.
Numbers are written with as few digits as read back exactly. Inf and
-Inf, which JSON also has no number for, are strings like "NaN".
get_metadata(), set_metadata()
x <- example_anipoint(n_obs = 2, n_individuals = 1) json <- get_metadata_json(x) json y <- set_metadata_json(x, json) identical(get_metadata(y), get_metadata(x))x <- example_anipoint(n_obs = 2, n_individuals = 1) json <- get_metadata_json(x) json y <- set_metadata_json(x, json) identical(get_metadata(y), get_metadata(x))
Prints the metadata of an anipoint() or anievent() one category at a
time, one name: value line per field, wrapped to the console width.
Values carry their units where the metadata declares them, such as
sampling_rate: 30 Hz. Fields that are not set are left out and named
together on a closing line. The rate the device recorded at is shown
with the current rate, and only when the two differ, as in
sampling_rate: 50 Hz (recorded at 200 Hz).
all = TRUE prints every field, including those not set, the values each
factor field allows, and spec_version. source_sampling_rate then has
a line of its own.
## S3 method for class 'aniframe_metadata' print(x, all = FALSE, ...)## S3 method for class 'aniframe_metadata' print(x, all = FALSE, ...)
x |
An |
all |
Whether to print every field (default |
... |
Unused. |
x, invisibly.
md <- get_metadata(set_metadata(example_anipoint(), sampling_rate = 30)) md print(md, all = TRUE)md <- get_metadata(set_metadata(example_anipoint(), sampling_rate = 30)) md print(md, all = TRUE)
Prints the counts of a structure's points, segments and joints, then each
list wrapped to the console width. A segment prints as from - to when
its name is the default from-to, and as name: from - to when it has a
name of its own; joints likewise, over their two segments.
Like a tibble's rows, a list of more than 20 prints its first 10 and how
many more there are. n sets how many to print; n = Inf prints all.
## S3 method for class 'anistructure' print(x, n = NULL, ...) ## S3 method for class 'anistructure' format(x, n = NULL, width = cli::console_width(), ...)## S3 method for class 'anistructure' print(x, n = NULL, ...) ## S3 method for class 'anistructure' format(x, n = NULL, width = cli::console_width(), ...)
x |
An |
n |
How many points, segments and joints to print. |
... |
Unused. |
width |
Width to wrap the lists to (default the console width). |
print(): x, invisibly. format(): a character vector of lines.
s <- example_structure() s print(s, n = 3)s <- example_structure() s print(s, n = 3)
Convert radians to degrees
rad_to_deg(x)rad_to_deg(x)
x |
Numeric vector of angles (radians). |
Numeric vector of angles expressed in degrees.
Other angle utilities:
angle_to_rad(),
deg_to_rad(),
unwrap_angle(),
wrap_angle()
rad_to_deg(pi) rad_to_deg(c(0, pi / 2, pi))rad_to_deg(pi) rad_to_deg(c(0, pi / 2, pi))
Reflects the column carrying an axis role and flips its declared
direction, so the data describes the same scene with the axis pointing
the other way — for example converting image coordinates, where y runs
down, to a y that runs up.
An axis runs from zero to its extent, so turning it over gives
new = extent - old. An axis with no declared axis_extents is centred
on its origin, and turning it over negates it. On a frame that stores
angles there is no column to reflect, and phi and theta are
recomputed instead.
A declared handedness flips with any linear axis.
reflect_axis(data, axis)reflect_axis(data, axis)
data |
An anipoint object. |
axis |
An axis role: |
The anipoint, reflected, with its orientation metadata updated.
set_axis_directions(), get_handedness()
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) af <- set_metadata(af, axis_extents = c(y = 1080)) af <- set_axis_directions(af, c(x = "right", y = "down")) af <- reflect_axis(af, "y") get_axis_directions(af)af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) af <- set_metadata(af, axis_extents = c(y = 1080)) af <- set_axis_directions(af, c(x = "right", y = "down")) af <- reflect_axis(af, "y") get_axis_directions(af)
Records the direction of one or more axes, keyed by axis role. Roles not
named keep the direction they had. This only declares: the values are left
alone. To turn an axis over and keep the data describing the same scene,
use reflect_axis().
set_axis_directions(data, directions)set_axis_directions(data, directions)
data |
An anipoint object. |
directions |
Named character vector, axis role to direction — one of
|
Directions are read from where the recording was made: right/left
across the view, up/down within it, back/forward toward and away
from the viewer. No two axes may point along the same pair. Three declared
directions fix the handedness, which is recorded too.
The anipoint, with the new directions recorded.
get_axis_directions(), reflect_axis(),
get_angle_direction()
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) af <- set_axis_directions(af, c(x = "right", y = "down")) get_axis_directions(af) get_angle_direction(af)af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) af <- set_axis_directions(af, c(x = "right", y = "down")) get_axis_directions(af) get_angle_direction(af)
Makes column the index, the when$index slot, and re-sorts the frame.
A column that was a when key stops being one. unit declares the unit
of the new index in the same call; without it, the frame goes on
declaring the unit of the old index.
The new index must be numeric, with no missing values, and must increase
within each group of keys (identity plus temporal context) in the order
of the old index, so the rows keep their order: values that go backwards
have been matched to the wrong rows. Date-times are refused: the index
holds the time since the start, as numbers, and the start goes in
start_datetime, so absolute time is start_datetime plus the index.
set_index(data, column, unit = NULL)set_index(data, column, unit = NULL)
data |
An anipoint object. |
column |
Length-one character vector naming the index column. It
must exist in |
unit |
The unit of the new index, one of the levels of |
data, re-indexed and restructured.
The previous index becomes an ordinary, undeclared column rather than a key, as grouping by it would put each row in a group of its own.
When it counted frames (unit_time is "frame"), it is renamed
frame. Frame numbers recorded with the data are data, not something to
compute again from a rate, and convert_unit_time() makes the frame
column the index again when converting to "frame". An old index
already named frame keeps its name, and so does one replaced by a new
index named frame. If the frame has another column named frame,
set_index() refuses rather than overwrite either; rename one first.
Timestamps recorded alongside the data, such as a camera's log of when each frame was taken, are added as a column and made the index with their unit:
data |>
dplyr::mutate(timestamp = stamps[time + 1]) |>
set_index("timestamp", unit = "s")
Here the log has one entry per frame and frames count from 0, so the timestamp of a row is matched by its frame number. Matching by position, assigning the log in row order, lines up only when every group is complete and sorted: an anipoint has one row per time per key, and a keypoint can be missing from some frames. Bringing a log, or any other data, into a frame is a join question, tracked in anicore#1.
The frame numbers move to a column named frame, and a declared
sampling_rate is kept as the nominal rate. sampling_interval is
measured from the timestamps, and validate_anipoint() allows the two to
differ by 1%. Overwriting time with dplyr::mutate() instead would
lose the frame numbers and leave the frame saying the index counts frames
when it holds seconds.
get_index(), convert_unit_time() to rescale the index into
another unit.
df <- data.frame(frame = 1:3, individual = "a", x = c(1, 2, 3), y = c(0, 1, 0)) af <- as_anipoint(df, index = "frame") get_index(af) # A camera log, one timestamp per frame, frames counted from 0 af <- as_anipoint(data.frame( individual = "a", keypoint = rep(c("head", "tail"), each = 4), time = rep(0:3, 2), x = 1:8, y = 1:8 )) stamps <- c(0, 0.0332, 0.0668, 0.1001) logged <- af |> dplyr::mutate(timestamp = stamps[time + 1]) |> set_index("timestamp", unit = "s") logged # The frame numbers are kept, and become the index again get_index(convert_unit_time(logged, "frame"))df <- data.frame(frame = 1:3, individual = "a", x = c(1, 2, 3), y = c(0, 1, 0)) af <- as_anipoint(df, index = "frame") get_index(af) # A camera log, one timestamp per frame, frames counted from 0 af <- as_anipoint(data.frame( individual = "a", keypoint = rep(c("head", "tail"), each = 4), time = rep(0:3, 2), x = 1:8, y = 1:8 )) stamps <- c(0, 0.0332, 0.0668, 0.1001) logged <- af |> dplyr::mutate(timestamp = stamps[time + 1]) |> set_index("timestamp", unit = "s") logged # The frame numbers are kept, and become the index again get_index(convert_unit_time(logged, "frame"))
Sets or updates metadata for an aniframe or anievent object. Metadata can be provided either as named arguments or as a list. If the object already has metadata, the new values will be merged with existing values, with new values taking precedence.
Fields are written by their own name wherever they live in the
category tree (see list_default_metadata()): set_metadata(data, sampling_rate = 30) lands in time, set_metadata(data, handedness = "left") in space. Categories themselves are not writable, and the
variable declaration goes through its dedicated setters.
Character values for factor fields will be automatically converted to factors if they match allowed levels.
set_metadata(data, ..., metadata = NULL)set_metadata(data, ..., metadata = NULL)
data |
An aniframe or anievent object. |
... |
Named metadata values (e.g., |
metadata |
Alternatively, a named list of metadata. Cannot be used
simultaneously with |
The object with updated metadata.
A frame keeps two rates apart. source_sampling_rate, with the other
provenance in recording, is the rate the device recorded at, and no
processing changes it. sampling_rate, in time, is the rate of the
data as it is now: the rate filters and conversions compute with, and
the one resampling changes.
The first declaration of a rate fills both. Setting sampling_rate on a
frame that has neither rate yet also sets source_sampling_rate, since
that is the moment the camera's rate is declared; this holds for
as_anipoint(metadata = ) too. After that sampling_rate changes on its
own, so correcting a wrong camera rate means setting
source_sampling_rate as well, in the same call.
A device with no fixed rate, such as an event-driven sensor whose
readings a reader integrates into windows, is declared with
source_sampling_rate = NaN: "no fixed rate", as distinct from NA,
"not declared yet". The first declaration then leaves it alone, and
is.na() is still TRUE for code that only asks whether there is a
recorded rate to use.
get_metadata(), list_default_metadata()
data <- example_anipoint() # Set metadata using named arguments data <- set_metadata(data, sampling_rate = 30, source = "sleap") # Set metadata using a list md <- list(sampling_rate = 30, source = "sleap") data <- set_metadata(data, metadata = md) # The first declared rate is also recorded as the device's af <- set_metadata(example_anipoint(), sampling_rate = 200) get_metadata(af, "source_sampling_rate") # Later rates are the data's own, as after resampling af <- set_metadata(af, sampling_rate = 50) get_metadata(af, c("sampling_rate", "source_sampling_rate")) # A sensor with no fixed rate trackball <- example_anipoint() |> set_metadata(source_sampling_rate = NaN) |> set_metadata(sampling_rate = 100) get_metadata(trackball, "source_sampling_rate")data <- example_anipoint() # Set metadata using named arguments data <- set_metadata(data, sampling_rate = 30, source = "sleap") # Set metadata using a list md <- list(sampling_rate = 30, source = "sleap") data <- set_metadata(data, metadata = md) # The first declared rate is also recorded as the device's af <- set_metadata(example_anipoint(), sampling_rate = 200) get_metadata(af, "source_sampling_rate") # Later rates are the data's own, as after resampling af <- set_metadata(af, sampling_rate = 50) get_metadata(af, c("sampling_rate", "source_sampling_rate")) # A sensor with no fixed rate trackball <- example_anipoint() |> set_metadata(source_sampling_rate = NaN) |> set_metadata(sampling_rate = 100) get_metadata(trackball, "source_sampling_rate")
A frame can carry any number of named anistructure()s, each spanning the
levels of one identity or temporal variable. Several may span the same
variable and overlap: a football frame can hold team, defence and
left_flank over individual alongside a skeleton over keypoint. A
structure's points are its members; levels it does not list are
outside it.
set_structure() attaches a structure under name, replacing one of
the same name.
get_structure() returns all structures as a named list, or one.
remove_structure() drops one.
Points not found in the data are kept with a warning, in case they are recorded in another file.
set_structure(data, structure, variable = "keypoint", name = variable) get_structure(data, name = NULL) remove_structure(data, name)set_structure(data, structure, variable = "keypoint", name = variable) get_structure(data, name = NULL) remove_structure(data, name)
data |
An anipoint. |
structure |
An |
variable |
The |
name |
Name to store the structure under. Defaults to |
set_structure() and remove_structure(): data, with the
structures updated. get_structure(): a named list of structures, or
one structure.
af <- example_anipoint(n_obs = 3, n_individuals = 2) af <- set_structure(af, example_structure()) get_structure(af, "keypoint") # Several structures over the same variable af <- af |> set_structure(anistructure(points = c("1", "2")), "individual", "pair") |> set_structure(anistructure(points = "1"), "individual", "focal") names(get_structure(af))af <- example_anipoint(n_obs = 3, n_individuals = 2) af <- set_structure(af, example_structure()) get_structure(af, "keypoint") # Several structures over the same variable af <- af |> set_structure(anistructure(points = c("1", "2")), "individual", "pair") |> set_structure(anistructure(points = "1"), "individual", "focal") names(get_structure(af))
Run-length-encodes per-frame state and point variables into the
long-format anievent(). Works on a data frame (with bare-name
selection of the event columns and explicit time / identity)
or on an anipoint() (where everything is read from metadata).
to_anievent(data, ...) ## S3 method for class 'anievent' to_anievent(data, ...) ## S3 method for class 'data.frame' to_anievent( data, time, state = NULL, point = NULL, variables_what = NULL, variables_when = NULL, metadata = list(), ... ) ## S3 method for class 'anipoint' to_anievent( data, variables_what = NULL, variables_when = NULL, metadata = list(), ... )to_anievent(data, ...) ## S3 method for class 'anievent' to_anievent(data, ...) ## S3 method for class 'data.frame' to_anievent( data, time, state = NULL, point = NULL, variables_what = NULL, variables_when = NULL, metadata = list(), ... ) ## S3 method for class 'anipoint' to_anievent( data, variables_what = NULL, variables_when = NULL, metadata = list(), ... )
data |
A data frame or an |
... |
Passed to methods. |
time |
For data-frame input, the column holding per-frame times. Bare name (tidyselect). Required. |
state |
For data-frame input, columns to run-length-encode
as state bouts. Bare names (tidyselect). Logical columns produce
bouts on TRUE-runs, labelled by the column name; factor or
character columns produce one bout per contiguous non- |
point |
For data-frame input, columns to encode as point
bouts. Bare names (tidyselect). Logical columns produce one
point bout per TRUE frame, labelled by the column name; factor
or character columns produce one bout per non- |
variables_what |
For data-frame input, identity columns
(e.g. |
variables_when |
For data-frame input, additional temporal-
grouping columns (e.g. |
metadata |
Optional list of metadata attached to the result.
For an anipoint input, fields like |
Distinct from as_anievent(): that one is a strict cast — the
input must already be in canonical anievent shape (one row per
bout, with channel / type / label / start / stop).
to_anievent() is the encoding verb that produces that shape
from per-frame data.
An anievent().
df <- data.frame( individual = 1L, time = 1:8, behaviour = factor(c("REM", "REM", "REM", "wake", "wake", "REM", "REM", NA)), woke_up = c(FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE), call = c(NA, "alarm", NA, NA, NA, NA, NA, NA) ) to_anievent( df, time = time, state = c(behaviour, woke_up), point = call, variables_what = individual )df <- data.frame( individual = 1L, time = 1:8, behaviour = factor(c("REM", "REM", "REM", "wake", "wake", "REM", "REM", NA)), woke_up = c(FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE), call = c(NA, "alarm", NA, NA, NA, NA, NA, NA) ) to_anievent( df, time = time, state = c(behaviour, woke_up), point = call, variables_what = individual )
Reverses the discontinuity introduced by wrapping, by accumulating the
shortest step between successive angles. A heading that crosses the end of
its range, pi in the signed range wrap_angle() gives, therefore
continues to increase rather than jumping back to -pi, which is what makes
it differentiable. NA values are preserved in place.
unwrap_angle(x)unwrap_angle(x)
x |
A numeric vector of angles, in radians, in any range. |
A numeric vector the same length as x, without wrapping
discontinuities. It starts at the first non-missing angle of x and is
not confined to any range.
Other angle utilities:
angle_to_rad(),
deg_to_rad(),
rad_to_deg(),
wrap_angle()
# A heading turning steadily past a full circle, wrapped to (-pi, pi] wrapped <- wrap_angle(seq(0, 3 * pi, length.out = 7)) wrapped # Unwrapping restores the steady progression unwrap_angle(wrapped)# A heading turning steadily past a full circle, wrapped to (-pi, pi] wrapped <- wrap_angle(seq(0, 3 * pi, length.out = 7)) wrapped # Unwrapping restores the steady progression unwrap_angle(wrapped)
Re-runs the structural invariants of an anievent object on demand:
required columns (channel, type, label, start, stop)
are present with the expected types — hard errors;
type is a factor with levels c("state", "point") — hard
error;
stop >= start for every row — hard error;
modifiers, if present, is a list-column whose cells are
character vectors — hard error;
within each (identity + temporal-grouping) group, two bouts of
the same channel should not overlap — warning only.
Overlapping bouts within a channel are permitted on the
anievent side; this is the form the data takes from coding
tools that allow non-mutually-exclusive coding (e.g. BORIS
without strict ethogram).
validate_anievent(data)validate_anievent(data)
data |
An anievent object. |
The input data, invisibly.
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) try(validate_anievent(af))af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) try(validate_anievent(af))
Re-checks, on demand, that an anipoint's metadata still describes the
frame it is attached to. The two drift apart silently under ordinary
dplyr work: dplyr::select() drops a column without touching the
metadata that names it, and assignment can change a column's type. The
invariants are therefore checked rather than assumed:
validate_anipoint(data, rate_tolerance = 0.01)validate_anipoint(data, rate_tolerance = 0.01)
data |
An anipoint object. |
rate_tolerance |
How far the declared |
the index column is present and numeric — hard error;
every declared column is present in the data — hard error;
every position column is numeric — hard error;
coordinate_system agrees with the axis roles — warning
only. The frame is still usable, and the field is derived rather
than declared, so it can be refreshed;
identity, temporal context and the index together name one observation per row — warning only (#49);
a declared sampling_rate agrees with the spacing of the index, to
within rate_tolerance — warning only (#114). The spacing is the
median gap, so a few dropped frames do not count against it.
The input data, invisibly.
ensure_is_spatial() for the spatial subset of these checks,
which is the part downstream filters need on every call;
validate_anievent() for the anievent equivalent.
af <- anipoint(time = 1:5, x = 1:5, y = 1:5) validate_anipoint(af)af <- anipoint(time = 1:5, x = 1:5, y = 1:5) validate_anipoint(af)
Checks the rules a structure has to satisfy:
points are unique and not NA;
every segment joins two known, different points, and any expected
length is positive;
every joint pairs two different, defined segments, about an axis that is
"x", "y", "z" or a segment name;
a joint's min is not above its max, and its rest lies between them;
root is one of the points, or NA.
anistructure() and set_structure() call it, so a structure built there is
already valid; call it yourself after editing a structure's parts by hand.
validate_anistructure(x)validate_anistructure(x)
x |
An |
x, invisibly; errors naming the first problem otherwise.
validate_anistructure(example_structure())validate_anistructure(example_structure())
The variables metadata category says which columns play which role.
Each role is a list of named slots:
what: keys, the identity columns.
when: keys, the temporal context (session, trial), plus index on
an anipoint or interval (start, stop) on an anievent.
where: position, axis role to column, and optionally
orientation: yaw in 2D or a unit quaternion qw, qx, qy, qz
in 3D (anipoint only).
event: state and point, the per-frame event columns (anipoint
only).
The frame is grouped by the keys of what and when; see get_keys().
Declaring restructures the frame to match — columns are retyped,
reordered and regrouped, and coordinate_system is re-derived — so the
metadata and the frame cannot drift apart. A column must exist before it
is declared.
set_variables() replaces the slots it is given and leaves the rest.
add_variables() appends to them.
remove_variables() drops columns from them.
Each role argument takes a named list of slots, or a character vector for
the role's main slot: keys for what and when, position for
where. In remove_variables() a character vector drops the columns
from every slot of the role.
get_variables(data, role = NULL, slot = NULL) set_variables(data, what = NULL, when = NULL, where = NULL, event = NULL) add_variables(data, what = NULL, when = NULL, where = NULL, event = NULL) remove_variables(data, what = NULL, when = NULL, where = NULL, event = NULL)get_variables(data, role = NULL, slot = NULL) set_variables(data, what = NULL, when = NULL, where = NULL, event = NULL) add_variables(data, what = NULL, when = NULL, where = NULL, event = NULL) remove_variables(data, what = NULL, when = NULL, where = NULL, event = NULL)
data |
An aniframe. |
role |
Role to read, or |
slot |
Slot within |
what, when, where, event
|
A named list of slots, or a character vector for the role's main slot. |
get_variables(): the whole category; the columns of a role,
unnamed; or one slot as stored. The setters: data, restructured.
get_keys(), get_index(), set_index(), get_axes()
af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) get_variables(af, "what") get_variables(af, "where", "position") # Declaring an identity column groups the frame by it af |> dplyr::mutate(id = "a") |> add_variables(what = "id") |> dplyr::group_vars() # Axis roles map to columns of any name df <- data.frame(time = 1:3, u = c(1, 2, 3), v = c(0, 1, 0)) as_anipoint(df, variables_where = c("u", "v")) |> set_variables(where = c(x = "u", y = "v")) |> get_metadata("coordinate_system")af <- example_anipoint(n_obs = 3, n_individuals = 1, n_keypoints = 1) get_variables(af, "what") get_variables(af, "where", "position") # Declaring an identity column groups the frame by it af |> dplyr::mutate(id = "a") |> add_variables(what = "id") |> dplyr::group_vars() # Axis roles map to columns of any name df <- data.frame(time = 1:3, u = c(1, 2, 3), v = c(0, 1, 0)) as_anipoint(df, variables_where = c("u", "v")) |> set_variables(where = c(x = "u", y = "v")) |> get_metadata("coordinate_system")
Wraps a vector of angles to a standard interval using modulo arithmetic.
By default that is the signed range (-pi, pi], which the suite uses for
every direction; see the section below.
wrap_angle(x, modulo = c("pi", "2pi", "asis"))wrap_angle(x, modulo = c("pi", "2pi", "asis"))
x |
A numeric vector of angles, in radians. |
modulo |
A character string (default
|
A numeric vector the same length as x, wrapped to the chosen range.
Directions throughout the animovement suite are signed, in (-pi, pi]
((-180, 180] in degrees). It is the range atan2() returns, so per-row
directions such as a course or a heading come out in it already, and the
circular summaries circ_mean() and circ_median() return it too, so a
summary can be compared with the values it summarises. 0 points along
x, and the sign says which side of x a direction lies on: positive
toward y, negative away from it. Which way that turns on screen is the
frame's angle direction, get_angle_direction().
The range does not change what a direction means: -pi / 2 and
3 * pi / 2 are the same direction, and every circ_*() function treats
them alike. To report directions in [0, 2*pi) instead, wrap at the end
with wrap_angle(x, "2pi"). Differences (circ_difference()) are signed
in (-pi, pi] as well, while unwrapped and cumulative angles
(unwrap_angle()) are not confined to any range. The
orientation article
covers the convention alongside the frame's axis directions.
Other angle utilities:
angle_to_rad(),
deg_to_rad(),
rad_to_deg(),
unwrap_angle()
angles <- c(-pi, 0, pi, 3 * pi / 2, 2 * pi, 3 * pi) # The signed range, which directions use throughout the suite wrap_angle(angles) # The same angles on [0, 2*pi) wrap_angle(angles, "2pi") # "asis" is a no-op, useful when the range is chosen by a caller wrap_angle(angles, "asis")angles <- c(-pi, 0, pi, 3 * pi / 2, 2 * pi, 3 * pi) # The signed range, which directions use throughout the suite wrap_angle(angles) # The same angles on [0, 2*pi) wrap_angle(angles, "2pi") # "asis" is a no-op, useful when the range is chosen by a caller wrap_angle(angles, "asis")