Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/statsmodels/multivariate/factor_rotation/_wrappers.py : 8%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
4from ._analytic_rotation import target_rotation
5from ._gpa_rotation import oblimin_objective, orthomax_objective, CF_objective
6from ._gpa_rotation import ff_partial_target, ff_target
7from ._gpa_rotation import vgQ_partial_target, vgQ_target
8from ._gpa_rotation import rotateA, GPA
10__all__ = []
13def rotate_factors(A, method, *method_args, **algorithm_kwargs):
14 r"""
15 Subroutine for orthogonal and oblique rotation of the matrix :math:`A`.
16 For orthogonal rotations :math:`A` is rotated to :math:`L` according to
18 .. math::
20 L = AT,
22 where :math:`T` is an orthogonal matrix. And, for oblique rotations
23 :math:`A` is rotated to :math:`L` according to
25 .. math::
27 L = A(T^*)^{-1},
29 where :math:`T` is a normal matrix.
31 Parameters
32 ----------
33 A : numpy matrix (default None)
34 non rotated factors
35 method : str
36 should be one of the methods listed below
37 method_args : list
38 additional arguments that should be provided with each method
39 algorithm_kwargs : dictionary
40 algorithm : str (default gpa)
41 should be one of:
43 * 'gpa': a numerical method
44 * 'gpa_der_free': a derivative free numerical method
45 * 'analytic' : an analytic method
47 Depending on the algorithm, there are algorithm specific keyword
48 arguments. For the gpa and gpa_der_free, the following
49 keyword arguments are available:
51 max_tries : int (default 501)
52 maximum number of iterations
54 tol : float
55 stop criterion, algorithm stops if Frobenius norm of gradient is
56 smaller then tol
58 For analytic, the supported arguments depend on the method, see above.
60 See the lower level functions for more details.
62 Returns
63 -------
64 The tuple :math:`(L,T)`
66 Notes
67 -----
68 What follows is a list of available methods. Depending on the method
69 additional argument are required and different algorithms
70 are available. The algorithm_kwargs are additional keyword arguments
71 passed to the selected algorithm (see the parameters section).
72 Unless stated otherwise, only the gpa and
73 gpa_der_free algorithm are available.
75 Below,
77 * :math:`L` is a :math:`p\times k` matrix;
78 * :math:`N` is :math:`k\times k` matrix with zeros on the diagonal and ones
79 elsewhere;
80 * :math:`M` is :math:`p\times p` matrix with zeros on the diagonal and ones
81 elsewhere;
82 * :math:`C` is a :math:`p\times p` matrix with elements equal to
83 :math:`1/p`;
84 * :math:`(X,Y)=\operatorname{Tr}(X^*Y)` is the Frobenius norm;
85 * :math:`\circ` is the element-wise product or Hadamard product.
87 oblimin : orthogonal or oblique rotation that minimizes
88 .. math::
89 \phi(L) = \frac{1}{4}(L\circ L,(I-\gamma C)(L\circ L)N).
91 For orthogonal rotations:
93 * :math:`\gamma=0` corresponds to quartimax,
94 * :math:`\gamma=\frac{1}{2}` corresponds to biquartimax,
95 * :math:`\gamma=1` corresponds to varimax,
96 * :math:`\gamma=\frac{1}{p}` corresponds to equamax.
98 For oblique rotations rotations:
100 * :math:`\gamma=0` corresponds to quartimin,
101 * :math:`\gamma=\frac{1}{2}` corresponds to biquartimin.
103 method_args:
105 gamma : float
106 oblimin family parameter
107 rotation_method : str
108 should be one of {orthogonal, oblique}
110 orthomax : orthogonal rotation that minimizes
112 .. math::
113 \phi(L) = -\frac{1}{4}(L\circ L,(I-\gamma C)(L\circ L)),
115 where :math:`0\leq\gamma\leq1`. The orthomax family is equivalent to
116 the oblimin family (when restricted to orthogonal rotations).
117 Furthermore,
119 * :math:`\gamma=0` corresponds to quartimax,
120 * :math:`\gamma=\frac{1}{2}` corresponds to biquartimax,
121 * :math:`\gamma=1` corresponds to varimax,
122 * :math:`\gamma=\frac{1}{p}` corresponds to equamax.
124 method_args:
126 gamma : float (between 0 and 1)
127 orthomax family parameter
129 CF : Crawford-Ferguson family for orthogonal and oblique rotation which
130 minimizes:
132 .. math::
134 \phi(L) =\frac{1-\kappa}{4} (L\circ L,(L\circ L)N)
135 -\frac{1}{4}(L\circ L,M(L\circ L)),
137 where :math:`0\leq\kappa\leq1`. For orthogonal rotations the oblimin
138 (and orthomax) family of rotations is equivalent to the
139 Crawford-Ferguson family.
140 To be more precise:
142 * :math:`\kappa=0` corresponds to quartimax,
143 * :math:`\kappa=\frac{1}{p}` corresponds to varimax,
144 * :math:`\kappa=\frac{k-1}{p+k-2}` corresponds to parsimax,
145 * :math:`\kappa=1` corresponds to factor parsimony.
147 method_args:
149 kappa : float (between 0 and 1)
150 Crawford-Ferguson family parameter
151 rotation_method : str
152 should be one of {orthogonal, oblique}
154 quartimax : orthogonal rotation method
155 minimizes the orthomax objective with :math:`\gamma=0`
157 biquartimax : orthogonal rotation method
158 minimizes the orthomax objective with :math:`\gamma=\frac{1}{2}`
160 varimax : orthogonal rotation method
161 minimizes the orthomax objective with :math:`\gamma=1`
163 equamax : orthogonal rotation method
164 minimizes the orthomax objective with :math:`\gamma=\frac{1}{p}`
166 parsimax : orthogonal rotation method
167 minimizes the Crawford-Ferguson family objective with
168 :math:`\kappa=\frac{k-1}{p+k-2}`
170 parsimony : orthogonal rotation method
171 minimizes the Crawford-Ferguson family objective with :math:`\kappa=1`
173 quartimin : oblique rotation method that minimizes
174 minimizes the oblimin objective with :math:`\gamma=0`
176 quartimin : oblique rotation method that minimizes
177 minimizes the oblimin objective with :math:`\gamma=\frac{1}{2}`
179 target : orthogonal or oblique rotation that rotates towards a target
181 matrix : math:`H` by minimizing the objective
183 .. math::
185 \phi(L) =\frac{1}{2}\|L-H\|^2.
187 method_args:
189 H : numpy matrix
190 target matrix
191 rotation_method : str
192 should be one of {orthogonal, oblique}
194 For orthogonal rotations the algorithm can be set to analytic in which
195 case the following keyword arguments are available:
197 full_rank : bool (default False)
198 if set to true full rank is assumed
200 partial_target : orthogonal (default) or oblique rotation that partially
201 rotates towards a target matrix :math:`H` by minimizing the objective:
203 .. math::
205 \phi(L) =\frac{1}{2}\|W\circ(L-H)\|^2.
207 method_args:
209 H : numpy matrix
210 target matrix
211 W : numpy matrix (default matrix with equal weight one for all entries)
212 matrix with weights, entries can either be one or zero
214 Examples
215 --------
216 >>> A = np.random.randn(8,2)
217 >>> L, T = rotate_factors(A,'varimax')
218 >>> np.allclose(L,A.dot(T))
219 >>> L, T = rotate_factors(A,'orthomax',0.5)
220 >>> np.allclose(L,A.dot(T))
221 >>> L, T = rotate_factors(A,'quartimin',0.5)
222 >>> np.allclose(L,A.dot(np.linalg.inv(T.T)))
223 """
224 if 'algorithm' in algorithm_kwargs:
225 algorithm = algorithm_kwargs['algorithm']
226 algorithm_kwargs.pop('algorithm')
227 else:
228 algorithm = 'gpa'
229 assert not ('rotation_method' in algorithm_kwargs), (
230 'rotation_method cannot be provided as keyword argument')
231 L = None
232 T = None
233 ff = None
234 vgQ = None
235 p, k = A.shape
236 # set ff or vgQ to appropriate objective function, compute solution using
237 # recursion or analytically compute solution
238 if method == 'orthomax':
239 assert len(method_args) == 1, ('Only %s family parameter should be '
240 'provided' % method)
241 rotation_method = 'orthogonal'
242 gamma = method_args[0]
243 if algorithm == 'gpa':
244 vgQ = lambda L=None, A=None, T=None: orthomax_objective(
245 L=L, A=A, T=T, gamma=gamma, return_gradient=True)
246 elif algorithm == 'gpa_der_free':
247 ff = lambda L=None, A=None, T=None: orthomax_objective(
248 L=L, A=A, T=T, gamma=gamma, return_gradient=False)
249 else:
250 raise ValueError('Algorithm %s is not possible for %s '
251 'rotation' % (algorithm, method))
252 elif method == 'oblimin':
253 assert len(method_args) == 2, ('Both %s family parameter and '
254 'rotation_method should be '
255 'provided' % method)
256 rotation_method = method_args[1]
257 assert rotation_method in ['orthogonal', 'oblique'], (
258 'rotation_method should be one of {orthogonal, oblique}')
259 gamma = method_args[0]
260 if algorithm == 'gpa':
261 vgQ = lambda L=None, A=None, T=None: oblimin_objective(
262 L=L, A=A, T=T, gamma=gamma, return_gradient=True)
263 elif algorithm == 'gpa_der_free':
264 ff = lambda L=None, A=None, T=None: oblimin_objective(
265 L=L, A=A, T=T, gamma=gamma, rotation_method=rotation_method,
266 return_gradient=False)
267 else:
268 raise ValueError('Algorithm %s is not possible for %s '
269 'rotation' % (algorithm, method))
270 elif method == 'CF':
271 assert len(method_args) == 2, ('Both %s family parameter and '
272 'rotation_method should be provided'
273 % method)
274 rotation_method = method_args[1]
275 assert rotation_method in ['orthogonal', 'oblique'], (
276 'rotation_method should be one of {orthogonal, oblique}')
277 kappa = method_args[0]
278 if algorithm == 'gpa':
279 vgQ = lambda L=None, A=None, T=None: CF_objective(
280 L=L, A=A, T=T, kappa=kappa, rotation_method=rotation_method,
281 return_gradient=True)
282 elif algorithm == 'gpa_der_free':
283 ff = lambda L=None, A=None, T=None: CF_objective(
284 L=L, A=A, T=T, kappa=kappa, rotation_method=rotation_method,
285 return_gradient=False)
286 else:
287 raise ValueError('Algorithm %s is not possible for %s '
288 'rotation' % (algorithm, method))
289 elif method == 'quartimax':
290 return rotate_factors(A, 'orthomax', 0, **algorithm_kwargs)
291 elif method == 'biquartimax':
292 return rotate_factors(A, 'orthomax', 0.5, **algorithm_kwargs)
293 elif method == 'varimax':
294 return rotate_factors(A, 'orthomax', 1, **algorithm_kwargs)
295 elif method == 'equamax':
296 return rotate_factors(A, 'orthomax', 1/p, **algorithm_kwargs)
297 elif method == 'parsimax':
298 return rotate_factors(A, 'CF', (k-1)/(p+k-2),
299 'orthogonal', **algorithm_kwargs)
300 elif method == 'parsimony':
301 return rotate_factors(A, 'CF', 1, 'orthogonal', **algorithm_kwargs)
302 elif method == 'quartimin':
303 return rotate_factors(A, 'oblimin', 0, 'oblique', **algorithm_kwargs)
304 elif method == 'biquartimin':
305 return rotate_factors(A, 'oblimin', 0.5, 'oblique', **algorithm_kwargs)
306 elif method == 'target':
307 assert len(method_args) == 2, (
308 'only the rotation target and orthogonal/oblique should be provide'
309 ' for %s rotation' % method)
310 H = method_args[0]
311 rotation_method = method_args[1]
312 assert rotation_method in ['orthogonal', 'oblique'], (
313 'rotation_method should be one of {orthogonal, oblique}')
314 if algorithm == 'gpa':
315 vgQ = lambda L=None, A=None, T=None: vgQ_target(
316 H, L=L, A=A, T=T, rotation_method=rotation_method)
317 elif algorithm == 'gpa_der_free':
318 ff = lambda L=None, A=None, T=None: ff_target(
319 H, L=L, A=A, T=T, rotation_method=rotation_method)
320 elif algorithm == 'analytic':
321 assert rotation_method == 'orthogonal', (
322 'For analytic %s rotation only orthogonal rotation is '
323 'supported')
324 T = target_rotation(A, H, **algorithm_kwargs)
325 else:
326 raise ValueError('Algorithm %s is not possible for %s rotation'
327 % (algorithm, method))
328 elif method == 'partial_target':
329 assert len(method_args) == 2, ('2 additional arguments are expected '
330 'for %s rotation' % method)
331 H = method_args[0]
332 W = method_args[1]
333 rotation_method = 'orthogonal'
334 if algorithm == 'gpa':
335 vgQ = lambda L=None, A=None, T=None: vgQ_partial_target(
336 H, W=W, L=L, A=A, T=T)
337 elif algorithm == 'gpa_der_free':
338 ff = lambda L=None, A=None, T=None: ff_partial_target(
339 H, W=W, L=L, A=A, T=T)
340 else:
341 raise ValueError('Algorithm %s is not possible for %s '
342 'rotation' % (algorithm, method))
343 else:
344 raise ValueError('Invalid method')
345 # compute L and T if not already done
346 if T is None:
347 L, phi, T, table = GPA(A, vgQ=vgQ, ff=ff,
348 rotation_method=rotation_method,
349 **algorithm_kwargs)
350 if L is None:
351 assert T is not None, 'Cannot compute L without T'
352 L = rotateA(A, T, rotation_method=rotation_method)
353 return L, T