behavr tables
A single data structure to store data and metadata
a behavr table
Variables and metavariables
As we have seen in the previous section, metadata are crucial for proper statistical analysis of the experimental data. In the context of ethomics, the data are long time series of recorded variables such as position, orientation and number of beam crosses, for each individual.
Variables are different form metavariables in so far as the latter are made of only one value per animal.
It is easier (and less error prone) to always keep the data and metadata together.
In rethomics, in order to handle large amounts of data (together with metadata), we have designed the behavr package.
behavr tables are based on the very powerful package data.table, but enhanced with metadata.
A behavr table is, indeed, formed internally by two tables: the metadata table and the data table, both are linked by the id column (see figure above).
For most purposes, you can use a behavr table just like a data.table.
Therefore, do take a look at the introduction to data.table for further details!
When we load any behavioural data in rethomics, we get a behavr table as a result.
In this section, we will discuss the usual operations that you can perform on behavr tables.
Operating on behavr tables
Now that we have all our data at the same place, we want to be able to manipulate it.
In the next part of this tutorial, we will create some toy data and learn how to manipulate it.
This is where basic knowledge of data.table comes in handy.
The following table is an overview of operations in behavr tables.
DT represents an behavr table.
| Section | Operation | Expression | Example |
|---|---|---|---|
| Generalities | Summarise behavr table |
summary(DT) |
How many individuals, variables, metavariables, etc? – summary(dt) |
| Pure data | Create/alter a variable | DT[, new_column := some_value] |
When are animals ‘very active’? – dt[, very_active := activity >= 2] |
| Remove a variable | DT[, column_to_delete := NULL] |
Lets remove a variable we don’t need? – dt[, very_active := NULL] |
|
| Select data rows | DT[criteria] |
Exclude data before the first hour – small_dt <- dt[t > hours(1)] |
|
| Pure metadata | Access metadata table | DT[meta = TRUE] |
Show metadata as table – dt[meta = TRUE] |
| Create/alter metavariable | DT[, new_meta := some_value, meta=TRUE] |
Define a new factor that is a comibiation of ‘sex x condition’ – dt[, treatment := paste(sex, condition, sep='|'), meta=T] |
|
| Meta & data | Use metavariable as variable | xmv(metavariable) |
Add 10s to all time, only for animals in condition 'A' – dt[, t := ifelse(xmv(condition) == 'A', t + 10, t)] |
| Remove individuals according to metavariable | DT[criteria] |
Remove all males (from data, and metadata) – dt_females <- dt[xmv(sex) == 'females'] |
|
| Summarise | Compute individual statistics | DT[, .( statistics = some_math()), by='id'] |
Compute the average activity, per animal – stat_dt <- dt[, .(mean_acti = mean(active)), by='id'] |
| Rejoin metadata to data | rejoin(DT) |
Merge metadata and summary statistics – stat_dt <- rejoin(stat_dt) |
|
| Advanced | Stitch experiments | stitch_on(DT, metavariable) |
TODO – TODO |
Playing with toy data
The behavr package has a set of functions to make toy data. This provides us with a playgound to test functions and plots without having to get any real data.
In order to understand behavr object, lets create a toy one.
First, we make some dummy metadata (always needed to create a behavr table):
library(behavr)## Loading required package: data.table
metadata <- data.table( id = paste("toy_experiment", 1:10, sep = "|"),
sex = rep(c("male", "female"), each = 5),
condition = c("A", "B") )
metadata## id sex condition
## 1: toy_experiment|1 male A
## 2: toy_experiment|2 male B
## 3: toy_experiment|3 male A
## 4: toy_experiment|4 male B
## 5: toy_experiment|5 male A
## 6: toy_experiment|6 female B
## 7: toy_experiment|7 female A
## 8: toy_experiment|8 female B
## 9: toy_experiment|9 female A
## 10: toy_experiment|10 female B
This metadata describes an hypothetical experiment with ten animals (1:10, five males and five females).
They are exposed to two conditions ("A" and "B").
Then, we use toy_dam_data() to simulate (instead of linking/loading) one day of DAMS-like data for these ten animals (and two conditions):
dt <- toy_dam_data(metadata, duration = days(1))
dt##
## ==== METADATA ====
##
## id sex condition
## <char> <char> <char>
## 1: toy_experiment|1 male A
## 2: toy_experiment|10 female B
## 3: toy_experiment|2 male B
## 4: toy_experiment|3 male A
## 5: toy_experiment|4 male B
## 6: toy_experiment|5 male A
## 7: toy_experiment|6 female B
## 8: toy_experiment|7 female A
## 9: toy_experiment|8 female B
## 10: toy_experiment|9 female A
##
## ====== DATA ======
##
## id t activity
## <char> <num> <int>
## 1: toy_experiment|1 0 0
## 2: toy_experiment|1 60 2
## 3: toy_experiment|1 120 0
## 4: toy_experiment|1 180 1
## 5: toy_experiment|1 240 0
## ---
## 14406: toy_experiment|9 86160 0
## 14407: toy_experiment|9 86220 0
## 14408: toy_experiment|9 86280 2
## 14409: toy_experiment|9 86340 1
## 14410: toy_experiment|9 86400 0
As you can see, when we print dt, our behavr table, we have two sections: METADATA and DATA.
The former is actually just the metadata we created whilst the latter stores the data (i.e. the variables) for all animals.
The special column id is also known as a key, and is shared between both data and metadata.
It internally allows us to map them to one another.
In other words, it is a unique id for each individual.
In this specific example, the variables t and activity are the time and the number of beam crosses, respectively.
Generalities
A quick way to retreive general information about a behavr table is to use summary:
summary(dt)## behavr table with:
## 10 individuals
## 2 metavariables
## 2 variables
## 1.441e+04 measurements
## 1 key (id)
This tells us immediately how many variables, metavariables and data points, we have.
One can also print a detailed summary (i.e. one per animal):
summary(dt, detailed = TRUE)##
## Summary of each individual (one per row):
## id sex condition data_points time_range
## 1: toy_experiment|1 male A 1441 [0 -> 86400 (86400)]
## 2: toy_experiment|10 female B 1441 [0 -> 86400 (86400)]
## 3: toy_experiment|2 male B 1441 [0 -> 86400 (86400)]
## 4: toy_experiment|3 male A 1441 [0 -> 86400 (86400)]
## 5: toy_experiment|4 male B 1441 [0 -> 86400 (86400)]
## 6: toy_experiment|5 male A 1441 [0 -> 86400 (86400)]
## 7: toy_experiment|6 female B 1441 [0 -> 86400 (86400)]
## 8: toy_experiment|7 female A 1441 [0 -> 86400 (86400)]
## 9: toy_experiment|8 female B 1441 [0 -> 86400 (86400)]
## 10: toy_experiment|9 female A 1441 [0 -> 86400 (86400)]
Data
Playing with variables is just like in data.table.
Read the official data.table tutorial for more functionalities.
For instance, we can add a new variable, very_active, that is TRUE if and only if
there was at least two beam crosses in a minute, for a given individual:
dt[, very_active := activity >= 2]If we decide we don’t need this variable anymore, we can remove it:
dt[, very_active := NULL]Sometimes, we would like to filter the data. That is, we select rows according to one or several criteria. Often we would like to exclude the very start of the experiment. For example, we can keep data after one hour:
dt <- dt[ t > hours(1)]Note that that using dt <- mean we make a new table that overwrite the old one (since it has the same name).
Metadata
In order to access the metadata, we can add meta = TRUE inside the []:
dt[meta = TRUE]## id sex condition
## 1: toy_experiment|1 male A
## 2: toy_experiment|10 female B
## 3: toy_experiment|2 male B
## 4: toy_experiment|3 male A
## 5: toy_experiment|4 male B
## 6: toy_experiment|5 male A
## 7: toy_experiment|6 female B
## 8: toy_experiment|7 female A
## 9: toy_experiment|8 female B
## 10: toy_experiment|9 female A
This way, we can also create new metavariables.
For instance, say you want to collapse sex and condition which both have two levels into one treatment, with four levels:
dt[, treatment := paste(sex, condition, sep='|'), meta=T]
# just to show the result:
dt[meta = TRUE]## id sex condition treatment
## 1: toy_experiment|1 male A male|A
## 2: toy_experiment|10 female B female|B
## 3: toy_experiment|2 male B male|B
## 4: toy_experiment|3 male A male|A
## 5: toy_experiment|4 male B male|B
## 6: toy_experiment|5 male A male|A
## 7: toy_experiment|6 female B female|B
## 8: toy_experiment|7 female A female|A
## 9: toy_experiment|8 female B female|B
## 10: toy_experiment|9 female A female|A
paste() is a function that links strings of characters with an arbitrary separator ("|" here).
New metavariables can also be added from a summary (see Summarise data). ### Data & Metadata {-}
The strength of behavr tables is their ability to seamlessly use metavariables as though they were variables.
For the sake of the example, let’s say you would like to alter the variable t (time) so that we add ten seconds, only to individuals that have condition 'A'.
dt[, t := ifelse(xmv(condition) == 'A', t + 10, t)]
The key here is the use of xmv (eXpand MetaVariable), which maps condition back in the data.
We can also use this mechanism to remove individuals according to the value of a metavariable. For instance, lets get rid of the males!
dt <- dt[xmv(sex) == 'female']
summary(dt)## behavr table with:
## 5 individuals
## 3 metavariables
## 2 variables
## 6.9e+03 measurements
## 1 key (id)
When individuals are removed, metadata is automatically updated.
In effect, we removed males from both data and metadata.
This operation cannot be undone, as we overwrite dt with a new value.
An alternative would be to save the result in a new table (e.g. dt_females <- dt[xmv(sex) == 'female'])
This would use some additional memory, but it is safer.
Summarise data
Thanks to data.table by operations, it is simple and efficient to compute statistics per individual.
For instance, we may want to compute the average activity for each animal:
stat_dt <- dt[,
.(mean_acti = mean(activity)),
by='id']
stat_dt##
## ==== METADATA ====
##
## id sex condition treatment
## <char> <char> <char> <char>
## 1: toy_experiment|10 female B female|B
## 2: toy_experiment|6 female B female|B
## 3: toy_experiment|7 female A female|A
## 4: toy_experiment|8 female B female|B
## 5: toy_experiment|9 female A female|A
##
## ====== DATA ======
##
## id mean_acti
## <char> <num>
## 1: toy_experiment|10 0.4420290
## 2: toy_experiment|6 0.1615942
## 3: toy_experiment|7 0.4434783
## 4: toy_experiment|8 0.1731884
## 5: toy_experiment|9 0.2550725
You can actually compute many variables in one go this way:
stat_dt <- dt[,
.(mean_acti = mean(activity),
max_acti = max(activity)
),
by='id']
stat_dt##
## ==== METADATA ====
##
## id sex condition treatment
## <char> <char> <char> <char>
## 1: toy_experiment|10 female B female|B
## 2: toy_experiment|6 female B female|B
## 3: toy_experiment|7 female A female|A
## 4: toy_experiment|8 female B female|B
## 5: toy_experiment|9 female A female|A
##
## ====== DATA ======
##
## id mean_acti max_acti
## <char> <num> <int>
## 1: toy_experiment|10 0.4420290 3
## 2: toy_experiment|6 0.1615942 2
## 3: toy_experiment|7 0.4434783 3
## 4: toy_experiment|8 0.1731884 2
## 5: toy_experiment|9 0.2550725 2
Then, if needed, this summary can be added back to the metadata:
# create new metadata table by joining current meta and the summary table
new_meta <- dt[stat_dt, meta=T]
# set new metadata
setmeta(dt, new_meta)
head(dt[meta=T])## id sex condition treatment mean_acti max_acti
## 1: toy_experiment|10 female B female|B 0.4420290 3
## 2: toy_experiment|6 female B female|B 0.1615942 2
## 3: toy_experiment|7 female A female|A 0.4434783 3
## 4: toy_experiment|8 female B female|B 0.1731884 2
## 5: toy_experiment|9 female A female|A 0.2550725 2
This way we can store per-individual aggregates and visualise or analyse them with respect to the pre-existing metadata.
Now, in order to perform statistics, we would like to merge our summaries to the metadata.
This way we end up with only one data.table
That is, we want to rejoin them to one another (i.e. we enrich our summaries with the metadata):
final_dt <- rejoin(stat_dt)
final_dt## id sex condition treatment mean_acti max_acti
## 1: toy_experiment|10 female B female|B 0.4420290 3
## 2: toy_experiment|6 female B female|B 0.1615942 2
## 3: toy_experiment|7 female A female|A 0.4434783 3
## 4: toy_experiment|8 female B female|B 0.1731884 2
## 5: toy_experiment|9 female A female|A 0.2550725 2
This table is exactly what you need for statistics and visualisation in R!