EcoTrialStructure
EcoTrialStructure provides simple and natural operations to analyze experiments in economic decision making. You can import both behavioral and physiological data.
Matlab-file import (Padoa-Schioppa lab users)
When the data have already been saved as a .mat
file, the first thing to do is import the data. This package's test/data
folder contains a test file that serves as a useful demo:
julia> using EcoTrialStructure
julia> cts, trs, ets = parsemat(joinpath(pkgdir(EcoTrialStructure), "test", "data", "testfile.mat"));
julia> cts
OrderedCollections.OrderedDict{Int64, CellsTrial{Float64}} with 2 entries:
3 => 63 cells with 69 timepoints
201 => 63 cells with 39 timepoints
julia> trs
OrderedCollections.OrderedDict{Int64, TrialResult} with 2 entries:
3 => TrialResult(nA=0, nB=2, leftA=true, choseA=true)
201 => TrialResult(nA=2, nB=0, leftA=false, choseA=true)
julia> ets
OrderedCollections.OrderedDict{Int64, EventTiming} with 2 entries:
3 => EventTiming(trial_start=0.0 ms, offer_on=2003.0 ms, offer_off=4820.0 m…
201 => EventTiming(trial_start=0.0 ms, offer_on=2002.0 ms, offer_off=4812.0 m…
This test data file was extracted from a much larger & more complete experiment with 237 trials, of which just 2 were used for testing purposes. cts
has information about the cells (see CellsTrial
), trs
about the offers and behavioral decisions (see TrialResult
), and ets
about the timing of events during each trial (see EventTiming
). Each is indexed with the trial index, i.e., ets[3]
extracts the event timing for trial 3.
You can extract additional information from the .mat file; an example is given by positive_cells
.
Manipulating the core types
Extracting data can be done by standard Julia methods, for example:
julia> trs[3].nB
2
There are also a number of convenience utilities, like isforced
to detect whether a given trial presented a "forced" choice. See more examples in Simple utilities.
CellsTrial
provides a number of convenience methods for extracting comparable data from different trials; see the examples in its documentation for a detailed explanation. But as an overall example, here is a demonstration based on the data above in Matlab-file import. Here, we extract dFoF
data triggered at offer_on
across all (two) trials, extracting 2 pre-frames and 8 post-frames:
julia> fs = FrameSeq(:offer_on, -2:7) # 0 corresponds to offer_on
FrameSeq(:offer_on, -2:7)
julia> dFoFs = [cts[trialindex][fs(et), :][2] for (trialindex, et) in ets];
julia> dFoFs[1] # trial 3
10×63 OffsetArray(::Matrix{Float64}, -2:7, 1:63) with eltype Float64 with indices -2:7×1:63:
0.146783 0.479383 0.598108 … 0.0408331 0.0357164 0.0741511
0.177113 0.481758 0.579934 0.0181103 0.029064 0.0962147
0.158358 0.318992 0.679291 0.0356008 -0.0285638 0.242723
0.0949043 0.427107 0.792304 0.105977 -0.0537317 0.26273
0.207441 0.387189 0.50655 0.0597971 -0.0613983 0.135856
0.173742 0.240543 0.49942 … 0.0809025 -0.0496011 0.129161
0.0754508 0.292467 0.44907 0.156167 0.00943657 0.143208
0.115572 0.281595 0.356081 0.0406873 0.0155088 0.122557
0.119914 0.193427 0.418782 0.100677 0.288955 0.19112
0.0501042 0.12239 0.416016 0.0648454 0.622751 0.0572375
and where dFoFs[2]
returns the data for trial 201.
As explanation, fs(et)
"concretizes" the abstract notion of offer_on
to the specific time for the trial corresponding to the events recorded in et
. Frame sampling may not be precisely syncronized with behavioral events, so the frame nearest to the event time is chosen as the basepoint. Because indexing a CellsTrial
with a time interval or FrameSeq
returns both the time interval and the dFoF
data, the final [2]
selects just the dFoF
data.
API reference
.mat-file parsing
EcoTrialStructure.parsemat
— Functionparsemat(filename)
parsemat(readfunction, filename)
Parse the trial data in filename
. Optionally pass a readfunction
to extract additional elements; readfunction
should have syntax
function readfunction(data, args...)
moredata = data["matlab_variable_name"]
# maybe do some processing/validation
return args..., moredata
end
data
is the result of MAT.matread(filename)
.
See also: positive_cells
.
EcoTrialStructure.positive_cells
— Functionpositive_cells(data, args...)
Extract the vector of idx_positive_cells
from the .mat file.
Core types
EcoTrialStructure.CellsTrial
— TypeCellsTrial(t, dFoF)
Store cellular responses for a single trial. t
is the list of frame times (in units of time, see Unitful.jl) and dFoF
is a matrix with one row for each time in t
, and one column per cell.
Examples
julia> dFoF = [ 0.1 0.8;
-0.1 0.7;
0.2 0.6;
0.1 0.5;
0.0 0.4];
julia> ct = CellsTrial((100:100:500) * ms, dFoF)
2 cells with 5 timepoints
It's straightforward to extract the dFoF values over a particular time interval:
julia> tframes, df = ct[175ms..310ms, :]; # frames range nearest to given start..stop times
julia> tframes
200.0f0 ms..300.0f0 ms
julia> df
2×2 Matrix{Float64}:
-0.1 0.7
0.2 0.6
This extracted the dFoF
values recorded between 200ms and 300ms, inclusive.
In other cases, you might want a specific number of frames, starting at a particular time:
julia> tframes, df = ct[FrameSeq(175ms, 3), :]; # start nearest to 175ms, and grab 3 frames' worth
julia> tframes
200.0f0 ms..400.0f0 ms
julia> df
3×2 OffsetArray(::Matrix{Float64}, 0:2, 1:2) with eltype Float64 with indices 0:2×1:2:
-0.1 0.7
0.2 0.6
0.1 0.5
EcoTrialStructure.FrameSeq
— TypeFrameSeq(tstart, nframes)
FrameSeq(tstart, offsetrange)
FrameSeq(eventfield::Symbol, nframes)
A sequence of nframes
frames starting at the nearest timepoint to tstart
. Optionally specify a range of frames offset from tstart
.
julia> fs = FrameSeq(100ms, 5)
FrameSeq(100.0f0 ms, 0:4)
julia> fs = FrameSeq(100ms, -2:4)
FrameSeq(100.0f0 ms, -2:4)
See CellsTrial
for an example using this in indexing.
Alternatively, this can be constructed specifying a particular fieldname of EventTiming
, in which case the concrete timing can be deferred until a later time based on a specific trial:
julia> fs = FrameSeq(:go, 5)
FrameSeq(:go, 0:4)
julia> et = EventTiming(0ms, 100ms, 400ms, 450ms, 837ms, 1.2s)
EventTiming(trial_start=0.0f0 ms, offer_on=100.0f0 ms, offer_off=400.0f0 ms, go=450.0f0 ms, choice=837.0f0 ms, trial_end=1200.0f0 ms)
julia> fs(et)
FrameSeq(450.0f0 ms, 0:4)
EcoTrialStructure.OfferType
— TypeOfferType(nA, nB, leftA::Bool)
Encode the offers (nA
and nB
are the number of drops of A and B, respectively), and whether A was on the left.
If you have a list of OfferType
s, you can sort
them. High-A trials will come before high-B trials, and left before right.
EcoTrialStructure.TrialResult
— TypeTrialResult(nA, nB, leftA::Bool, choseA::Union{Bool,Missing})
TrialResult(tt::OfferType, choseA::Union{Bool,Missing})
Encode the offer configuration (see OfferType
), and whether the animal chose A, B, or failed to make a choice (choseA = true | false | missing
, respectively).
EcoTrialStructure.EventTiming
— TypeEventTiming(trial_start, offer_on, offer_off, go, choice, trial_end)
Specify the timing of events during a trial. All times should be in physical units (s
or ms
).
Examples
julia> EventTiming(0ms, 100ms, 400ms, 450ms, 837ms, 1.2s)
EventTiming(trial_start=0.0f0 ms, offer_on=100.0f0 ms, offer_off=400.0f0 ms, go=450.0f0 ms, choice=837.0f0 ms, trial_end=1200.0f0 ms)
Simple utilities
EcoTrialStructure.madechoice
— Functionmadechoice(tr::TrialResult)
Return true
if the animal made a choice in the trial.
EcoTrialStructure.isforced
— Functionisforced(tt::OfferType)
isforced(tr::TrialResult)
Return true
for a forced-choice trial, where either nA
or nB
is zero.
EcoTrialStructure.iswrong
— Functioniswrong(tr::TrialResult)
Return true
if tr
is a forced-choice trial and the animal chose the wrong option.
EcoTrialStructure.isdeferred
— Functionisdeferred(fs::FrameSeq)
Return true
if the start time in fs
is a field name of EventTiming
, and hence requires concrete instantiation as fs(et)
for a specific trial.
EcoTrialStructure.ncells
— Functionncells(ct::CellsTrial)
Return the number of cells stored in the dFoF
field of ct
.