# Source code for statsmodels.multivariate.factor_rotation._analytic_rotation

# -*- coding: utf-8 -*-
"""
This file contains analytic implementations of rotation methods.
"""

import numpy as np
import scipy as sp

[docs]def target_rotation(A, H, full_rank=False):
r"""
Analytically performs orthogonal rotations towards a target matrix,
i.e., we minimize:

.. math::
\phi(L) =\frac{1}{2}\|AT-H\|^2.

where :math:T is an orthogonal matrix. This problem is also known as
an orthogonal Procrustes problem.

Under the assumption that :math:A^*H has full rank, the analytical
solution :math:T is given by:

.. math::
T = (A^*HH^*A)^{-\frac{1}{2}}A^*H,

see Green (1952). In other cases the solution is given by :math:T = UV,
where :math:U and :math:V result from the singular value decomposition
of :math:A^*H:

.. math::
A^*H = U\Sigma V,

see Schonemann (1966).

Parameters
----------
A : numpy matrix (default None)
non rotated factors
H : numpy matrix
target matrix
full_rank : bool (default FAlse)
if set to true full rank is assumed

Returns
-------
The matrix :math:T.

References
----------
 Green (1952, Psychometrika) - The orthogonal approximation of an
oblique structure in factor analysis

 Schonemann (1966) - A generalized solution of the orthogonal
procrustes problem

 Gower, Dijksterhuis (2004) - Procrustes problems
"""
ATH = A.T.dot(H)
if full_rank or np.linalg.matrix_rank(ATH) == A.shape:
T = sp.linalg.fractional_matrix_power(ATH.dot(ATH.T), -1/2).dot(ATH)
else:
U, D, V = np.linalg.svd(ATH, full_matrices=False)
T = U.dot(V)
return T

[docs]def procrustes(A, H):
r"""
Analytically solves the following Procrustes problem:

.. math::
\phi(L) =\frac{1}{2}\|AT-H\|^2.

(With no further conditions on :math:H)

Under the assumption that :math:A^*H has full rank, the analytical
solution :math:T is given by:

.. math::
T = (A^*HH^*A)^{-\frac{1}{2}}A^*H,

see Navarra, Simoncini (2010).

Parameters
----------
A : numpy matrix
non rotated factors
H : numpy matrix
target matrix
full_rank : bool (default False)
if set to true full rank is assumed

Returns
-------
The matrix :math:T.

References
----------
 Navarra, Simoncini (2010) - A guide to empirical orthogonal functions
for climate data analysis
"""
return np.linalg.inv(A.T.dot(A)).dot(A.T).dot(H)

[docs]def promax(A, k=2):
r"""
Performs promax rotation of the matrix :math:A.

This method was not very clear to me from the literature, this
implementation is as I understand it should work.

Promax rotation is performed in the following steps:

* Determine varimax rotated patterns :math:V.

* Construct a rotation target matrix :math:|V_{ij}|^k/V_{ij}

* Perform procrustes rotation towards the target to obtain T

* Determine the patterns

First, varimax rotation a target matrix :math:H is determined with
orthogonal varimax rotation.
Then, oblique target rotation is performed towards the target.

Parameters
----------
A : numpy matrix
non rotated factors
k : float
parameter, should be positive

References
----------
 Browne (2001) - An overview of analytic rotation in exploratory
factor analysis

 Navarra, Simoncini (2010) - A guide to empirical orthogonal functions
for climate data analysis
"""
assert k > 0
# define rotation target using varimax rotation:
from ._wrappers import rotate_factors
V, T = rotate_factors(A, 'varimax')
H = np.abs(V)**k/V
# solve procrustes problem
S = procrustes(A, H)  # np.linalg.inv(A.T.dot(A)).dot(A.T).dot(H);
# normalize
d = np.sqrt(np.diag(np.linalg.inv(S.T.dot(S))))
D = np.diag(d)
T = np.linalg.inv(S.dot(D)).T
return A.dot(T), T