forked from andersonwinkler/PALM
-
Notifications
You must be signed in to change notification settings - Fork 0
/
palm_tree.m
283 lines (245 loc) · 9.73 KB
/
palm_tree.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
function Ptree = palm_tree(B,M)
% Generates a tree that takes the dependence structure
% between observations into account. From this tree, the
% permutations can be generated later.
%
% Usage:
% Ptree = palm_tree(B,M)
%
% - B : Multi-level block definitions.
% - M : Design matrix.
% - Ptree : Permutation tree, from which permutations are generated
% later.
%
% Each node is a cell with 4 elements:
% N{1,1} : A 3-column array for whole block in which:
% - the 1st is a sequence of indices that indicates the
% current lexicographic permutation.
% - the 2nd are indices that indicate the current
% shuffling in relation to the original
% - the 3rd are indices that indicate the current
% permutation in relation to the previous
% For within-block, this is a NaN.
% N{1,2} : A logical vector indicating the current state of sign
% flips for the tree. 0 is treated as 1, and 1 as -1.
% N{1,3} : The branches that begin here.
%
% Reference:
% * Winkler AM, Webster MA, Vidaurre D, Nichols TE, Smith SM.
% Multi-level block permutation. Neuroimage. 2015;123:253-68.
%
% _____________________________________
% Anderson M. Winkler
% FMRIB / University of Oxford
% Dec/2013
% http://brainder.org
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
% PALM -- Permutation Analysis of Linear Models
% Copyright (C) 2015 Anderson M. Winkler
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program. If not, see <http://www.gnu.org/licenses/>.
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if nargin == 1 || isempty(M),
M = (1:size(B,1))';
elseif size(B,1) ~= size(M,1),
error('The two inputs must have the same number of rows.');
end
% Make some initial sanity checks:
Bs = sortrows(B);
warned = checkblk(Bs,Bs(1)>=0,0);
if warned,
error([...
'Due to one or more of the issues listed above, the block\n' ...
'definition may cause problems when the permutations are generated.\n' ...
'Please, correct the block definition file and try again.%s'],'');
end
% Order of the observations in the original data. Note
% that B should have not been sorted, and should retain
% the order as originally entered by the user (even if
% the rows are totally scrambled).
O = (1:size(M,1))';
% Now make the actual tree.
% The sanity of the block definitions should have already
% been taken care of by the wrapper function, so no need to
% check validity here. If the first element is negative,
% it is fair to assume that so are the remaining of the
% elements in the first column.
wholeblock = B(1) > 0;
Ptree = cell(1,3);
[Ptree{1},Ptree{3}] = maketree( ...
B(:,2:end),M,O,wholeblock,wholeblock);
if wholeblock,
Ptree{2} = false(size(Ptree{3},1),1);
else
Ptree{2} = [];
end
% ==============================================================
function [S,Ptree] = maketree(B,M,O,wholeblock,nosf)
% Now makes the actual tree, each branch recursively.
% - B : Block definitions
% - M : Design matrix
% - O : Observation indices
% - wholeblock : boolean, indicates if this is part of whole-block
% at the immediately upper level.
% - nosf : It also implies no signflip this level, because
% of a higher whole-block)
% Unique blocks at this level & some other vars for later
B1 = B(:,1);
U = unique(B1);
nU = numel(U);
if size(B,2) > 1,
Ptree = cell(nU,3);
else
Ptree = cell(nU,1);
end
% For each block
for u = 1:nU,
% Enter into each unique block
idx = B1 == U(u);
% If this isn't the last level, continue constructing
% the branches recursively.
if size(B,2) > 1,
wholeblockb = B(find(idx,1),1) > 0;
[Ptree{u,1},Ptree{u,3}] = ...
maketree( ...
B(idx,2:end), ...
M(idx,:), ...
O(idx), ...
wholeblockb, ...
wholeblockb || nosf);
Ptree{u,2} = [];
% Count the number of possible sign-flips for these branches
if nosf,
% If it was flipped at higher levels (whole-block)
Ptree{u,2} = [];
elseif size(Ptree{u,3},2) > 1,
% If it might be flipped here, but there are distal branches:
% If this is whole-block, assign a number. If within-block,
% no sign-flips allowed at this level.
if isnan(Ptree{u,1}(1)),
Ptree{u,2} = [];
else
Ptree{u,2} = false(size(Ptree{u,3},1),1);
end
else
% If there are no further branches
Ptree{u,2} = false(size(Ptree{u,3},1),1);
end
else
% At the terminal branches, there is no more tree, so
% just keep track of the observation indices.
Ptree{u,1} = O(idx);
end
end
% Make the sequence of values that are the reference for the
% lexicographic permutations to be done later. This is doesn't
% apply to the highest level.
if wholeblock && nU > 1,
% Identify repeated sets of rows, which receive then
% the same index; these repetitions are taken care of
% later by the Algorithm L.
B1M = sortrows([B1 M]); % B1 is here to be together with M during sorting
Ms = B1M(:,2:end); % but its removed here, as it's of no use.
[~,~,S] = unique(reshape(Ms',[numel(Ms)/nU nU])','rows');
% Put in ascending order, and (un)shuffle the branches
% accordingly
[S,idx] = sort(S);
S = [S (1:numel(S))' (1:numel(S))'];
Ptree = Ptree(idx,:);
elseif wholeblock && nU == 1,
% For whole block starting at the second level, the
% permutation matrix is simply the number 1.
S = [1 1 1];
else
% There isn't whole-block permutation at the 1st level,
% only within-block, so this case is marked as NaN.
S = NaN;
end
% ==============================================================
function warned = checkblk(B,wholeblock,recdepth)
% For a given block, check if:
% - the leftmost column is valid.
% - the blocks are of the same size for whole-block permutation.
% - the sign indicator is the same for all blocks for whole-block
% permutation.
% - the indices aren't 0 or non-integer.
% - the tree branches are all identical for whole-block permutation.
% Note this uses recursion.
% Vars for later
warned = false;
B1 = B(:,1);
U = unique(B1);
nU = numel(U);
Ucnt = zeros(nU,1);
Usgn = Ucnt;
Uvec = cell(nU,1);
% Using positive/negative indices implies that the leftmost column must
% exist and be filled entirely with the same digit.
if recdepth == 0 && numel(U) > 1,
error('The highest level (leftmost column) must be entirely filled by the same value.');
end
% For each block
for u = 1:nU,
if U(u) == 0,
% The index 0 cannot be considered valid (neither
% positive or negative).
warning('The digit 0 should be avoided as block indicator (level %d).\n',recdepth); %#ok
warned = true;
elseif rem(U(u),1) ~= 0,
% Let's avoid fractional indices too.
warning('Non-integer indices should be avoided (level %d).\n',recdepth); %#ok
warned = true;
else
% Enter into each unique block to see what else
idx = B1 == U(u);
if size(B,2) > 1,
% Here the test for whole-block permutation allows 0 as index,
% just so that these otherwise invalid blocks are not ignored.
% The warning message above should raise the user attention.
checkblk(B(idx,2:end),B(find(idx,1),1)>=0,recdepth+1);
% This is to check if the remainder cols are all equal. It's
% necessary that the rows of the original B are sorted for this
% to work.
tmp = B(idx,2:end);
Uvec{u} = tmp(:)';
end
% Check the size and sign of the sub-blocks
Ucnt(u) = sum(idx);
Usgn(u) = sign(U(u));
end
end
% Check if all subblocks within each EB are of the same size.
if wholeblock && any(diff(Ucnt)),
% Note that counting the number of times Matlab/Octave
% reports as the source of the error the line above where
% palm_renumber is called again also helps to identify
% which is the offending block level.
error('Not al sub-blocks within an EB are of the same size at level %d.\n',recdepth);
elseif wholeblock && numel(unique(Usgn)) > 1,
% Make sure that for whole-block permutation, there is no mix of
% sub-blocks with positive and negative signs, as these cannot be
% shuffled.
error('Not all sub-blocks within an EB have the same sign indicator at level %d.\n',recdepth);
elseif wholeblock,
% Now check whether the branches that begin at a given level are all
% identical, as needed for whole-block permutation. Note that for this
% check to work, the blocks must be numbered using the "restart" (i.e.,
% not "continuous") method. See the function palm_reindex.m.
Uvec = cell2mat(Uvec);
Ur = unique(Uvec,'rows');
if size(Ur,1) > 1,
warning('Not all blocks are identical after level %d to allow valid whole-block permutation.\n',recdepth); %#ok
warned = true;
end
end