1
1
# -*- coding: utf-8 -*-
2
2
import os
3
+ from functools import lru_cache
3
4
import numpy as np
4
5
from numpy import ones , kron , mean , eye , hstack , tile
5
6
from numpy .linalg import pinv
@@ -86,67 +87,61 @@ def _list_outputs(self):
86
87
return outputs
87
88
88
89
89
- def ICC_rep_anova (Y , nocache = False ):
90
+ @lru_cache (maxsize = 1 )
91
+ def ICC_projection_matrix (shape ):
92
+ nb_subjects , nb_conditions = shape
93
+
94
+ x = kron (eye (nb_conditions ), ones ((nb_subjects , 1 ))) # sessions
95
+ x0 = tile (eye (nb_subjects ), (nb_conditions , 1 )) # subjects
96
+ X = hstack ([x , x0 ])
97
+ return X @ pinv (X .T @ X , hermitian = True ) @ X .T
98
+
99
+
100
+ def ICC_rep_anova (Y , projection_matrix = None ):
90
101
"""
91
102
the data Y are entered as a 'table' ie subjects are in rows and repeated
92
103
measures in columns
104
+
93
105
One Sample Repeated measure ANOVA
106
+
94
107
Y = XB + E with X = [FaTor / Subjects]
95
108
96
- This is a hacked up (but fully compatible) version of ICC_rep_anova
97
- from nipype that caches some very expensive operations that depend
98
- only on the input array shape - if you're going to run the routine
99
- multiple times (like, on every voxel of an image), this gives you a
100
- HUGE speed boost for large input arrays. If you change the dimensions
101
- of Y, it will reinitialize automatially. Set nocache to True to get
102
- the original, much slower behavior. No, actually, don't do that.
109
+ ``ICC_rep_anova`` involves an expensive operation to compute a projection
110
+ matrix, which depends only on the shape of ``Y``, which is computed by
111
+ calling ``ICC_projection_matrix(Y.shape)``. If arrays of multiple shapes are
112
+ expected, it may be worth pre-computing and passing directly as an
113
+ argument to ``ICC_rep_anova``.
114
+
115
+ If only one ``Y.shape`` will occur, you do not need to explicitly handle
116
+ these, as the most recently calculated matrix is cached automatically.
117
+ For example, if you are running the same computation on every voxel of
118
+ an image, you will see significant speedups.
119
+
120
+ If a ``Y`` is passed with a new shape, a new matrix will be calculated
121
+ automatically.
103
122
"""
104
- global icc_inited
105
- global current_Y_shape
106
- global dfc , dfe , dfr
107
- global nb_subjects , nb_conditions
108
- global x , x0 , X
109
- global centerbit
110
-
111
- try :
112
- current_Y_shape
113
- if nocache or (current_Y_shape != Y .shape ):
114
- icc_inited = False
115
- except NameError :
116
- icc_inited = False
117
-
118
- if not icc_inited :
119
- [nb_subjects , nb_conditions ] = Y .shape
120
- current_Y_shape = Y .shape
121
- dfc = nb_conditions - 1
122
- dfe = (nb_subjects - 1 ) * dfc
123
- dfr = nb_subjects - 1
123
+ [nb_subjects , nb_conditions ] = Y .shape
124
+ dfc = nb_conditions - 1
125
+ dfr = nb_subjects - 1
126
+ dfe = dfr * dfc
124
127
125
128
# Compute the repeated measure effect
126
129
# ------------------------------------
127
130
128
131
# Sum Square Total
129
- mean_Y = mean (Y )
130
- SST = ((Y - mean_Y ) ** 2 ).sum ()
131
-
132
- # create the design matrix for the different levels
133
- if not icc_inited :
134
- x = kron (eye (nb_conditions ), ones ((nb_subjects , 1 ))) # sessions
135
- x0 = tile (eye (nb_subjects ), (nb_conditions , 1 )) # subjects
136
- X = hstack ([x , x0 ])
137
- centerbit = X @ pinv (X .T @ X , hermitian = True ) @ X .T
132
+ demeaned_Y = Y - mean (Y )
133
+ SST = np .sum (demeaned_Y ** 2 )
138
134
139
135
# Sum Square Error
140
- predicted_Y = centerbit @ Y .flatten ("F" )
141
- residuals = Y .flatten ("F" ) - predicted_Y
142
- SSE = (residuals ** 2 ).sum ()
143
-
144
- residuals .shape = Y .shape
136
+ if projection_matrix is None :
137
+ projection_matrix = ICC_projection_matrix (Y .shape )
138
+ residuals = Y .flatten ("F" ) - (projection_matrix @ Y .flatten ("F" ))
139
+ SSE = np .sum (residuals ** 2 )
145
140
146
141
MSE = SSE / dfe
147
142
148
143
# Sum square session effect - between columns/sessions
149
- SSC = (( mean (Y , 0 ) - mean_Y ) ** 2 ). sum ( ) * nb_subjects
144
+ SSC = np . sum ( mean (demeaned_Y , 0 ) ** 2 ) * nb_subjects
150
145
MSC = SSC / dfc / nb_subjects
151
146
152
147
session_effect_F = MSC / MSE
@@ -162,6 +157,4 @@ def ICC_rep_anova(Y, nocache=False):
162
157
e_var = MSE # variance of error
163
158
r_var = (MSR - MSE ) / nb_conditions # variance between subjects
164
159
165
- icc_inited = True
166
-
167
160
return ICC , r_var , e_var , session_effect_F , dfc , dfe
0 commit comments