Reading, Converting, and Filtering Tracking Data

knitr::opts_chunk$set(dpi=72)

Introduction

The package implements a special data structure, the tracks object, to allow rapid computation of different analysis metrics on cell tracks. This tutorial will show how to load tracking data, how to deal with tracking objects, how to filter and subset data, and how to convert between track objects and other datastructures.

1 Reading in data

First load the package:

library( celltrackR )
library( ggplot2 )
# Save current par() settings
oldpar <- par( no.readonly =TRUE )

1.1 Input data format

Tracking data is usually stored as a table, with columns indicating the cellid, time, and coordinates of each measured point. Here we have an example in the file "t-cells.txt", which we can read in as a normal dataframe:

d <- read.table( system.file("extdata", "t-cells.txt", package="celltrackR" ) )
str(d)
head(d)

The result is a normal dataframe, where here we have an index for the time point in the first column, cell id in the second column, the actual time (in seconds) in the third column, and $(x,y,z)$ coordinates in columns 4:6.

1.2 Directly reading in data as a tracks object

While we can read tracks as a dataframe by using R's basic function read.table(), the function read.tracks.csv() allows to read in data directly as a tracks object, a special data structure designed for efficient handling of tracking data.

Applying this to the same file as before:

t <- read.tracks.csv( system.file("extdata", "t-cells.txt", package="celltrackR" ), 
              header = FALSE, 
                      id.column = 2, time.column = 3, pos.columns = 4:6 )
plot(t)

where we have to specify header=FALSE because the file does not contain any column headers. Note that read.tracks.csv() also works with non-csv text files, as long as the data is organised with separate columns for track id, time index, and coordinates. See the documentation at ?read.tracks.csv for details.

These tracks are of T cells imaged in the cervical lymph node of a healthy mouse; they are the raw data from which the TCells dataset in the package was obtained. See the vignette on preprocessing the package datasets for details.

2 The tracks object

2.1 The tracks object data structure

The tracks object is a special datastructure that allows efficient handling of track datasets. As an example, we will use the tracks loaded in the previous section.

A tracks object has the form of a list, where each element of the list is a track of a single cell:

# Structure of the TCells object
str( t, list.len = 3 )

# This object is both a list and a "tracks" object
is.list( t )
is.tracks( t )

# The first element is the track of the first cell in the data:
head( t[[1]] )

Each track in the tracks object is a matrix with coordinates at different timepoints for each cell. The cell id is no longer a column in this matrix, as tracks belonging to different cells are stored in different elements of the tracks object list.

2.2 Subsetting data

Note that we can subset the track matrix of an individual track using the double square brackets:

# Get the first track
t1 <- t[[1]]
str(t1)

# This is no longer a tracks object, but a matrix
is.tracks( t1 )
is.matrix( t1 )

If we now want to plot this track, the plotting method for tracks will not work because this is not recognized as a tracks object. We can use the frunction wrapTrack() to "pack" this matrix back into a tracks object:

par( mfrow=c(1,2) )
plot( t1, main = "Plotting matrix directly" )
plot( wrapTrack( t1 ), main = "After using wrapTrack()" )

Note that we can also achieve this by subsetting with single instead of double brackets:

# Get the first track
t1b <- t[1]
str(t1b)

# This remains a track object
is.tracks( t1b )

In the same way, we can also subset multiple tracks at once

# Get the first and the third track
t13 <- t[c(1,3)]
str(t13)

Note that the track ids are strings that do not always correspond to the index of the track in the dataset. If we want the ones with ids 1 and 3, we can subset using the track name as a character string:

# Get tracks with ids 1 and 3
t13b <- t[c("1","3")]
str(t13b)

2.3 Using tracks objects in combination with R's lapply and sapply

Because tracks objects are lists, we can make use of R's lapply() and sapply() functions to compute metrics or manipulate tracks efficiently.

For example, if we want to compute the speed of each track, we simply use:

speeds <- sapply( t, speed )
head(speeds)

Note that sapply() applies the speed() function to each matrix in the track list (analogous to subsetting with double brackets). Thus, the speed() function sees an individual track matrix, not a tracks object.

Or we can use lapply() to manipulate each track in the dataset with some custom function, keeping separate tracks as separate list elements. For example, suppose we wish to remove all data after a given timepoint:

