Skip to content

Commit d039266

Browse files
authored
Merge pull request #43 from EducationalTestingService/some-minor-updates
Some minor updates
2 parents 13fea37 + 838f339 commit d039266

File tree

9 files changed

+142
-47
lines changed

9 files changed

+142
-47
lines changed

.circleci/config.yml

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ jobs:
1010
working_directory: ~/repo
1111
steps:
1212
- checkout
13-
- restore_cache:
14-
keys:
15-
- deps
1613

1714
- run: rm -rf ~/repo/artifacts
1815
- run: mkdir ~/repo/artifacts
@@ -22,29 +19,18 @@ jobs:
2219
wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh
2320
chmod +x miniconda.sh
2421
./miniconda.sh -b -f
25-
~/miniconda3/bin/conda install --file conda_requirements.txt --yes
26-
~/miniconda3/bin/pip install nose-cov python-coveralls
27-
28-
- save_cache:
29-
paths:
30-
- "~/miniconda3/pkgs"
31-
key: deps
32-
33-
# install factor analyzer
34-
- run:
35-
name: Install factor analyzer
36-
command: |
37-
~/miniconda3/bin/pip install -e .
22+
export PATH=~/miniconda3/bin:$PATH
23+
conda install -c anaconda --yes setuptools
24+
conda install python=3.7 --file requirements.txt --yes
25+
pip install nose nose-cov python-coveralls
26+
pip install -e .
3827
3928
# run all of the tests
4029
- run:
41-
name: Run tests
42-
command: ~/miniconda3/bin/nosetests -v tests --with-coverage --cover-package=factor_analyzer --cov-config .coveragerc
43-
44-
# change to factor analyzer directory and run coveralls
45-
- run:
46-
name: Run coveralls
47-
command: cd ~/repo/factor_analyzer && ~/miniconda3/bin/coveralls
30+
name: Run tests and coveralls
31+
command: |
32+
~/miniconda3/bin/nosetests -v tests --with-coverage --cover-package=factor_analyzer --cov-config .coveragerc
33+
cd ~/repo/factor_analyzer && ~/miniconda3/bin/coveralls
4834
4935
- store_artifacts:
5036
path: ~/repo/artifacts

conda_requirements.txt

Lines changed: 0 additions & 6 deletions
This file was deleted.

