Repository navigation
[MRG] varimax rotation for factor analysis - #11064
Conversation
|
This pull request introduces 1 alert when merging 99991b7 into 593bf65 - view on lgtm.com new alerts:
Comment posted by lgtm.com |
|
Well, it's green ... |
jnothman
left a comment
There was a problem hiding this comment.
Just looking at the basics, thanks
|
|
||
| rotation : None | 'varimax' | 'orthomax' | ||
| If not None, apply the indicated rotation. Currently, varimax and | ||
| orthomax are implemented. |
There was a problem hiding this comment.
Should there be a reference or some discussion in the user guide? At least there should be a link to the example from the user guide
| # test rotation | ||
| n_comps = 2 | ||
| for method in ("varimax", 'quartimax'): | ||
| fa_var = FactorAnalysis(n_components=n_comps, rotation=method) |
There was a problem hiding this comment.
Please assert that the solution varies with the parameter
|
CIs on 9f582f0 can all be canceled, I pushed quite preemptively. |
|
@jnothman better? |
| fa_var = FactorAnalysis(n_components=n_comps, rotation=method) | ||
| results.append(fa_var.fit_transform(X)) | ||
| for a, b in ((0, 1), (0, 2), (1, 2)): | ||
| assert_raises(AssertionError, assert_array_almost_equal, |
There was a problem hiding this comment.
I think assert not np.allclose would be clearer
There was a problem hiding this comment.
Hm ... only here, or also in 112? 84 also uses assert_array_almost_equal.
There was a problem hiding this comment.
You can use assert_allclose in those cases, but here you are raising an error in the case that the assertion fails, which is especially difficult to read.
|
@jnothman now? :) |
|
Anything else? It seems ready to me. |
|
@jnothman can I bother you about this one again? :) |
|
I don't feel confident to review the technical basis without being able to
sit down with references.
in that vein, could you please provide references or a technical
description of what the rotation optimises in the user guide
|
| fa_var = FactorAnalysis(n_components=n_comps, rotation=method) | ||
| results.append(fa_var.fit_transform(X)) | ||
| for a, b in ((0, 1), (0, 2), (1, 2)): | ||
| assert_raises(AssertionError, assert_array_almost_equal, |
There was a problem hiding this comment.
You can use assert_allclose in those cases, but here you are raising an error in the case that the assertion fails, which is especially difficult to read.
| :align: center | ||
| :scale: 75% | ||
|
|
||
| Factor Analysis is often followed by a rotation of the factors, usually to improve |
There was a problem hiding this comment.
Please note the parameter name to control this
|
To be honest, I didn't sit down with the original paper for this one, I think I looked at a few implementations online and just made sure it matched the R output ... E.g., |
|
Thanks. This won't make it into 0.20 which we are trying to release. Please
ping in a few weeks.
|
|
Ok, sure. Thanks. |
|
Is now the time? :) |
jnothman
left a comment
There was a problem hiding this comment.
Please provide references somewhere in the documentation (user guide and/or docstring)
|
|
||
| for _ in range(itermax): | ||
| comp_rot = np.dot(components, rotation_matrix) | ||
| tmp = np.diag((comp_rot ** 2).sum(axis=0)) / nrow * gamma |
There was a problem hiding this comment.
why do all this computation unnecessarily in the quartimax case?
Can't we just use if here?
| def _ortho_rotation(self, components, method='varimax', eps=1e-6, | ||
| itermax=100): | ||
| """Return rotated components.""" | ||
| if (method == 'varimax'): |
There was a problem hiding this comment.
parentheses should be removed here and below.
Can just use gamma = method == 'varimax', even.
| ConvergenceWarning) | ||
|
|
||
| self.components_ = W | ||
| if self.rotation is not None: |
There was a problem hiding this comment.
might as well do this check inside the method
There was a problem hiding this comment.
Actually, replace the previous line to self.components_ = self._rotate(W). I think that would be much clearer to the reader.
| "Rotate the factor analysis solution." | ||
| implemented = {"varimax", "quartimax"} | ||
| if method in implemented: | ||
| self.components_ = self._ortho_rotation( |
There was a problem hiding this comment.
It seems strange to put this in a separate function. Instead:
if method is None:
return
if method not in implemented:
raise ...
do the rotation
self.components_ = ...There was a problem hiding this comment.
To make it simpler to test, you could make this a module-level private function or a staticmethod, taking components_ in and returning it optionally modified.
There was a problem hiding this comment.
Ok sure. I'd for now keep it as a separate function to make it easier to refactor if somebody decides to add oblique or other rotations ..?
| np.dot(components.T, comp_rot ** 3 - np.dot(comp_rot, tmp))) | ||
| rotation_matrix = np.dot(u, v) | ||
| var_new = np.sum(s) | ||
| if var != 0 and (var_new < var * (1 + eps)): |
There was a problem hiding this comment.
parentheses around < expression are redundant
| np.eye(X.shape[1]), 12) | ||
|
|
||
| # test rotation | ||
| n_comps = 2 |
| self.components_ = self._ortho_rotation( | ||
| self.components_.T, method=method, eps=tol)[:n_components] | ||
| else: | ||
| raise NotImplementedError("'method' must be in %s, not %s" |
There was a problem hiding this comment.
Usually we would use a ValueError. Subtle, yes, but NotImplementedError is usually for a function call with argument types where the operation is not yet supported, for instance.
| for method in (None, "varimax", 'quartimax'): | ||
| fa_var = FactorAnalysis(n_components=n_comps, rotation=method) | ||
| results.append(fa_var.fit_transform(X)) | ||
| for a, b in ((0, 1), (0, 2), (1, 2)): |
There was a problem hiding this comment.
make the outer collection a list to improve readability [(0, 1), (0, 2) ...]
There was a problem hiding this comment.
but it would be much more readable to use itertools.combinations([None, 'varimax', 'quartimax'], 2) and have stored the results by name
jnothman
left a comment
There was a problem hiding this comment.
Please fix up these changes and hopefully we can get another reviewer in towards merging this.
|
|
||
| print(__doc__) | ||
|
|
||
| # ############################################################################# |
There was a problem hiding this comment.
| # ############################################################################# | |
| ############################################################################## |
| ax.set_title("Iris feature correlation matrix") | ||
| plt.tight_layout() | ||
|
|
||
| # ############################################################################# |
There was a problem hiding this comment.
| # ############################################################################# | |
| ############################################################################## |
Varimax PR: address review comments
|
|
||
| print(__doc__) | ||
|
|
||
| ############################################################################## |
There was a problem hiding this comment.
We are now using the following for section dividers:
| ############################################################################## | |
| # %% |
|
@amueller and @agramfort, are you happy with this PR? |
agramfort
left a comment
There was a problem hiding this comment.
needs a versionadded added to docstring and update to what's new page. Besides LGTM !
| `"The varimax criterion for analytic rotation in factor analysis" | ||
| <https://link.springer.com/article/10.1007%2FBF02289233>`_ | ||
| H. F. Kaiser, 1958 | ||
|
|
|
@agramfort was that a formal approval? |
agramfort
left a comment
There was a problem hiding this comment.
+1 for MRG provided CIs turn green. thx @jona-sassenhagen
|
The CI failure seems to be related to setting up the environment, would updating the branch with upstream fix it? Last time, this branch was merged with upstream was 12 days ago. |
Yes merging with master should fix it. I have done to merge to see if it works. |
|
We're green! |
|
Thanks for your contribution, and for your patience, @jona-sassenhagen!
|
|
Thanks everyone. |
* init varimax * fix py2 and add example text * fix print again * test, docs * fix test * fix doctstring * np.allclose for test * add refrence * address comments * update * Fix linting failure. * Fix deprecation warning. * a bit of cleanup * Address @jnothman's review comments. * Update section divider * Add note in what's new page and versionadded to docstring. Co-authored-by: Eric Prestat <[email protected]> Co-authored-by: Thomas J. Fan <[email protected]>
* init varimax * fix py2 and add example text * fix print again * test, docs * fix test * fix doctstring * np.allclose for test * add refrence * address comments * update * Fix linting failure. * Fix deprecation warning. * a bit of cleanup * Address @jnothman's review comments. * Update section divider * Add note in what's new page and versionadded to docstring. Co-authored-by: Eric Prestat <[email protected]> Co-authored-by: Thomas J. Fan <[email protected]>
Reference Issues/PRs
closes #2688
What does this implement/fix? Explain your changes.
As discussed in #2688, sklearn's Factor Analysis does not support rotations. This makes it essentially useless for e.g. psychologists.
This PR implements the varimax and quartimax rotations.