# Function to remove all data after given timepoint
# x must be a single track matrix, which is what this function will
# receive from lapply
removeAfterT <- function( x, time.cutoff ){

  # Filter out later timepoints
  x2 <- x[ x[,"t"] <= time.cutoff, ]

  # Return the new matrix, or NULL if there are no timepoints before the cutoff
  if( nrow(x2) == 0 ){
    return(NULL)
  } else {
    return(x2)
  }
}

# Call function on each track using lapply
filtered.t <- lapply( t, function(x) removeAfterT( x, 200 ) )

# Remove any tracks where NULL was returned
filtered.t <- filtered.t[ !sapply( filtered.t, is.null )]

Note that lapply() returns list but not a tracks object:

str(filtered.t, list.len = 3 )
is.list( filtered.t )
is.tracks( filtered.t )

We can fix this by calling as.tracks().

filtered.t <- as.tracks( filtered.t )
is.tracks( filtered.t )

str(filtered.t, list.len = 1)

We now have a new tracks object, which contains only tracks that had coordinates at $t<200$.

2.4 Built-in filtering/subsetting functions

The package contains several built-in functions to filter and subset tracks.

The function filterTracks() can be used to select tracks with a certain property. For example, to select all tracks with at least 15 steps (16 datapoints):

# The filtering function must return TRUE or FALSE for each track given to it
my.filter <- function(x){
  return( nrow(x) > 15 )
}

# Filter with this function using filterTracks
long.tracks <- filterTracks( my.filter, t )

# check the minimum track length; # steps = number of coordinates minus 1
min( sapply( long.tracks, nrow ) - 1 )

The function selectTracks() selects tracks based on upper and lower bounds of a certain measure. For example, we can get the fastest half of the T cells:

# Filter with this function using filterTracks
median.speed <- median( sapply( t, speed ) )
fast.tracks <- selectTracks( t, speed, median.speed, Inf )

# these should have a higher mean speed
c( "all tracks" = mean( sapply( t, speed ) ),
   "fastest half" = mean( sapply( fast.tracks, speed ) ) )

Another option is to filter not tracks, but timepoints within those tracks. Using the function subsample(), we can adjust the time resolution of the data by keeping e.g. only every $k^{th}$ timepoint:

# Lower resolution
lower.res <- subsample( t, k = 2 )

# Plot the result; plot just one track to see the result more clearly
par(mfrow=c(1,2))
plot( t[1], main = "Original data")
plot( lower.res[1], main = "Lower resolution" )

2.5 Extracting subtracks

The package also contains functions to extract parts of tracks. For example, use subtracks() to extract subtracks of a given length:

subtrack.nsteps <- 2
t.2steps <- subtracks( t, subtrack.nsteps )
str( t.2steps, list.len = 3 )

Note that these subtracks overlap:

# Last step of the first subtrack and first step of the second are equal
t.2steps[c(1,2)]

We can prevent this by adjusting the overlap argument to 0, or even to negative values so that space is left between the subtracks:

t.2steps.b <- subtracks( t, subtrack.nsteps, overlap = 0 )

# No longer any overlap
t.2steps.b[c(1,2)]

An alternative to subtracks() is prefixes(), which returns only the first subtrack of a given length from each track:

t.prefixes <- prefixes( t, subtrack.nsteps )

# these subtracks come from different cells
t.prefixes[c(1,2)]

If we want to extract subtracks starting at a specific timepoint, use subtracksByTime():

# Check which timepoints occur in the dataset
tp <- timePoints(t)
tp

# Extract all subtracks starting from the third timepoint
t.sbytime <- subtracksByTime( t, tp[3], subtrack.nsteps )

t.sbytime[c(1,2)]

3 Converting between tracks objects and other data structures

We can convert between tracks, regular R lists, and dataframes using as.tracks(), as.list(), or as.data.frame():

# Original tracks object
str( t, list.len = 3 )

# Converted to dataframe
t.df <- as.data.frame(t)
str( t.df )

# Converted to list (note class at the bottom)
t.list <- as.list(t)
str( t.list, list.len = 3 )

# Convert list back to tracks
str( as.tracks( t.list ), list.len = 3 )

# Convert dataframe to tracks
str( as.tracks( t.df ), list.len = 3 )

Note that the method as.tracks.data.frame() contains arguments id.column, time.column, and pos.columns to specify where information is stored, just like read.tracks.csv.

For help, see ?as.list.tracks, ?as.data.frame.tracks, ?as.tracks.data.frame, or as.tracks.list.

# Reset par() settings
par(oldpar)


Try the celltrackR package in your browser

Any scripts or data that you put into this service are public.

celltrackR documentation built on March 21, 2022, 5:06 p.m.