import unittest
import numpy as np
from statsmodels.multivariate.factor_rotation._wrappers import rotate_factors
from statsmodels.multivariate.factor_rotation._gpa_rotation import (
ff_partial_target, vgQ_partial_target, ff_target, vgQ_target, CF_objective,
orthomax_objective, oblimin_objective, GPA)
from statsmodels.multivariate.factor_rotation._analytic_rotation import (
target_rotation)
class TestAnalyticRotation(unittest.TestCase):
@staticmethod
def str2matrix(A):
A = A.lstrip().rstrip().split('\n')
A = np.array([row.split() for row in A]).astype(np.float)
return A
def test_target_rotation(self):
"""
Rotation towards target matrix example
http://www.stat.ucla.edu/research/gpa
"""
A = self.str2matrix("""
.830 -.396
.818 -.469
.777 -.470
.798 -.401
.786 .500
.672 .458
.594 .444
.647 .333
""")
H = self.str2matrix("""
.8 -.3
.8 -.4
.7 -.4
.9 -.4
.8 .5
.6 .4
.5 .4
.6 .3
""")
T = target_rotation(A, H)
L = A.dot(T)
L_required = self.str2matrix("""
0.84168 -0.37053
0.83191 -0.44386
0.79096 -0.44611
0.80985 -0.37650
0.77040 0.52371
0.65774 0.47826
0.58020 0.46189
0.63656 0.35255
""")
self.assertTrue(np.allclose(L, L_required, atol=1e-05))
T = target_rotation(A, H, full_rank=True)
L = A.dot(T)
self.assertTrue(np.allclose(L, L_required, atol=1e-05))
def test_orthogonal_target(self):
"""
Rotation towards target matrix example
http://www.stat.ucla.edu/research/gpa
"""
A = self.str2matrix("""
.830 -.396
.818 -.469
.777 -.470
.798 -.401
.786 .500
.672 .458
.594 .444
.647 .333
""")
H = self.str2matrix("""
.8 -.3
.8 -.4
.7 -.4
.9 -.4
.8 .5
.6 .4
.5 .4
.6 .3
""")
vgQ = lambda L=None, A=None, T=None: vgQ_target(H, L=L, A=A, T=T)
L, phi, T, table = GPA(A, vgQ=vgQ, rotation_method='orthogonal')
T_analytic = target_rotation(A, H)
self.assertTrue(np.allclose(T, T_analytic, atol=1e-05))
class TestGPARotation(unittest.TestCase):
@staticmethod
def str2matrix(A):
A = A.lstrip().rstrip().split('\n')
A = np.array([row.split() for row in A]).astype(np.float)
return A
@classmethod
def get_A(cls):
return cls.str2matrix("""
.830 -.396
.818 -.469
.777 -.470
.798 -.401
.786 .500
.672 .458
.594 .444
.647 .333
""")
@classmethod
def get_quartimin_example(cls):
A = cls.get_A()
table_required = cls.str2matrix("""
0.00000 0.42806 -0.46393 1.00000
1.00000 0.41311 -0.57313 0.25000
2.00000 0.38238 -0.36652 0.50000
3.00000 0.31850 -0.21011 0.50000
4.00000 0.20937 -0.13838 0.50000
5.00000 0.12379 -0.35583 0.25000
6.00000 0.04289 -0.53244 0.50000
7.00000 0.01098 -0.86649 0.50000
8.00000 0.00566 -1.65798 0.50000
9.00000 0.00558 -2.13212 0.25000
10.00000 0.00557 -2.49020 0.25000
11.00000 0.00557 -2.84585 0.25000
12.00000 0.00557 -3.20320 0.25000
13.00000 0.00557 -3.56143 0.25000
14.00000 0.00557 -3.92005 0.25000
15.00000 0.00557 -4.27885 0.25000
16.00000 0.00557 -4.63772 0.25000
17.00000 0.00557 -4.99663 0.25000
18.00000 0.00557 -5.35555 0.25000
""")
L_required = cls.str2matrix("""
0.891822 0.056015
0.953680 -0.023246
0.929150 -0.046503
0.876683 0.033658
0.013701 0.925000
-0.017265 0.821253
-0.052445 0.764953
0.085890 0.683115
""")
return A, table_required, L_required
@classmethod
def get_biquartimin_example(cls):
A = cls.get_A()
table_required = cls.str2matrix("""
0.00000 0.21632 -0.54955 1.00000
1.00000 0.19519 -0.46174 0.50000
2.00000 0.09479 -0.16365 1.00000
3.00000 -0.06302 -0.32096 0.50000
4.00000 -0.21304 -0.46562 1.00000
5.00000 -0.33199 -0.33287 1.00000
6.00000 -0.35108 -0.63990 0.12500
7.00000 -0.35543 -1.20916 0.12500
8.00000 -0.35568 -2.61213 0.12500
9.00000 -0.35568 -2.97910 0.06250
10.00000 -0.35568 -3.32645 0.06250
11.00000 -0.35568 -3.66021 0.06250
12.00000 -0.35568 -3.98564 0.06250
13.00000 -0.35568 -4.30635 0.06250
14.00000 -0.35568 -4.62451 0.06250
15.00000 -0.35568 -4.94133 0.06250
16.00000 -0.35568 -5.25745 0.06250
""")
L_required = cls.str2matrix("""
1.01753 -0.13657
1.11338 -0.24643
1.09200 -0.26890
1.00676 -0.16010
-0.26534 1.11371
-0.26972 0.99553
-0.29341 0.93561
-0.10806 0.80513
""")
return A, table_required, L_required
@classmethod
def get_biquartimin_example_derivative_free(cls):
A = cls.get_A()
table_required = cls.str2matrix("""
0.00000 0.21632 -0.54955 1.00000
1.00000 0.19519 -0.46174 0.50000
2.00000 0.09479 -0.16365 1.00000
3.00000 -0.06302 -0.32096 0.50000
4.00000 -0.21304 -0.46562 1.00000
5.00000 -0.33199 -0.33287 1.00000
6.00000 -0.35108 -0.63990 0.12500
7.00000 -0.35543 -1.20916 0.12500
8.00000 -0.35568 -2.61213 0.12500
9.00000 -0.35568 -2.97910 0.06250
10.00000 -0.35568 -3.32645 0.06250
11.00000 -0.35568 -3.66021 0.06250
12.00000 -0.35568 -3.98564 0.06250
13.00000 -0.35568 -4.30634 0.06250
14.00000 -0.35568 -4.62451 0.06250
15.00000 -0.35568 -4.94133 0.06250
16.00000 -0.35568 -6.32435 0.12500
""")
L_required = cls.str2matrix("""
1.01753 -0.13657
1.11338 -0.24643
1.09200 -0.26890
1.00676 -0.16010
-0.26534 1.11371
-0.26972 0.99553
-0.29342 0.93561
-0.10806 0.80513
""")
return A, table_required, L_required
@classmethod
def get_quartimax_example_derivative_free(cls):
A = cls.get_A()
table_required = cls.str2matrix("""
0.00000 -0.72073 -0.65498 1.00000
1.00000 -0.88561 -0.34614 2.00000
2.00000 -1.01992 -1.07152 1.00000
3.00000 -1.02237 -1.51373 0.50000
4.00000 -1.02269 -1.96205 0.50000
5.00000 -1.02273 -2.41116 0.50000
6.00000 -1.02273 -2.86037 0.50000
7.00000 -1.02273 -3.30959 0.50000
8.00000 -1.02273 -3.75881 0.50000
9.00000 -1.02273 -4.20804 0.50000
10.00000 -1.02273 -4.65726 0.50000
11.00000 -1.02273 -5.10648 0.50000
""")
L_required = cls.str2matrix("""
0.89876 0.19482
0.93394 0.12974
0.90213 0.10386
0.87651 0.17128
0.31558 0.87647
0.25113 0.77349
0.19801 0.71468
0.30786 0.65933
""")
return A, table_required, L_required
def test_orthomax(self):
"""
Quartimax example
http://www.stat.ucla.edu/research/gpa
"""
A = self.get_A()
vgQ = lambda L=None, A=None, T=None: orthomax_objective(
L=L, A=A, T=T, gamma=0, return_gradient=True)
L, phi, T, table = GPA(A, vgQ=vgQ, rotation_method='orthogonal')
table_required = self.str2matrix("""
0.00000 -0.72073 -0.65498 1.00000
1.00000 -0.88561 -0.34614 2.00000
2.00000 -1.01992 -1.07152 1.00000
3.00000 -1.02237 -1.51373 0.50000
4.00000 -1.02269 -1.96205 0.50000
5.00000 -1.02273 -2.41116 0.50000
6.00000 -1.02273 -2.86037 0.50000
7.00000 -1.02273 -3.30959 0.50000
8.00000 -1.02273 -3.75881 0.50000
9.00000 -1.02273 -4.20804 0.50000
10.00000 -1.02273 -4.65726 0.50000
11.00000 -1.02273 -5.10648 0.50000
""")
L_required = self.str2matrix("""
0.89876 0.19482
0.93394 0.12974
0.90213 0.10386
0.87651 0.17128
0.31558 0.87647
0.25113 0.77349
0.19801 0.71468
0.30786 0.65933
""")
self.assertTrue(np.allclose(table, table_required, atol=1e-05))
self.assertTrue(np.allclose(L, L_required, atol=1e-05))
# oblimin criterion gives same result
vgQ = lambda L=None, A=None, T=None: oblimin_objective(
L=L, A=A, T=T, gamma=0, rotation_method='orthogonal',
return_gradient=True)
L_oblimin, phi2, T2, table2 = GPA(A, vgQ=vgQ,
rotation_method='orthogonal')
self.assertTrue(np.allclose(L, L_oblimin, atol=1e-05))
# derivative free quartimax
out = self.get_quartimax_example_derivative_free()
A, table_required, L_required = out
ff = lambda L=None, A=None, T=None: orthomax_objective(
L=L, A=A, T=T, gamma=0, return_gradient=False)
L, phi, T, table = GPA(A, ff=ff, rotation_method='orthogonal')
self.assertTrue(np.allclose(table, table_required, atol=1e-05))
self.assertTrue(np.allclose(L, L_required, atol=1e-05))
def test_equivalence_orthomax_oblimin(self):
"""
These criteria should be equivalent when restricted to orthogonal
rotation.
See Hartman 1976 page 299.
"""
A = self.get_A()
gamma = 0 # quartimax
vgQ = lambda L=None, A=None, T=None: orthomax_objective(
L=L, A=A, T=T, gamma=gamma, return_gradient=True)
L_orthomax, phi, T, table = GPA(
A, vgQ=vgQ, rotation_method='orthogonal')
vgQ = lambda L=None, A=None, T=None: oblimin_objective(
L=L, A=A, T=T, gamma=gamma, rotation_method='orthogonal',
return_gradient=True)
L_oblimin, phi2, T2, table2 = GPA(A, vgQ=vgQ,
rotation_method='orthogonal')
self.assertTrue(np.allclose(L_orthomax, L_oblimin, atol=1e-05))
gamma = 1 # varimax
vgQ = lambda L=None, A=None, T=None: orthomax_objective(
L=L, A=A, T=T, gamma=gamma, return_gradient=True)
L_orthomax, phi, T, table = GPA(
A, vgQ=vgQ, rotation_method='orthogonal')
vgQ = lambda L=None, A=None, T=None: oblimin_objective(
L=L, A=A, T=T, gamma=gamma, rotation_method='orthogonal',
return_gradient=True)
L_oblimin, phi2, T2, table2 = GPA(
A, vgQ=vgQ, rotation_method='orthogonal')
self.assertTrue(np.allclose(L_orthomax, L_oblimin, atol=1e-05))
def test_orthogonal_target(self):
"""
Rotation towards target matrix example
http://www.stat.ucla.edu/research/gpa
"""
A = self.get_A()
H = self.str2matrix("""
.8 -.3
.8 -.4
.7 -.4
.9 -.4
.8 .5
.6 .4
.5 .4
.6 .3
""")
vgQ = lambda L=None, A=None, T=None: vgQ_target(H, L=L, A=A, T=T)
L, phi, T, table = GPA(A, vgQ=vgQ, rotation_method='orthogonal')
table_required = self.str2matrix("""
Loading ...