factor_analyzer/rotator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,9 @@ def _oblimin_obj(self, loadings):
191191
The value of the criterion for the objective.
192192
"""
193193
X = np.dot(loadings**2, np.eye(loadings.shape[1]) != 1)
194-
if (0 != self.gamma):
194+
if (self.gamma != 0):
195195
p = loadings.shape[0]
196-
X = np.diag(1, p) - np.dot(np.zeros((p, p)), X)
196+
X = np.diag(np.full(1, p)) - np.dot(np.zeros((p, p)), X)
197197
gradient = loadings * X
198198
criterion = np.sum(loadings**2 * X) / 4
199199
return {'grad': gradient, 'criterion': criterion}

factor_analyzer/test_utils.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,8 @@ def check_rotation(test_name,
340340
method,
341341
rotation,
342342
rel_tol=0,
343-
abs_tol=0.1):
343+
abs_tol=0.1,
344+
**kwargs):
344345
"""
345346
Check the rotation results.
346347
@@ -373,8 +374,8 @@ def check_rotation(test_name,
373374
r_loading = r_input['loading']
374375
r_loading = normalize(r_loading, absolute=False)
375376

376-
rotator = Rotator(method=rotation)
377-
rotated_loading = rotator.fit_transform(r_loading, rotation)
377+
rotator = Rotator(method=rotation, **kwargs)
378+
rotated_loading = rotator.fit_transform(r_loading)
378379

379380
r_output = collect_r_output(test_name, factors, method, rotation,
380381
output_types=['loading'])

factor_analyzer/utils.py

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,48 @@
77
:organization: ETS
88
"""
99
import numpy as np
10+
import warnings
11+
from scipy.linalg import cholesky
1012

1113

12-
def cov(x):
14+
def inv_chol(x, logdet=False):
15+
"""
16+
Calculate inverse using cholesky.
17+
Optionally, calculate the log determinant
18+
of the cholesky.
19+
20+
Parameters
21+
----------
22+
x : array-like
23+
The matrix to invert.
24+
logdet : bool, optional
25+
Whether to calculate the
26+
log determinant, instead of
27+
the inverse.
28+
Defaults to False.
29+
30+
Returns
31+
-------
32+
chol_inv : array-like
33+
The inverted matrix
34+
chol_logdet : array-like or None
35+
The log determinant, if `logdet=True`;
36+
otherwise, None.
37+
"""
38+
chol = cholesky(x, lower=True)
39+
40+
chol_inv = np.linalg.inv(chol)
41+
chol_inv = np.dot(chol_inv.T, chol_inv)
42+
chol_logdet = None
43+
44+
if logdet:
45+
chol_diag = np.diag(chol)
46+
chol_logdet = np.sum(np.log(chol_diag * chol_diag))
47+
48+
return chol_inv, chol_logdet
49+
50+
51+
def cov(x, ddof=0):
1352
"""
1453
Calculate the covariance matrix.
1554
@@ -19,13 +58,17 @@ def cov(x):
1958
A 1-D or 2-D array containing multiple variables
2059
and observations. Each column of x represents a variable,
2160
and each row a single observation of all those variables.
61+
ddof : int, optional
62+
Means Delta Degrees of Freedom. The divisor used in calculations
63+
is N - ddof, where N represents the number of elements.
64+
Defaults to 0.
2265
2366
Returns
2467
-------
2568
r : numpy array
2669
The covariance matrix of the variables.
2770
"""
28-
r = np.cov(x, rowvar=False, ddof=0)
71+
r = np.cov(x, rowvar=False, ddof=ddof)
2972
return r
3073

3174

@@ -185,20 +228,30 @@ def partial_correlations(x):
185228
variables.
186229
"""
187230
numrows, numcols = x.shape
188-
x_cov = cov(x)
231+
x_cov = cov(x, ddof=1)
189232
# create empty array for when we cannot compute the
190233
# matrix inversion
191234
empty_array = np.empty((numcols, numcols))
192235
empty_array[:] = np.nan
193236
if numcols > numrows:
194237
icvx = empty_array
195238
else:
196-
# we also return nans if there is singularity in the data
197-
# (e.g. all human scores are the same)
239+
# if the determinant is less than the lowest representable
240+
# 32 bit integer, then we use the pseudo-inverse;
241+
# otherwise, use the inverse; if a linear algebra error
242+
# occurs, then we just set the matrix to empty
198243
try:
244+
assert np.linalg.det(x_cov) > np.finfo(np.float32).eps
199245
icvx = np.linalg.inv(x_cov)
246+
except AssertionError:
247+
icvx = np.linalg.pinv(x_cov)
248+
warnings.warn('The inverse of the variance-covariance matrix '
249+
'was calculated using the Moore-Penrose generalized '
250+
'matrix inversion, due to its determinant being at '
251+
'or very close to zero.')
200252
except np.linalg.LinAlgError:
201253
icvx = empty_array
254+
202255
pcor = -1 * covariance_to_correlation(icvx)
203256
np.fill_diagonal(pcor, 1.0)
204257
return pcor
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"","ULS1","ULS2"
2+
"q01",0.556214446794607,0.12038813487999
3+
"q02",-0.281822776327459,0.387645428358021
4+
"q03",-0.60542332172341,0.253798600475957
5+
"q04",0.606804469688735,0.0934257765516741
6+
"q05",0.523025792652978,0.0534918898887911
7+
"q06",0.529725212374218,0.0417559566308573
8+
"q07",0.660720452123604,-0.00325533411175857
9+
"q08",0.531132789779371,0.397312113320854
10+
"q09",-0.266230283279582,0.469334924366135
11+
"q10",0.404307105368452,0.00582165019885599
12+
"q11",0.633075237129907,0.262235950578387
13+
"q12",0.643286181854888,-0.0807440059731107
14+
"q13",0.647738977613556,0.0396589226602089
15+
"q14",0.628659441568136,-0.0173462348178553
16+
"q15",0.561403964204197,0.000270516482592699
17+
"q16",0.654041340932595,-0.00697912977316184
18+
"q17",0.628165284655674,0.339069156172524
19+
"q18",0.678697773243279,-0.0132586489358537
20+
"q19",-0.398762512423482,0.282518905874988
21+
"q20",0.40464340977971,-0.152208016747372
22+
"q21",0.630863031076314,-0.0722141114458285
23+
"q22",-0.277948597756047,0.2903121776673
24+
"q23",-0.130382489433764,0.192236993346541
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
,ULS1,ULS2
2+
q01,0.5562144467946071,0.12038813487998999
3+
q02,-0.281822776327459,0.387645428358021
4+
q03,-0.6054233217234101,0.253798600475957
5+
q04,0.606804469688735,0.0934257765516741
6+
q05,0.523025792652978,0.0534918898887911
7+
q06,0.529725212374218,0.0417559566308573
8+
q07,0.660720452123604,-0.0032553341117585698
9+
q08,0.5311327897793711,0.39731211332085403
10+
q09,-0.266230283279582,0.469334924366135
11+
q10,0.404307105368452,0.00582165019885599
12+
q11,0.633075237129907,0.262235950578387
13+
q12,0.643286181854888,-0.0807440059731107
14+
q13,0.647738977613556,0.0396589226602089
15+
q14,0.628659441568136,-0.0173462348178553
16+
q15,0.561403964204197,0.00027051648259269903
17+
q16,0.654041340932595,-0.00697912977316184
18+
q17,0.628165284655674,0.33906915617252403
19+
q18,0.6786977732432788,-0.0132586489358537
20+
q19,-0.3987625124234821,0.282518905874988
21+
q20,0.40464340977971003,-0.15220801674737197
22+
q21,0.6308630310763139,-0.0722141114458285
23+
q22,-0.27794859775604697,0.2903121776673
24+
q23,-0.13038248943376402,0.192236993346541

tests/test_expected_rotator.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,15 @@ def test_07_equamax_minres_2_factors():
175175

176176
check = check_rotation(test_name, factors, method, rotation)
177177
assert check > THRESHOLD
178+
179+
180+
def test_07_oblimin_minres_2_factors_gamma():
181+
182+
test_name = 'test07_gamma'
183+
factors = 2
184+
method = 'uls'
185+
rotation = 'oblimin'
186+
gamma = 0.5
187+
188+
check = check_rotation(test_name, factors, method, rotation, gamma=gamma)
189+
assert check > THRESHOLD

tests/test_utils.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ def test_partial_correlations_num_columns_greater():
233233
assert_almost_equal(result, expected.values)
234234

235235

236-
def test_partial_correlations_catch_linalgerror():
236+
def test_partial_correlations_with_zero_det():
237237

238238
# Covariance matrix that will be singular
239239
data = pd.DataFrame([[10, 10, 10, 10],
@@ -242,13 +242,14 @@ def test_partial_correlations_catch_linalgerror():
242242
[20, 20, 20, 20],
243243
[11, 11, 11, 11]])
244244

245-
empty_array = np.empty((4, 4))
246-
empty_array[:] = np.nan
247-
np.fill_diagonal(empty_array, 1.0)
248-
249-
expected = pd.DataFrame(empty_array,
250-
columns=[0, 1, 2, 3],
251-
index=[0, 1, 2, 3])
245+
expected = [[1.0,
246+
-0.9999999999999998,
247+
-0.9999999999999998,
248+
-0.9999999999999998],
249+
[-1.0000000000000004, 1.0, -1.0, -1.0],
250+
[-1.0000000000000004, -1.0, 1.0, -1.0],
251+
[-1.0000000000000004, -1.0, -1.0, 1.0]]
252+
expected = pd.DataFrame(expected)
252253

253254
result = partial_correlations(data)
254255
assert_almost_equal(result, expected.values)

0 commit comments

Comments
 (0)