Repository: mne-tools/mne-python

Issue:
The computation of the mixing matrix for ICA imported from EEGLAB ICA is broken
#### Describe the bug

Commit https://github.com/mne-tools/mne-python/pull/6938/commits/23c6aa9ac85411afba3dd3687787b1c112226546 introduced a bug in the PR https://github.com/mne-tools/mne-python/pull/6938 as described in the comment https://github.com/mne-tools/mne-python/pull/6938/commits/23c6aa9ac85411afba3dd3687787b1c112226546#r413113709

#### Steps to reproduce

Using an ICA with a number of components smaller than the number of channels, I get the following components with the code as patched with this commit:

![image](https://user-images.githubusercontent.com/5932954/80005986-4b33d080-8492-11ea-9f67-b5c3684262cd.png)

and using seaborn clustermap on the correlation matrix between these components, I get:

![image](https://user-images.githubusercontent.com/5932954/80006073-63a3eb00-8492-11ea-94f5-b3195514ed98.png)

#### Expected results

Now, if I revert this change, I get something more reasonable:

![image](https://user-images.githubusercontent.com/5932954/80006215-98b03d80-8492-11ea-9733-092c02354fe3.png)

![image](https://user-images.githubusercontent.com/5932954/80006257-a239a580-8492-11ea-86a0-0ca1716dc212.png)

I unfortunately don't have the "gold truth" to compare with for the components at this time, but it already seems much more reasonable to me...

#### Proposal

I spotted that the issue here is the change from 

```
    ica.mixing_matrix_ = np.linalg.pinv(ica.pca_components_) @ eeg.icawinv
```
to
```
    ica._update_mixing_matrix()  # ==> self.mixing_matrix_ = linalg.pinv(self.unmixing_matrix_)
```

(Also, shouldn't "u * s" be "u @ s"? It does not seem to make any difference in my specific use case, but conceptually, it should be matrix multiplication, not a broadcasting.)

Apparently, the way EEGLAB defined its variable `linalg.pinv(self.unmixing_matrix_) != np.linalg.pinv(ica.pca_components_) @ eeg.icawinv`. I find it very hard to align the interfaces of MNE and EEGLAB because the approaches to ICA are a bit different, the naming is different, and so on, which makes it quite confusing. For example, how `u @ s` relates exactly to EEGLAB `icawinv` is unclear to me at this point. 

This seems to work:
```
    if n_components < n_ch:
        u, s, v = _safe_svd(eeg.icaweights, full_matrices=False)
        ica.unmixing_matrix_ = u @ s
        ica.pca_components_ = v
    else:
        [...]
    ica.mixing_matrix_ = np.linalg.pinv(ica.pca_components_) @ eeg.icawinv
```
The down side is that calling `ica._update_mixing_matrix()` at a later point would introduce a bug. So, an additional (ugly) fix is to store somewhere the value of `eeg.icawinv` and make  `ica._update_mixing_matrix()` of `ica.method == "imported_eeglab" `, which would looks like:

```
def _update_mixing_matrix(self):
    if self.method == "imported_eeglab":
        self.mixing_matrix_ = np.linalg.pinv(self.pca_components_) @ self._eeglab_icawinv         
    else:
        self.mixing_matrix_ = linalg.pinv(self.unmixing_matrix_)

def read_ica_eeglab(fname):
    [...]
    if n_components < n_ch:
        u, s, v = _safe_svd(eeg.icaweights, full_matrices=False)
        ica.unmixing_matrix_ = u @ s
        ica.pca_components_ = v
    else:
        [...]
    ica._eeglab_icawinv = eeg.icawinv  # Not great to add this attribute...
    ica._update_mixing_matrix()
```

FYI, simply doing
```
    if n_components < n_ch:
        u, s, v = _safe_svd(eeg.icaweights, full_matrices=False)
        # replace `ica.unmixing_matrix_ = u @ s` by
        ica.unmixing_matrix_ = np.linalg.pinv(np.linalg.pinv(ica.pca_components_) @ eeg.icawinv)
        ica.pca_components_ = v
    else:
        [...]
    ica._update_mixing_matrix() 
```
does not work. Don't ask me to explain why... Anyway, I can submit a PR with the proposed ugly changes and we can see if we can prettify it somehow... although, at this point, I am at a lost for a clean alignment of MNE and EEGLAB ICA interfaces, so I am not sure I can improve much on this.  In any case, I would argue that a working ugly code is preferable to an elegant broken one...


Discussion:
... and I can see if I can also propose in the PR a second test for this ICA import. I suspect that the current one test only the else part corresponding to the `if n_components < n_ch:` which might explain why it passes without issues. 
> (Also, shouldn't "u * s" be "u @ s"? It does not seem to make any difference in my specific use case, but conceptually, it should be matrix multiplication, not a broadcasting.)

`s` is a vector here so a (properly) broadcasted multiply is the same as a dot with the matrix version of s (`np.diag(s)`)

So the code:
```
        u, s, v = _safe_svd(eeg.icaweights, full_matrices=False)
        ica.unmixing_matrix_ = u @ s
```
Must be broken:
```
>>> import numpy as np
>>> x = np.random.RandomState(0).randn(3, 3)
>>> u, s, v = np.linalg.svd(x, full_matrices=False)
>>> s.shape
(3,)
>>> (u @ s).shape
(3,)
```
You either need to `u @ np.diag(s)` or use broadcasting to make it do the right thing:
```
>>> np.allclose(u @ np.diag(s), u * s)
True
```
Can you see if making use of this allows you to make more progress on the unification?

>  I am not sure I can improve much on this. In any case, I would argue that a working ugly code is preferable to an elegant broken one...

Indeed we need to make it work. One issue will be how we store any new values during I/O round trip. But yes making something work in the first place would help.

> and I can see if I can also propose in the PR a second test for this ICA import. I suspect that the current one test only the else part corresponding to the if n_components < n_ch: which might explain why it passes without issues.

I would actually do this in the opposite order. Write the test that fails in a first commit and push that (after adding any necessary mne-testing-data data, etc.). Then change the ICA code to make the new case and the old case work. That way others can take a look simultaneously -- I'd try to at least look to understand better for example. And we get the benefit of doing true TDD :)
> Using an ICA with a number of components smaller than the number of channels, I get the following components with the code as patched with this commit:

Just by eye looking at the maps thought of something -- have you tried tweaking the `rtol` in any `pinv` commands used in the code? To me it looks like a pseudoinversion gone wrong, blowing up a component. In particular we might store these matrices in `float32` format in which case the default `rtol=1e-15` could be wrong after I/O round trip. But even before that, it might be too high.

If you set them all to something very conservative like `rtol=1e-5` and your maps still look crazy, then this is not the problem. Though it could be some errantly set `n_components`-sort of thing somewhere, if those are used to choose the number of components during any inversion step.
Any progress on this? Just ran into this problem and can confirm that 379a1b9 seems to work fine.
@marcpabst can you share a file with some code that could be the basis of a test ?
Sure. ICA solutions should be fine from a privacy standpoint, I suppose. I might have to remove the actual data but that should not be needed for creating the `ICA` object anyway. Also, for me, the `Info` object is `None`. Btw I'm happy to work on this myself if no one else is atm.
any help is very much appreciated ! if you can do the PR it's even better.

>

In case it can be of any help, here is the "diff" from the version I use:

```
diff --git a/mne/preprocessing/ica.py b/mne/preprocessing/ica.py
index ae9bf6c45..0f579df71 100644
--- a/mne/preprocessing/ica.py
+++ b/mne/preprocessing/ica.py
@@ -709,7 +709,10 @@ class ICA(ContainsMixin):
         self.current_fit = fit_type
 
     def _update_mixing_matrix(self):
-        self.mixing_matrix_ = linalg.pinv(self.unmixing_matrix_)
+        if self.method == "imported_eeglab":
+            self.mixing_matrix_ = np.linalg.pinv(self.pca_components_) @ self._eeglab_icawinv
+        else:
+            self.mixing_matrix_ = linalg.pinv(self.unmixing_matrix_)
 
     def _update_ica_names(self):
         """Update ICA names when n_components_ is set."""
@@ -2545,11 +2548,13 @@ def read_ica_eeglab(fname):
         # sphere = eye(urchans), so let's use SVD to get our square
         # weights matrix (u * s) and our PCA vectors (v) back
         u, s, v = _safe_svd(eeg.icaweights, full_matrices=False)
-        ica.unmixing_matrix_ = u * s
+        ica.unmixing_matrix_ = u @ s
         ica.pca_components_ = v
     else:
         ica.unmixing_matrix_ = eeg.icaweights
         ica.pca_components_ = eeg.icasphere
+    ica._eeglab_icawinv = eeg.icawinv
     ica._update_mixing_matrix()
+
     ica._update_ica_names()
     return ica
```

I guess I did not have time to make a proper PR for that. My immediate concerns more often leans toward having code that simply works (at the expense of aesthetics or generalizability) when I am working on a specific analysis... so this "works" but it may be less well integrated than what would be an acceptable solution for a proper `python-mne` PR.
I can do the PR but can you share a file so I can assess myself
that what I get on my laptop is correct?

>

Here you go. I set all data to 0. but otherwise, this file should work. 

Oh, I forgot to rename the file to *.set, sorry.

[eeg.zip](https://github.com/mne-tools/mne-python/files/5281155/eeg.zip)

```
>>> ica =  mne.preprocessing.ica.read_ica_eeglab('eeg.mat')
>>> ica.plot_components()
```

RuntimeError: The ICA's measurement info is missing. Please fit the ICA or add the corresponding info object.

you don't get a proper info when reading from EEGLAB?

I do when using `read_raw_eeglab()`. Here's my working code. Note that I have to re-set the montage because coordinates directly imported from EEGLAB are not rendered correctly ([eeg_with_montage.zip](https://github.com/mne-tools/mne-python/files/5282371/eeg_with_montage.zip)).

```python
import mne

ica = mne.preprocessing.ica.read_ica_eeglab("eeg.set")
raw = mne.io.read_raw_eeglab("eeg.set")

ica.info = raw.info

ica._update_ica_names()

besa = mne.channels.read_custom_montage("standard-10-5-cap385.elp")
ica.info.set_montage(besa)

ica.plot_components()
```
Looks like this to me:
![image](https://user-images.githubusercontent.com/2624210/94260849-29eb7a80-ff31-11ea-801d-512407cb3e2e.png)

EDIT: Here is the aforementioned problem with imported EEGLAB coordinates:
![image](https://user-images.githubusercontent.com/2624210/94261594-653a7900-ff32-11ea-9bf0-dabbcaa30a04.png)





with current stable release I have issues:


import mne

ica = mne.preprocessing.ica.read_ica_eeglab("eeg.set")
raw = mne.io.read_raw_eeglab("eeg.set")

ica.info = raw.info

besa = mne.channels.read_custom_montage("standard-10-5-cap385.elp")
ica.info.set_montage(besa, on_missing='warn')

ica.plot_components()

gives me:


ValueError: The following electrodes have overlapping positions, which
causes problems during visualization:
PZ, OZ, FP2, FZ, CZ

>

Really weird. I'm on 0.21.0 and the code I posted works fine. Although using `ica.info.set_montage(besa, match_case=False)` might help?

Resolving patch:
diff --git a/mne/preprocessing/ica.py b/mne/preprocessing/ica.py
--- a/mne/preprocessing/ica.py
+++ b/mne/preprocessing/ica.py
@@ -123,7 +123,7 @@ def _check_for_unsupported_ica_channels(picks, info, allow_ref_meg=False):
 
 @fill_doc
 class ICA(ContainsMixin):
-    u"""M/EEG signal decomposition using Independent Component Analysis (ICA).
+    u"""Data decomposition using Independent Component Analysis (ICA).
 
     This object estimates independent components from :class:`mne.io.Raw`,
     :class:`mne.Epochs`, or :class:`mne.Evoked` objects. Components can
@@ -139,11 +139,11 @@ class ICA(ContainsMixin):
         Number of principal components (from the pre-whitening PCA step) that
         are passed to the ICA algorithm during fitting. If :class:`int`, must
         not be larger than ``max_pca_components``. If :class:`float` between 0
-        and 1, the number of components with cumulative explained variance less
-        than ``n_components`` will be used. If ``None``, ``max_pca_components``
-        will be used. Defaults to ``None``; the actual number used when
-        executing the :meth:`ICA.fit` method will be stored in the attribute
-        ``n_components_`` (note the trailing underscore).
+        and 1, the number of components with cumulative explained variance
+        greater than ``n_components`` will be used. If ``None``,
+        ``max_pca_components`` will be used. Defaults to ``None``; the actual
+        number used when executing the :meth:`ICA.fit` method will be stored in
+        the attribute ``n_components_`` (note the trailing underscore).
     max_pca_components : int | float | None
         The number of principal components (from the pre-whitening PCA step)
         that are retained for later use (i.e., for signal reconstruction in
@@ -151,8 +151,8 @@ class ICA(ContainsMixin):
         parameter). Use this parameter to reduce the dimensionality of the
         input data via PCA before any further processing is performed.
         If :class:`float` between 0 and 1, the number of components with
-        cumulative explained variance less than ``max_pca_components`` will be
-        used. If ``1.0`` or ``None``, no dimensionality reduction occurs and
+        cumulative explained variance greater than ``max_pca_components`` will
+        be used. If ``1.0`` or ``None``, no dimensionality reduction occurs and
         ``max_pca_components`` will equal the number of channels in the
         `~mne.io.Raw` or `~mne.Epochs` object passed to
         `~mne.preprocessing.ICA.fit`. Defaults to ``None``. When calling
@@ -169,10 +169,15 @@ class ICA(ContainsMixin):
         ``n_pca_components > n_components``, additional PCA components will be
         incorporated. If :class:`float` between 0 and 1, the number is chosen
         as the number of *PCA* components with cumulative explained variance
-        less than ``n_components`` (without accounting for ``ICA.include`` or
-        ``ICA.exclude``). If :class:`int` or :class:`float`, ``n_components_ ≤
-        n_pca_components ≤ max_pca_components_`` must hold. If ``None``,
-        ``max_pca_components`` will be used. Defaults to ``None``.
+        greater than ``n_components`` (without accounting for ``ICA.include``
+        or ``ICA.exclude``). If :class:`int` or :class:`float`,
+        ``n_components_ ≤ n_pca_components ≤ max_pca_components_`` must hold.
+        If ``None``, ``max_pca_components`` will be used. Defaults to ``None``.
+
+        .. versionchanged:: 0.22
+           The number of components returned for a :class:`python:float`
+           is greater than the given variance level instead of less than or
+           equal to it.
     noise_cov : None | instance of Covariance
         Noise covariance used for pre-whitening. If None (default), channels
         are scaled to unit variance ("z-standardized") prior to the whitening
@@ -185,7 +190,7 @@ class ICA(ContainsMixin):
         set additional parameters. Specifically, if you want Extended Infomax,
         set method='infomax' and fit_params=dict(extended=True) (this also
         works for method='picard'). Defaults to 'fastica'. For reference, see
-        [1]_, [2]_, [3]_ and [4]_.
+        :footcite:`Hyvarinen1999,BellSejnowski1995,LeeEtAl1999,AblinEtAl2018`.
     fit_params : dict | None
         Additional parameters passed to the ICA estimator as specified by
         ``method``.
@@ -259,25 +264,55 @@ class ICA(ContainsMixin):
     added to the object during fitting, consistent with standard scikit-learn
     practice.
 
-    Prior to fitting and applying the ICA, data is whitened (de-correlated and
-    scaled to unit variance, also called sphering transformation) by means of
-    a Principal Component Analysis (PCA). In addition to the whitening, this
-    step introduces the option to reduce the dimensionality of the data, both
-    prior to fitting the ICA (with the ``max_pca_components`` parameter) and
-    prior to reconstructing the sensor signals (with the ``n_pca_components``
-    parameter). In this way, we separate the question of how many ICA
-    components to estimate from the question of how much to reduce the
-    dimensionality of the signal. For example: by setting high values for
-    ``max_pca_components`` and ``n_pca_components``, relatively little
-    dimensionality reduction will occur when the signal is reconstructed,
-    regardless of the value of ``n_components`` (the number of ICA components
-    estimated).
+    ICA :meth:`fit` in MNE proceeds in two steps:
+
+    1. :term:`Whitening <whitening>` the data by means of principal component
+       analysis (PCA), using ``max_pca_components`` number of components.
+    2. Passing the ``n_components`` largest-variance components to the ICA
+       algorithm to obtain the unmixing matrix (and by pseudoinversion, the
+       mixing matrix).
+
+    ICA :meth:`apply` then:
+
+    1. Unmixes the data with the ``unmixing_matrix_``.
+    2. Includes ICA components based on ``ica.include`` and ``ica.exclude``.
+    3. Re-mixes the data with ``mixing_matrix_``.
+    4. Restores any data not passed to the ICA algorithm, i.e., the PCA
+       components between ``n_components`` and ``n_pca_components``.
+
+    Thus ``max_pca_components`` and ``n_pca_components`` determine how many
+    PCA components will be kept when reconstructing the data when calling
+    :meth:`apply`. These parameters can be used for dimensionality reduction of
+    the data, or dealing with low-rank data (such as those with projections, or
+    MEG data processed by SSS). It is important to remove any
+    numerically-zero-variance components in the data, otherwise numerical
+    instability causes problems when computing the mixing matrix.
+    Alternatively, using ``n_components`` as a float will also avoid
+    numerical stability problems.
+
+    The ``n_components`` parameter determines how many components out of
+    the ``max_pca_components`` the ICA algorithm will actually fit. This is not
+    typically used for EEG data, but for MEG data, it's common to use
+    ``n_components < max_pca_components``. For example, full-rank
+    306-channel MEG data might use ``max_pca_components=None`` (and
+    rank-reduced could use 0.9999) and ``n_components=40`` to find (and
+    later exclude) only large, dominating artifacts in the data, but still
+    reconstruct the data using all PCA components. Setting
+    ``max_pca_components=40``, on the other hand, would actually reduce the
+    rank of the resulting data to 40, which is typically undesirable.
+
+    If you are migrating from EEGLAB and intend to reduce dimensionality via
+    PCA, similarly to EEGLAB's ``runica(..., 'pca', n)`` functionality, simply
+    pass ``max_pca_components=n``, while leaving ``n_components`` and
+    ``n_pca_components`` at their respective default values. The resulting
+    reconstructed data after :meth:`apply` will have rank ``n``.
 
     .. note:: Commonly used for reasons of i) computational efficiency and
               ii) additional noise reduction, it is a matter of current debate
               whether pre-ICA dimensionality reduction could decrease the
               reliability and stability of the ICA, at least for EEG data and
-              especially during preprocessing [5]_. (But see also [6]_ for a
+              especially during preprocessing :footcite:`ArtoniEtAl2018`.
+              (But see also :footcite:`Montoya-MartinezEtAl2017` for a
               possibly confounding effect of the different whitening/sphering
               methods used in this paper (ZCA vs. PCA).)
               On the other hand, for rank-deficient data such as EEG data after
@@ -286,11 +321,6 @@ class ICA(ContainsMixin):
               interpolated channel) for optimal ICA performance (see the
               `EEGLAB wiki <eeglab_wiki_>`_).
 
-    If you are migrating from EEGLAB and intend to reduce dimensionality via
-    PCA, similarly to EEGLAB's ``runica(..., 'pca', n)`` functionality, simply
-    pass ``max_pca_components=n``, while leaving ``n_components`` and
-    ``n_pca_components`` at their respective default values.
-
     Caveat! If supplying a noise covariance, keep track of the projections
     available in the cov or in the raw object. For example, if you are
     interested in EOG or ECG artifacts, EOG and ECG projections should be
@@ -329,34 +359,7 @@ class ICA(ContainsMixin):
 
     References
     ----------
-    .. [1] Hyvärinen, A., 1999. Fast and robust fixed-point algorithms for
-           independent component analysis. IEEE transactions on Neural
-           Networks, 10(3), pp.626-634.
-
-    .. [2] Bell, A.J., Sejnowski, T.J., 1995. An information-maximization
-           approach to blind separation and blind deconvolution. Neural
-           computation, 7(6), pp.1129-1159.
-
-    .. [3] Lee, T.W., Girolami, M., Sejnowski, T.J., 1999. Independent
-           component analysis using an extended infomax algorithm for mixed
-           subgaussian and supergaussian sources. Neural computation, 11(2),
-           pp.417-441.
-
-    .. [4] Ablin P, Cardoso J, Gramfort A, 2018. Faster Independent Component
-           Analysis by Preconditioning With Hessian Approximations.
-           IEEE Transactions on Signal Processing 66:4040–4049
-
-    .. [5] Artoni, F., Delorme, A., und Makeig, S, 2018. Applying Dimension
-           Reduction to EEG Data by Principal Component Analysis Reduces the
-           Quality of Its Subsequent Independent Component Decomposition.
-           NeuroImage 175, pp.176–187.
-
-    .. [6] Montoya-Martínez, J., Cardoso, J.-F., Gramfort, A, 2017. Caveats
-           with stochastic gradient and maximum likelihood based ICA for EEG.
-           LVA-ICA International Conference, Feb 2017, Grenoble, France.
-           `〈hal-01451432〉 <hal-01451432_>`_
-
-    .. _hal-01451432: https://hal.archives-ouvertes.fr/hal-01451432/document
+    .. footbibliography::
     """  # noqa: E501
 
     @verbose
@@ -687,9 +690,8 @@ def _fit(self, data, fit_type):
         if (isinstance(self.max_pca_components, float) and
                 self.max_pca_components != 1.0):
             del data_transformed  # Free memory.
-            self.max_pca_components_ = (np.sum(pca.explained_variance_ratio_
-                                               .cumsum()
-                                        <= self.max_pca_components))
+            self.max_pca_components_ = _exp_var_ncomp(
+                pca.explained_variance_ratio_, self.max_pca_components)
 
             if (self.n_components is not None and
                     self.max_pca_components_ < self.n_components):
@@ -714,9 +716,9 @@ def _fit(self, data, fit_type):
         # given cumulative variance. This information will later be used to
         # only submit the corresponding parts of the data to ICA.
         if isinstance(self.n_components, float):
-            self.n_components_ = np.sum(
-                pca.explained_variance_ratio_.cumsum() <= self.n_components)
-            if self.n_components_ < 1:
+            self.n_components_ = _exp_var_ncomp(
+                pca.explained_variance_ratio_, self.n_components)
+            if self.n_components_ == 1:
                 raise RuntimeError('One PCA component captures most of the '
                                    'explained variance, your threshold resu'
                                    'lts in 0 components. You should select '
@@ -769,8 +771,9 @@ def _fit(self, data, fit_type):
             self.n_iter_ = n_iter + 1  # picard() starts counting at 0
             del _, n_iter
         assert self.unmixing_matrix_.shape == (self.n_components_,) * 2
-        self.unmixing_matrix_ /= np.sqrt(
-            self.pca_explained_variance_[sel])[None, :]  # whitening
+        norms = np.sqrt(self.pca_explained_variance_[:self.n_components_])
+        norms[norms == 0] = 1.
+        self.unmixing_matrix_ /= norms  # whitening
         self._update_mixing_matrix()
         self.current_fit = fit_type
 
@@ -1672,8 +1675,10 @@ def _pick_sources(self, data, include, exclude):
         n_ch, _ = data.shape
 
         if not(self.n_components_ <= _n_pca_comp <= self.max_pca_components_):
-            raise ValueError('n_pca_components must be >= '
-                             'n_components and <= max_pca_components.')
+            raise ValueError(
+                f'n_pca_components ({_n_pca_comp}) must be >= '
+                f'n_components_ ({self.n_components_}) and <= '
+                'max_pca_components_ ({self.max_pca_components_}).')
 
         logger.info('Transforming to ICA space (%i components)'
                     % self.n_components_)
@@ -1954,9 +1959,8 @@ def detect_artifacts(self, raw, start_find=None, stop_find=None,
     def _check_n_pca_components(self, _n_pca_comp, verbose=None):
         """Aux function."""
         if isinstance(_n_pca_comp, float):
-            _n_pca_comp = ((self.pca_explained_variance_ /
-                            self.pca_explained_variance_.sum()).cumsum() <=
-                           _n_pca_comp).sum()
+            _n_pca_comp = _exp_var_ncomp(
+                self.pca_explained_variance_, _n_pca_comp)
             logger.info('Selected %i PCA components by explained '
                         'variance' % _n_pca_comp)
         elif _n_pca_comp is None:
@@ -1967,6 +1971,14 @@ def _check_n_pca_components(self, _n_pca_comp, verbose=None):
         return _n_pca_comp
 
 
+def _exp_var_ncomp(var, n):
+    cvar = np.asarray(var, dtype=np.float64)
+    cvar = cvar.cumsum()
+    cvar /= cvar[-1]
+    # We allow 1., which would give us N+1
+    return min((cvar <= n).sum() + 1, len(cvar))
+
+
 def _check_start_stop(raw, start, stop):
     """Aux function."""
     out = list()
@@ -2724,16 +2736,18 @@ def read_ica_eeglab(fname):
 
     n_ch = len(ica.ch_names)
     assert eeg.icaweights.shape == (n_components, n_ch)
-    if n_components < n_ch:
-        # When PCA reduction is used in EEGLAB, runica returns
-        # weights= weights*sphere*eigenvectors(:,1:ncomps)';
-        # sphere = eye(urchans), so let's use SVD to get our square
-        # weights matrix (u * s) and our PCA vectors (v) back
-        u, s, v = _safe_svd(eeg.icaweights, full_matrices=False)
-        ica.unmixing_matrix_ = u * s
-        ica.pca_components_ = v
-    else:
-        ica.unmixing_matrix_ = eeg.icaweights
-        ica.pca_components_ = eeg.icasphere
+    # When PCA reduction is used in EEGLAB, runica returns
+    # weights= weights*sphere*eigenvectors(:,1:ncomps)';
+    # sphere = eye(urchans). When PCA reduction is not used, we have:
+    #
+    #     eeg.icawinv == pinv(eeg.icaweights @ eeg.icasphere)
+    #
+    # So in either case, we can use SVD to get our square whitened
+    # weights matrix (u * s) and our PCA vectors (v) back:
+    use = eeg.icaweights @ eeg.icasphere
+    u, s, v = _safe_svd(use, full_matrices=False)
+    ica.unmixing_matrix_ = u * s
+    ica.pca_components_ = v
+    ica.pca_explained_variance_ = s * s
     ica._update_mixing_matrix()
     return ica


Test patch:
diff --git a/mne/preprocessing/tests/test_ica.py b/mne/preprocessing/tests/test_ica.py
--- a/mne/preprocessing/tests/test_ica.py
+++ b/mne/preprocessing/tests/test_ica.py
@@ -18,8 +18,8 @@
 from mne import (Epochs, read_events, pick_types, create_info, EpochsArray,
                  EvokedArray, Annotations, pick_channels_regexp)
 from mne.cov import read_cov
-from mne.preprocessing import (ICA, ica_find_ecg_events, ica_find_eog_events,
-                               read_ica)
+from mne.preprocessing import (ICA as _ICA, ica_find_ecg_events,
+                               ica_find_eog_events, read_ica)
 from mne.preprocessing.ica import (get_score_funcs, corrmap, _sort_components,
                                    _ica_explained_variance, read_ica_eeglab)
 from mne.io import read_raw_fif, Info, RawArray, read_raw_ctf, read_raw_eeglab
@@ -27,8 +27,7 @@
 from mne.io.pick import _DATA_CH_TYPES_SPLIT
 from mne.io.eeglab.eeglab import _check_load_mat
 from mne.rank import _compute_rank_int
-from mne.utils import (catch_logging, _TempDir, requires_sklearn,
-                       run_tests_if_main)
+from mne.utils import catch_logging, requires_sklearn, run_tests_if_main
 from mne.datasets import testing
 from mne.event import make_fixed_length_events
 
@@ -53,6 +52,13 @@
 score_funcs_unsuited = ['pointbiserialr', 'ansari']
 
 
+def ICA(*args, **kwargs):
+    """Fix the random state in tests."""
+    if 'random_state' not in kwargs:
+        kwargs['random_state'] = 0
+    return _ICA(*args, **kwargs)
+
+
 def _skip_check_picard(method):
     if method == 'picard':
         try:
@@ -88,9 +94,10 @@ def test_ica_full_data_recovery(method):
                       max_pca_components=n_pca_components,
                       n_pca_components=n_pca_components,
                       method=method, max_iter=1)
+            picks = list(range(n_channels))
             with pytest.warns(UserWarning, match=None):  # sometimes warns
-                ica.fit(raw, picks=list(range(n_channels)))
-            _assert_ica_attributes(ica)
+                ica.fit(raw, picks=picks)
+            _assert_ica_attributes(ica, raw.get_data(picks))
             raw2 = ica.apply(raw.copy(), exclude=[])
             if ok:
                 assert_allclose(data[:n_channels], raw2._data[:n_channels],
@@ -103,8 +110,8 @@ def test_ica_full_data_recovery(method):
                       max_pca_components=n_pca_components,
                       n_pca_components=n_pca_components, random_state=0)
             with pytest.warns(None):  # sometimes warns
-                ica.fit(epochs, picks=list(range(n_channels)))
-            _assert_ica_attributes(ica)
+                ica.fit(epochs, picks=picks)
+            _assert_ica_attributes(ica, epochs.get_data(picks))
             epochs2 = ica.apply(epochs.copy(), exclude=[])
             data2 = epochs2.get_data()[:, :n_channels]
             if ok:
@@ -143,22 +150,75 @@ def test_ica_simple(method):
     data = np.dot(A, S)
     ica = ICA(n_components=n_components, method=method, random_state=0)
     ica._fit(data, 0)
-    transform = np.dot(np.dot(ica.unmixing_matrix_, ica.pca_components_), A)
+    transform = ica.unmixing_matrix_ @ ica.pca_components_ @ A
     amari_distance = np.mean(np.sum(np.abs(transform), axis=1) /
                              np.max(np.abs(transform), axis=1) - 1.)
     assert amari_distance < 0.1
 
 
+@requires_sklearn
+@pytest.mark.parametrize('n_components', (None, 0.9999, 8, 9, 10))
+@pytest.mark.parametrize('max_pca_components', [8, 9, 0.9999, 10])
+@pytest.mark.filterwarnings('ignore:FastICA did not converge.*:UserWarning')
+def test_ica_noop(n_components, max_pca_components, tmpdir):
+    """Test that our ICA is stable even with a bad max_pca_components."""
+    data = np.random.RandomState(0).randn(10, 1000)
+    info = create_info(10, 1000., 'eeg')
+    raw = RawArray(data, info)
+    raw.set_eeg_reference()
+    assert np.linalg.matrix_rank(raw.get_data()) == 9
+    kwargs = dict(n_components=n_components,
+                  max_pca_components=max_pca_components, verbose=True)
+    if isinstance(n_components, int) and \
+            isinstance(max_pca_components, int) and \
+            n_components > max_pca_components:
+        with pytest.raises(ValueError, match='n_components must be smaller.*'):
+            ICA(**kwargs)
+        return
+    ica = ICA(**kwargs)
+    if n_components == 10 and isinstance(max_pca_components, float):
+        with pytest.raises(ValueError, match='select only a subset'):
+            ica.fit(raw)
+        return
+    ica.fit(raw)
+    raw_new = ica.apply(raw.copy())
+    # not a no-op
+    if max_pca_components == 8:  # XXX this fails: or n_pca_components == 8:
+        assert not np.allclose(raw.get_data(), raw_new.get_data())
+        return
+    # XXX this case should warn?
+    if max_pca_components == 10:
+        assert ica.max_pca_components_ == 10
+        # XXX setting n_components to something safe prevents the blowup, maybe
+        # we don't need max_pca_components as float? For example setting the
+        # default or warning based on n_components is probably enough...
+        if not (max_pca_components == 10 and n_components in (8, 9, 0.9999)):
+            assert not np.allclose(raw.get_data(), raw_new.get_data())
+            return
+    else:
+        assert ica.max_pca_components_ == 9
+    assert_allclose(raw.get_data(), raw_new.get_data(), err_msg='Id failure')
+    _assert_ica_attributes(ica, data)
+    # and with I/O
+    fname = tmpdir.join('temp-ica.fif')
+    ica.save(fname)
+    ica = read_ica(fname)
+    raw_new = ica.apply(raw.copy())
+    assert_allclose(raw.get_data(), raw_new.get_data(), err_msg='I/O failure')
+    _assert_ica_attributes(ica)
+
+
 @requires_sklearn
 @pytest.mark.parametrize("method", ["infomax", "fastica", "picard"])
-def test_ica_n_iter_(method):
+def test_ica_n_iter_(method, tmpdir):
     """Test that ICA.n_iter_ is set after fitting."""
     _skip_check_picard(method)
 
     raw = read_raw_fif(raw_fname).crop(0.5, stop).load_data()
     n_components = 3
     max_iter = 1
-    ica = ICA(n_components=n_components, max_iter=max_iter, method=method)
+    ica = ICA(n_components=n_components, max_iter=max_iter, method=method,
+              random_state=0)
 
     if method == 'infomax':
         ica.fit(raw)
@@ -169,9 +229,8 @@ def test_ica_n_iter_(method):
     assert_equal(ica.n_iter_, max_iter)
 
     # Test I/O roundtrip.
-    tempdir = _TempDir()
-    output_fname = op.join(tempdir, 'test_ica-ica.fif')
-    _assert_ica_attributes(ica)
+    output_fname = tmpdir.join('test_ica-ica.fif')
+    _assert_ica_attributes(ica, raw.get_data('data'), limits=(1, 70))
     ica.save(output_fname)
     ica = read_ica(output_fname)
     _assert_ica_attributes(ica)
@@ -275,7 +334,7 @@ def test_ica_core(method):
         # Test ICA raw
         ica = ICA(noise_cov=n_cov, n_components=n_comp,
                   max_pca_components=max_n, n_pca_components=max_n,
-                  random_state=0, method=method, max_iter=1)
+                  method=method, max_iter=1)
         with pytest.raises(ValueError, match='Cannot check for channels of t'):
             'meg' in ica
 
@@ -330,10 +389,10 @@ def test_ica_core(method):
         # test epochs decomposition
         ica = ICA(noise_cov=n_cov, n_components=n_comp,
                   max_pca_components=max_n, n_pca_components=max_n,
-                  random_state=0, method=method)
+                  method=method)
         with pytest.warns(None):  # sometimes warns
             ica.fit(epochs, picks=picks)
-        _assert_ica_attributes(ica)
+        _assert_ica_attributes(ica, epochs.get_data(picks), limits=(0.2, 20))
         data = epochs.get_data()[:, 0, :]
         n_samples = np.prod(data.shape)
         assert_equal(ica.n_samples_, n_samples)
@@ -384,11 +443,10 @@ def test_ica_core(method):
 @requires_sklearn
 @pytest.mark.slowtest
 @pytest.mark.parametrize("method", ["picard", "fastica"])
-def test_ica_additional(method):
+def test_ica_additional(method, tmpdir):
     """Test additional ICA functionality."""
     _skip_check_picard(method)
 
-    tempdir = _TempDir()
     stop2 = 500
     raw = read_raw_fif(raw_fname).crop(1.5, stop).load_data()
     raw.del_proj()  # avoid warnings
@@ -406,10 +464,10 @@ def test_ica_additional(method):
 
     # test if n_components=None works
     ica = ICA(n_components=None, max_pca_components=None,
-              n_pca_components=None, random_state=0, method=method, max_iter=1)
+              n_pca_components=None, method=method, max_iter=1)
     with pytest.warns(UserWarning, match='did not converge'):
         ica.fit(epochs)
-    _assert_ica_attributes(ica)
+    _assert_ica_attributes(ica, epochs.get_data('data'), limits=(0.0005, 10))
     # for testing eog functionality
     picks2 = np.concatenate([picks, pick_types(raw.info, False, eog=True)])
     epochs_eog = Epochs(raw, events[:4], event_id, tmin, tmax, picks=picks2,
@@ -422,16 +480,17 @@ def test_ica_additional(method):
     assert (ica.info is None)
     with pytest.warns(RuntimeWarning, match='normalize_proj'):
         ica.fit(raw, picks[:5])
-    _assert_ica_attributes(ica)
+    _assert_ica_attributes(ica, raw.get_data(np.arange(5)))
     assert (isinstance(ica.info, Info))
     assert (ica.n_components_ < 5)
 
     ica = ICA(n_components=3, max_pca_components=4, method=method,
-              n_pca_components=4, random_state=0)
+              n_pca_components=4)
     pytest.raises(RuntimeError, ica.save, '')
 
     ica.fit(raw, picks=[1, 2, 3, 4, 5], start=start, stop=stop2)
-    _assert_ica_attributes(ica)
+    _assert_ica_attributes(
+        ica, raw.get_data(np.arange(1, 6), start=start, stop=stop2))
 
     # check Kuiper index threshold
     assert_equal(ica._get_ctps_threshold(), 0.21)
@@ -485,12 +544,11 @@ def test_ica_additional(method):
     corrmap([ica, ica3], template, threshold=[0.5], label='blinks', plot=True,
             ch_type="mag")
 
-    ica_different_channels = ICA(n_components=2, random_state=0).fit(
-        raw, picks=[2, 3, 4, 5])
+    ica_different_channels = ICA(n_components=2).fit(raw, picks=[2, 3, 4, 5])
     pytest.raises(ValueError, corrmap, [ica_different_channels, ica], (0, 0))
 
     # test warnings on bad filenames
-    ica_badname = op.join(op.dirname(tempdir), 'test-bad-name.fif.gz')
+    ica_badname = tmpdir.join('test-bad-name.fif.gz')
     with pytest.warns(RuntimeWarning, match='-ica.fif'):
         ica.save(ica_badname)
     with pytest.warns(RuntimeWarning, match='-ica.fif'):
@@ -528,7 +586,7 @@ def test_ica_additional(method):
     # epochs extraction from raw fit
     pytest.raises(RuntimeError, ica.get_sources, epochs)
     # test reading and writing
-    test_ica_fname = op.join(op.dirname(tempdir), 'test-ica.fif')
+    test_ica_fname = tmpdir.join('test-ica.fif')
     for cov in (None, test_cov):
         ica = ICA(noise_cov=cov, n_components=2, max_pca_components=4,
                   n_pca_components=4, method=method, max_iter=1)
@@ -749,14 +807,14 @@ def f(x, y):
     # test float n pca components
     ica.pca_explained_variance_ = np.array([0.2] * 5)
     ica.n_components_ = 0
-    for ncomps, expected in [[0.3, 1], [0.9, 4], [1, 1]]:
+    for ncomps, expected in [[0.3, 2], [0.9, 5], [1, 1]]:
         ncomps_ = ica._check_n_pca_components(ncomps)
         assert (ncomps_ == expected)
 
     ica = ICA(method=method)
     with pytest.warns(None):  # sometimes does not converge
         ica.fit(raw, picks=picks[:5])
-    _assert_ica_attributes(ica)
+    _assert_ica_attributes(ica, raw.get_data(picks[:5]))
     with pytest.warns(RuntimeWarning, match='longer'):
         ica.find_bads_ecg(raw, threshold='auto')
     ica.find_bads_eog(epochs, ch_name='MEG 0121')
@@ -816,13 +874,13 @@ def test_ica_twice(method):
     n_pca_components = 1.1
     ica1 = ICA(n_components=n_components, method=method,
                max_pca_components=max_pca_components,
-               n_pca_components=n_pca_components, random_state=0)
+               n_pca_components=n_pca_components)
 
     ica1.fit(raw, picks=picks, decim=3)
     raw_new = ica1.apply(raw, n_pca_components=n_pca_components)
     ica2 = ICA(n_components=n_components, method=method,
                max_pca_components=max_pca_components,
-               n_pca_components=1.0, random_state=0)
+               n_pca_components=1.0)
     ica2.fit(raw_new, picks=picks, decim=3)
     assert_equal(ica1.n_components_, ica2.n_components_)
 
@@ -833,7 +891,8 @@ def test_fit_params(method, tmpdir):
     """Test fit_params for ICA."""
     _skip_check_picard(method)
     fit_params = {}
-    ICA(fit_params=fit_params, method=method)  # test no side effects
+    # test no side effects
+    ICA(fit_params=fit_params, method=method)
     assert fit_params == {}
 
     # Test I/O roundtrip.
@@ -930,7 +989,7 @@ def test_eog_channel(method):
         picks1 = np.append(picks1a, picks1b)
         ica.fit(inst, picks=picks1)
         assert (any('EOG' in ch for ch in ica.ch_names))
-        _assert_ica_attributes(ica)
+        _assert_ica_attributes(ica, inst.get_data(picks1), limits=(0.8, 600))
     # Test case for MEG data. Should have no EOG channel
     for inst in [raw, epochs]:
         picks1 = pick_types(inst.info, meg=True, stim=False, ecg=False,
@@ -942,7 +1001,7 @@ def test_eog_channel(method):
 
 @requires_sklearn
 @pytest.mark.parametrize('max_pca_components', (1.0, 15, 0.99, 0.5, 1.5))
-def test_max_pca_components(max_pca_components):
+def test_max_pca_components(max_pca_components, tmpdir):
     """Test max_pca_components."""
     raw = read_raw_fif(raw_fname).crop(1.5, stop).load_data()
     events = read_events(event_name)
@@ -954,8 +1013,7 @@ def test_max_pca_components(max_pca_components):
     random_state = 12345
     method = 'infomax'
 
-    tempdir = _TempDir()
-    output_fname = op.join(tempdir, 'test_ica-ica.fif')
+    output_fname = tmpdir.join('test_ica-ica.fif')
 
     if max_pca_components == 1.5:
         with pytest.raises(ValueError, match='PCA .* needs values between'):
@@ -973,7 +1031,7 @@ def test_max_pca_components(max_pca_components):
 
     ica.fit(epochs)
 
-    _assert_ica_attributes(ica)
+    _assert_ica_attributes(ica, epochs.get_data(), limits=(0.01, 50))
     ica.save(output_fname)
     ica = read_ica(output_fname)
     assert_equal(ica.max_pca_components, max_pca_components)
@@ -990,11 +1048,10 @@ def test_max_pca_components(max_pca_components):
 
 @requires_sklearn
 @pytest.mark.parametrize('method', ['infomax', 'fastica', 'picard'])
-def test_max_pca_components_(method):
+def test_max_pca_components_(method, tmpdir):
     """Test that max_pca_components_ gets populated when reading old file."""
     _skip_check_picard(method)
-    tempdir = _TempDir()
-    fname = op.join(tempdir, 'test_ica-ica.fif')
+    fname = tmpdir.join('test_ica-ica.fif')
 
     raw = read_raw_fif(raw_fname).crop(1.5, stop).load_data()
     events = read_events(event_name)
@@ -1004,10 +1061,7 @@ def test_max_pca_components_(method):
 
     max_pca_components = 5
     ica = ICA(max_pca_components=max_pca_components, method=method)
-    if method == 'fastica':
-        with pytest.warns(UserWarning, match='did not converge'):
-            ica.fit(epochs)
-    else:
+    with pytest.warns(None):  # sometimes convergence
         ica.fit(epochs)
 
     # Old files don't have max_pca_components_; instead, during fitting,
@@ -1022,7 +1076,7 @@ def test_max_pca_components_(method):
 
 @requires_sklearn
 @pytest.mark.parametrize("method", ["fastica", "picard"])
-def test_n_components_none(method):
+def test_n_components_none(method, tmpdir):
     """Test n_components=None."""
     _skip_check_picard(method)
     raw = read_raw_fif(raw_fname).crop(1.5, stop).load_data()
@@ -1035,8 +1089,7 @@ def test_n_components_none(method):
     n_components = None
     random_state = 12345
 
-    tempdir = _TempDir()
-    output_fname = op.join(tempdir, 'test_ica-ica.fif')
+    output_fname = tmpdir.join('test_ica-ica.fif')
     ica = ICA(max_pca_components=max_pca_components, method=method,
               n_components=n_components, random_state=random_state)
     with pytest.warns(None):
@@ -1055,7 +1108,7 @@ def test_n_components_none(method):
 
 @requires_sklearn
 @pytest.mark.parametrize("method", ["fastica", "picard"])
-def test_n_components_and_max_pca_components_none(method):
+def test_n_components_and_max_pca_components_none(method, tmpdir):
     """Test n_components and max_pca_components=None."""
     _skip_check_picard(method)
     raw = read_raw_fif(raw_fname).crop(1.5, stop).load_data()
@@ -1068,8 +1121,7 @@ def test_n_components_and_max_pca_components_none(method):
     n_components = None
     random_state = 12345
 
-    tempdir = _TempDir()
-    output_fname = op.join(tempdir, 'test_ica-ica.fif')
+    output_fname = tmpdir.join('test_ica-ica.fif')
     ica = ICA(max_pca_components=max_pca_components, method=method,
               n_components=n_components, random_state=random_state)
     with pytest.warns(None):  # convergence
@@ -1097,8 +1149,7 @@ def test_ica_ctf():
 
         # test fit
         for inst in [raw, epochs]:
-            ica = ICA(n_components=2, random_state=0, max_iter=2,
-                      method=method)
+            ica = ICA(n_components=2, max_iter=2, method=method)
             with pytest.warns(UserWarning, match='did not converge'):
                 ica.fit(inst)
             _assert_ica_attributes(ica)
@@ -1110,7 +1161,7 @@ def test_ica_ctf():
 
     # test mixed compensation case
     raw.apply_gradient_compensation(0)
-    ica = ICA(n_components=2, random_state=0, max_iter=2, method=method)
+    ica = ICA(n_components=2, max_iter=2, method=method)
     with pytest.warns(UserWarning, match='did not converge'):
         ica.fit(raw)
     _assert_ica_attributes(ica)
@@ -1134,8 +1185,7 @@ def test_ica_labels():
 
     # set the appropriate EEG channels to EOG and ECG
     raw.set_channel_types({'EEG057': 'eog', 'EEG058': 'eog', 'EEG059': 'ecg'})
-    ica = ICA(n_components=4, random_state=0, max_iter=2, method='fastica',
-              allow_ref_meg=True)
+    ica = ICA(n_components=4, max_iter=2, method='fastica', allow_ref_meg=True)
     with pytest.warns(UserWarning, match='did not converge'):
         ica.fit(raw)
     _assert_ica_attributes(ica)
@@ -1159,8 +1209,7 @@ def test_ica_labels():
         assert key not in ica.labels_
 
     # derive reference ICA components and append them to raw
-    ica_rf = ICA(n_components=2, random_state=0, max_iter=2,
-                 allow_ref_meg=True)
+    ica_rf = ICA(n_components=2, max_iter=2, allow_ref_meg=True)
     with pytest.warns(UserWarning, match='did not converge'):
         ica_rf.fit(raw.copy().pick_types(meg=False, ref_meg=True))
     icacomps = ica_rf.get_sources(raw)
@@ -1209,8 +1258,7 @@ def test_ica_eeg():
                 continue
             # test fit
             for inst in [raw, epochs]:
-                ica = ICA(n_components=2, random_state=0, max_iter=2,
-                          method=method)
+                ica = ICA(n_components=2, max_iter=2, method=method)
                 with pytest.warns(None):
                     ica.fit(inst, picks=picks)
                 _assert_ica_attributes(ica)
@@ -1237,8 +1285,7 @@ def test_ica_eeg():
                 continue
             # test fit
             for inst in [raw, epochs]:
-                ica = ICA(n_components=2, random_state=0, max_iter=2,
-                          method=method)
+                ica = ICA(n_components=2, max_iter=2, method=method)
                 with pytest.warns(None):
                     ica.fit(inst)
                 _assert_ica_attributes(ica)
@@ -1281,7 +1328,7 @@ def test_read_ica_eeglab():
                     rtol=1e-05, atol=1e-08)
 
 
-def _assert_ica_attributes(ica):
+def _assert_ica_attributes(ica, data=None, limits=(0.3, 30)):
     """Assert some attributes of ICA objects."""
     __tracebackhide__ = True
     # This tests properties, but also serves as documentation of
@@ -1310,6 +1357,21 @@ def _assert_ica_attributes(ica):
     want = np.eye(ica.n_components_)
     want[:nz] = 0
     assert_allclose(mix_unmix, want, atol=1e-6, err_msg='Mixing as pinv')
+    assert ica.pca_explained_variance_.shape[0] >= \
+        ica.unmixing_matrix_.shape[1]
+    # our PCA components should be unit vectors (the variances get put into
+    # the unmixing_matrix_ to make it a whitener)
+    norms = np.linalg.norm(ica.pca_components_, axis=1)
+    assert_allclose(norms, 1.)
+    # let's check the whitening
+    if data is not None:
+        if data.ndim == 3:
+            data = data.transpose(1, 0, 2).reshape(data.shape[1], -1)
+        data = ica._transform(data)
+        norms = np.linalg.norm(data, axis=1)
+        # at least close to normal
+        assert norms.min() > limits[0]
+        assert norms.max() < limits[1]
 
 
 run_tests_if_main()
diff --git a/mne/utils/tests/test_numerics.py b/mne/utils/tests/test_numerics.py
--- a/mne/utils/tests/test_numerics.py
+++ b/mne/utils/tests/test_numerics.py
@@ -405,7 +405,7 @@ def test_hash():
 
 
 @requires_sklearn
-@pytest.mark.parametrize('n_components', (None, 0.8, 8, 'mle'))
+@pytest.mark.parametrize('n_components', (None, 0.9999, 8, 'mle'))
 @pytest.mark.parametrize('whiten', (True, False))
 def test_pca(n_components, whiten):
     """Test PCA equivalence."""
@@ -421,12 +421,13 @@ def test_pca(n_components, whiten):
     X_mne = pca_mne.fit_transform(X)
     assert_array_equal(X, X_orig)
     assert_allclose(X_skl, X_mne)
+    assert pca_mne.n_components_ == pca_skl.n_components_
     for key in ('mean_', 'components_',
                 'explained_variance_', 'explained_variance_ratio_'):
         val_skl, val_mne = getattr(pca_skl, key), getattr(pca_mne, key)
         assert_allclose(val_skl, val_mne)
     if isinstance(n_components, float):
-        assert 1 < pca_mne.n_components_ < n_dim
+        assert pca_mne.n_components_ == n_dim - 1
     elif isinstance(n_components, int):
         assert pca_mne.n_components_ == n_components
     elif n_components == 'mle':