-
Notifications
You must be signed in to change notification settings - Fork 2
/
demo_MDD.m
456 lines (351 loc) · 15 KB
/
demo_MDD.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
%% Multidimensional Dictionary - Demo
% File demonstrating some of the basic capabilities of MDD on a sample
% neuroscience dataset. For full instructions on using MDD, see
% tutorial_MDD.m
%% Overview
% MDD is a MATLAB tool for managing high-dimensional data that often arises
% in scientific data analysis. It can be thoughout of as a MATLAB cell
% array (or matrix) with some additional functionality.
%% Set up paths and formatting
% Format
format compact
format short g
% Check if in MDD folder
if ~exist(fullfile('.','data','sample_data.mat'), 'file')
error('Should be in MDD folder to run this code.')
end
% Add MDD toolbox to Matlab path if needed
if ~exist('MDD','class')
addpath(genpath(pwd));
end
%% Load a sample dataset and build an MDD object
% Load some sample simulated data
load('sample_data.mat');
load('sample_data_meta2.mat');
%%%
% The file _sample_data.mat_ contains _dat_, a 4-dimensional cell array:
whos dat
%%
% Each cell in dat contains some neural time series data. For example
dat(1,1,2,1)
%%
% Use this to build an MDD object.
% Construct MDD object
mdd = MDD(dat,axis_vals,axis_names);
%%%
% Note that _axis_vals_ and _axis_names_ contain
% useful metadata, but their details are not important here.
%% What is an MDD object and why use one?
% When working with high-dimensional data, like that cell array _dat_, it's
% often difficult to keep track of what each dimension represents.
% *MDD objects provide a way of organizing and manipulating this
% information.*
%%%
% Let's print a summary of the data contained within _mdd_:
% Print object summary
mdd.printAxisInfo
%%
% This tells us several things about our MDD object:
%%%
% * It is a 4-dimensional object with mdd.size = [3,3,2,8]
% * The four axes are titled: param1, param2, populations, and variables
% * This also summarizes the values that each axis takes on, and the corresponding
% data type of those values (numeric or cellstr). For example, the
% 'populations' axis takes on values 'E' and 'I' (for excitatory and
% inhibitory cells, respectively).
%%%
% An MDD object can be thought of in several ways:
%%%
% * A MATLAB matrix or cell array that can be indexed by using strings and
% regular expressions
% * An N-dimensional table (a table is equivalent to an MDD object in
% 2-dimensions)
% * A map/dictionary that associates multiple keys with a single value
%% MDD subscripts and indexing
% MDD contains several options for how to index data. The most basic method
% is to just index MDD objects like you would index normal Matlab matrices and
% cells. For example:
% Basic indexing
mdd3D = mdd(:,:,1,2:3);
mdd3D.printAxisInfo
%%
% This selects everything from dimensions 1 and 2, the 1st element from dimension 3, and the 2nd and 3rd
% elements from dimension 4. Another way to do this, however, is to
% reference the axis values directly. For example:
% Indexing by string query
mdd3D = mdd(:,:,'E','iNa');
mdd3D.printAxisInfo
%%
% Note that substring matching (based on Matlab's strcmp command)
% was used here to select both iNa_m and iNa_h. However,
% regular expressions can be used as well.
% The "/" denotes to use regular expressions for the search. This command
% picks out all variable names beginning with an "I".
% Indexing by regular expressions
mdd3D = mdd(:,:,'E','/^I/');
mdd3D.printAxisInfo
%%%
%%
% If we only want to specify the values for a single axis, we can use axisSubset.
% This indexes the 'variables' axis for all values containing 'iNa'
% Indexing by axis-value pairs
mdd3D = mdd.axisSubset('variables', 'iNa');
mdd3D.printAxisInfo
%%
% All standard forms of Matlab indexing work as well, including indexing by
% position, linear indexing, and logical indexing.
% Method 1 - Indexing by position
foo1 = mdd(1:3,3,2,8);
% Method 2 - Linear indexing
foo2 = mdd(142:144); % Take the last 3 entries in the data.
% Method 3 - Logical indexing
ind = false(1,144); ind(142:144) = true;
foo3 = mdd(ind); % Using logical indexing also produces the same result.
disp(isequal(foo1,foo2,foo3));
clear foo1 foo2 foo3
%%
% The results of all three indexing methods above are the same. For more details
% about indexing methods in Matlab, see: <https://www.mathworks.com/help/matlab/math/array-indexing.html>
%% Advanced indexing methods
% Finally, MDD supports inline queries using the valSubset method. Here are
% two examples.
% Select axis1 values greater than 5, and axis2 equal to 10
mdd3D = mdd.valSubset('>5','==10',:,:);
mdd3D.printAxisInfo
%%
% Select axis1 values between 8 and 11, and axis2 values between 3 and 11
mdd3D = mdd.valSubset('8 < x < 11','3 < x <= 11',:,:);
mdd3D.printAxisInfo
%% Running functions on MDD objects
% The primary purpose of MDD is to make working with high-dimensional data
% more convenient. Traditionally, if you are working with an N-dimensional
% matrix/cell array, you would write a function that takes in that variable and
% knows what to do with each of the N dimensions (usually involving nested
% for loops).
% This function would not work on data with N-1 or N+1 dimensions.
%%%
% MDD takes a different approach. With MDD, you specify function
% handles that operate on lower dimensional data (usually 1D, 2D, or 3D)
% and to "assign" these to specific dimension in your higher dimensional
% MDD object. The MDD method *recursiveFunc* is used for this. Examples
% below will show how recursiveFunc can be used for plotting. But it is not
% limited to this.
%%%
% *Plotting setup*
%%%
% This section can be safely ignored. Here I'm just adding a metadata
% structure to our mdd object, which will be
% used by some of the following plotting commands. This stores some
% additional metadata about the time series in _mdd_. I'm also defining
% a data structure defining how large to make the figures.
meta = struct;
meta.datainfo(1:2) = MDDAxis;
meta.datainfo(1).name = 'time(ms)';
meta.datainfo(1).values = time;
meta.datainfo(2).name = 'cells';
meta.datainfo(2).values = [];
mdd.meta = meta;
clear meta
op.figwidth=0.4;
op.figheight=0.3;
%% Plotting 3D data
% The workhorse of performing functions on MDD objects is *recursiveFunc*.
% Here is an example of how it is used to plot our data.
%%%
% First, we will pull out a 3D subset of the data. Our data consists of 9
% simulations of a neural network, in the form of a 3x3 parameter sweep
% (axes 1 and 2). For each simulation, we have data for the two different
% types of neurons simulated, excitatory (E) and inhibitory (I) cells. We also have
% the variables associated with each neuron (for example, membrane
% voltage, ionic currents, etc.). Here, we will only look at the voltage of the neurons.
% Pull out a 3D MDD object with only E cell membrane voltage
mdd3D = mdd(:,1:2,:,'v');
mdd3D.printAxisInfo
%%
% Now, let's run the plot:
% Set up plotting arguments
function_handles = {@xp_handles_fignew,@xp_subplot_grid,@xp_matrix_basicplot};
dimensions = {{'populations'},{'param1','param2'},{'data'}};
function_arguments = {{op},{},{}};
% Run the plot. Note the "+" icons next to each plot allow zooming.
close all
mdd3D.recursiveFunc(function_handles,dimensions,function_arguments);
%%%
% To break down what's going on here:
%%%
% * _function_handles_ - A cell array of function handles that specify what
% to do with specific dimensions of the data.
% * _dimensions_ - A cell array that tells which dimensions of the data should be assigned to
% which function handles.
% * _function_arguments_ - A cell array that tells what arguments to pass
% to each of the function handles. Here we pass _op_, which tells xp_handles_fignew
% how big to make the figure.
% * There should be 1 entry in _dimensions_ and _function_arguments_ for every
% function handle supplied. (E.g., their lengths should be equal)
%%%
% In this example, the 'populations' dimension of our data (either
% E cells or I cells) is handled by the function _xp_handles_fignew_, which
% creates a new figure for each value along this dimension provided. The dimensions 'param1'
% and 'param2' are handled by xp_subplot_grid, which creates a grid of
% subplots for the parameter sweep. Note that xp_subplot_grid operates on 2D data. Finally,
% xp_matrix_basicplot creates the actual plots, operating on the time
% series data contained in _mdd.data_
%%%
% Here is the result:
%%
% Note that each of the functions passed to _function_handles_
% is essentially acting on lower-dimensional MDD
% objects. For example, xp_matrix_basicplot operates on 0D data (e.g., a single cell)
% Call xp_matrix_basicplot directly with 0D data (e.g.,
foo = mdd(1,1,2,1);
close all
figure; xp_matrix_basicplot(foo);
%%%
% Similarly, xp_subplot_grid operates on 2D data, but it can only sets up the subplots and cannot
% plot anything. It needs recursiveFunc to chain everything together.
%% Modularization of plotting
% The advantage of recursiveFunc is that the function handles we use can be
% recycled and applied in different contexts. This allows us to work with data of different
% dimensionality and to slice the data in different ways. Below are two
% additional examples of this.
%%%
% *2D plot, sliced to visualize populations vs variables*
%%%
% Here we will use similar methods to look at our data in a different way.
% We will look at a single simulation (param values), and comparing both E
% and I cells and their underlying variables
% Pull out a 2D MDD object (variables axis is indexed using regular expressions)
mdd2D = mdd(2,2,:,'/v|iNa|iK/');
mdd2D.printAxisInfo
% Set up plotting arguments
function_handles = {@xp_subplot_grid,@xp_matrix_basicplot};
dimensions = {{'variables','populations'},{'data'}};
function_arguments = {{},{}};
% Run the plot
close all
figure; mdd2D.recursiveFunc(function_handles,dimensions,function_arguments);
%%
%%%
% *2D plot, sliced to visualize populations vs param1*
%%%
% Here we will repeat our above 3D plot, but instead group populations and
% param1 into the same set of subplots, and ignore param2. We also swapped in xp_matrix_imagesc for
% xp_matrix_basicplot, which represents the data using imagesc plots.
% Pull out a 2D MDD object
mdd2D = mdd(:,2,:,'v');
mdd2D.printAxisInfo
% Set up plotting arguments
function_handles = {@xp_subplot_grid,@xp_matrix_imagesc};
dimensions = {{'populations','param1'},{'data'}};
function_arguments = {{},{}};
% Run the plot
close all
figure; mdd2D.recursiveFunc(function_handles,dimensions,function_arguments);
%% Merging two MDD objects
% MDD also contains methods to perform operations on MDD objects. Here is
% an example of how two MDD objects can be merged together.
% For more details on methods like merge, see tutorial_MDD.m
% Slice the dataset one way
close all
mdd1 = mdd(2,:,'E','v');
% Slice it another way
mdd2 = mdd(:,3,'E','v');
%%
% Notice that mdd1 and mdd2 are overlapping at (2,3,1,1). Hence, when we
% merge, we will set forceMergeBool to true. Without this, it will throw a
% warning
% Merge with overwrite
mdd_merged = merge(mdd1,mdd2,true);
%%
% Now, plot the merged data
dimensions = {[1,2],0};
close all; figure; recursiveFunc(mdd_merged,{@xp_subplot_grid,@xp_matrix_imagesc},dimensions);
%%
% Compare to the original dataset
close all; figure; recursiveFunc(mdd(:,:,'E','v'),{@xp_subplot_grid,@xp_matrix_imagesc},dimensions);
%% Packing MDD dimensions (analogous to cell2mat)
% MDD object properties include the raw data (usually in the form of a cell array) plus
% associated metadata. The raw data is an accessible property of _mdd_:
% Take a slice of mdd
mdd2 = mdd(:,:,:,1);
% View MDD data
mdd2.data
%%
% There are several basic MDD operations for acting on data within an MDD
% object. _packDim_ allows takes a dimension from the MDD object and
% pushes it into the internal _data_ property. Here we will use it to pack
% the two cell populations (E and I) into _mdd.data_, so that they can be
% plotted simultaneously.
% First, let's average dimension 2 for every cell in _mdd2.data_. This
% averages all the traces together.
mdd2.data = cellfun(@(x) squeeze(mean(x,2)), mdd2.data, 'UniformOutput', false);
mdd2.data
%%
% Now, pack the 'populations' dimension into _mdd2.data_
% Pack populations into mdd2.data
mdd2 = mdd2.packDim('populations');
mdd2.printAxisInfo
%%
% The populations property is now missing from mdd2. Instead, mdd2.data
% contains this information - it is now 1001x2 instead of 10001x1
% View the contents of mdd2.data
mdd2.data
%%
% Plot the result
close all
figure; mdd2.recursiveFunc({@xp_subplot_grid,@xp_matrix_basicplot},{{'param1','param2'},{'data'}});
%%%
% Here, red traces are "I" cells and blue traces are "E" cells.
%% Unpacking MDD dimensions
% Conversely, MDD dimensions can also be unpacked. Here, we will use the
% unpackDim method to pull out individual traces and select a few to
% examine more closely.
% Take a slice of mdd (variables axis is indexed using regular expressions)
mdd2 = mdd(2,2,'E','/v|iNa|iK/');
% Unpack the 2nd dimension from mdd.data (corresponding to individual
% traces) and create a new, 5th dimension of mdd to store this.
src = 2; dest = 5;
mdd2=mdd2.unpackDim(src, dest);
mdd2.printAxisInfo
%%
% We now have a 5th dimension of _mdd2_ corresponding to each of the 80 traces
% that was originally in _mdd2.data_.
% Each of these tracs corresponds to a single cell in the simulation, so we
% can look at these more closely. Let's select a few.
% Select a few cells. Note we are also renaming axis5.
mdd2.axis(5).name = 'Cell Number';
mdd2 = mdd2(:,:,:,:,[1,3,6,9]);
mdd2.printAxisInfo
%%%
% For more details on how to rename axes, see tutorial_MDD.m
%%
% Finally, plot the result
% Plot using recursiveFunc
close all
figure; mdd2.recursiveFunc({@xp_subplot_grid,@xp_matrix_basicplot},{{'variables','Cell Number'},{'data'}});
%% Next steps
% To start using MDD yourself, see `tutorial_MDD.m`. In particular, this
% will provide additional details on how to build your own MDD objects and
% advanced methods to manipulate them.
% Run the following
edit tutorial_MDD.m
%% Summary so far
% At its core, MDD
% extends the way cells and matrices are indexed by allowing string labels to be
% assigned to each dimension of the cell array, similar to how row and column
% names are assigned to a table (e.g., in Pandas or SQL). MDD objects can then be indexed,
% sorted, merged, and manipulated
% according to these labels.
% (Section: MDD subscripts and indexing)
%%%
% Additionally, MDD includes methods for
% performing operations on high dimensional data. The goal is to modularize
% the process of working with high dimensional data. Within MDD, functions
% designed to work on low dimensional data
% (1 or 2 dimension) can each be assigned to each operate on different
% dimensions of a higher dimensional object. Chaining several of these
% functions together can allow the entire high dimensional object to be
% processed. The advantage of this modular approach is that functions can be easily
% assigned other dimensions or swapped out entirely, without necessitating
% substantial code re-writes.
% (Section: Running functions on MDD objects)