Repository: mne-tools/mne-python

Issue:
Set ICA's max_iter to a higher default
Currently, `ICA.max_iter` is set to 200 by default. This is one of the few MNE defaults I **always** change when working with data from our lab, and I've seen quite a few support requests indicating that ICA didn't converge within those 200 iterations too, the most recent one here: https://mne.discourse.group/t/cannot-find-ecg-artifacts-during-ica/2778/11?u=richard

I'd like to suggest to increase the `max_iter` default to 1000 to ensure that non-convergence will really be a rather rare exception for most users. I don't think there's many valid use cases for working with non-converged ICA decompositions anyway, or are there? Every time I've encountered the warning about non-converged ICA, the decomposition looked … flaky.

WDYT?

cc @cbrnr 


Discussion:
how is it done in other software packages?

>

> how is it done in other software packages?

EEGLAB uses

[500 for picard](https://github.com/sccn/eeglab/blob/96f5b56adec802ea3fc296085f17d3f21c204942/functions/popfunc/pop_runica.m#L167)
[1000 for pearson ICA](https://github.com/sccn/eeglab/blob/96f5b56adec802ea3fc296085f17d3f21c204942/functions/popfunc/pop_runica.m#L487)

For Infomax, AMICA and FastICA I couldn't find any defaults set in the EEGLAB sources – they're probably relying on the respective defaults of the binaries they're calling..?
let's try to stick to the same defautls

>

Ok it seems to be
[1000 for FastICA](https://github.com/davidkun/FastICA/blob/df9dbda6a50161285afd6933b04241f974c6e377/fastica.m#L94-L95)
[2000 for AMICA](https://github.com/japalmer29/amica/blob/008abb83a98ed34c0c57785804ee34ec52a59c4b/pop_runamica.m#L10)

Infomax I still couldn't find.
[512 for (extended) Infomax](https://github.com/sccn/eeglab/blob/96f5b56adec802ea3fc296085f17d3f21c204942/functions/sigprocfunc/binica.m#L29)
--> My proposal:

- Change the signature of `ICA(..., max_iter=200)` to `... max_iter=None`
- fill in the default based on the used algorithm:
    - Infomax & picard -> 500
    - fastica -> 1000

Needs a deprecation cycle.

Would also be suitable as a task for the sprint if we agree this is a change we'd like to have.

ok for a sprint issue

>

I will take this one, and then move on to other ICA issues w/ Tutorial, Example
Perfect!

Resolving patch:
diff --git a/mne/preprocessing/ica.py b/mne/preprocessing/ica.py
--- a/mne/preprocessing/ica.py
+++ b/mne/preprocessing/ica.py
@@ -186,18 +186,25 @@ class ICA(ContainsMixin):
         As estimation can be non-deterministic it can be useful to fix the
         random state to have reproducible results.
     method : {'fastica', 'infomax', 'picard'}
-        The ICA method to use in the fit method. Use the fit_params argument to
-        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
-        :footcite:`Hyvarinen1999,BellSejnowski1995,LeeEtAl1999,AblinEtAl2018`.
+        The ICA method to use in the fit method. Use the ``fit_params`` argument
+        to 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 :footcite:`Hyvarinen1999,BellSejnowski1995,LeeEtAl1999,AblinEtAl2018`.
     fit_params : dict | None
         Additional parameters passed to the ICA estimator as specified by
         ``method``.
-    max_iter : int
-        Maximum number of iterations during fit. Defaults to 200. The actual
-        number of iterations it took :meth:`ICA.fit` to complete will be stored
-        in the ``n_iter_`` attribute.
+    max_iter : int | 'auto'
+        Maximum number of iterations during fit. If ``'auto'``, it
+        will set maximum iterations to ``1000`` for ``'fastica'``
+        and to ``500`` for ``'infomax'`` or ``'picard'``. The actual number of
+        iterations it took :meth:`ICA.fit` to complete will be stored in the
+        ``n_iter_`` attribute.
+
+        .. versionchanged:: 0.23
+            Version 0.23 introduced ``'auto'`` settings for maximum iterations
+             as specified above. With version 0.24 ``'auto'`` will be the new
+            default, replacing the current ``max_iter=200``.
     allow_ref_meg : bool
         Allow ICA on MEG reference channels. Defaults to False.
 
@@ -360,7 +367,7 @@ class ICA(ContainsMixin):
     @verbose
     def __init__(self, n_components=None, *, noise_cov=None,
                  random_state=None, method='fastica', fit_params=None,
-                 max_iter=200, allow_ref_meg=False,
+                 max_iter=None, allow_ref_meg=False,
                  verbose=None):  # noqa: D102
         _validate_type(method, str, 'method')
         _validate_type(n_components, (float, 'int-like', None))
@@ -409,6 +416,19 @@ def __init__(self, n_components=None, *, noise_cov=None,
             # extended=True is default in underlying function, but we want
             # default False here unless user specified True:
             fit_params.setdefault('extended', False)
+        if max_iter is None:
+            depr_message = ('Version 0.23 introduced max_iter="auto", '
+                            'setting max_iter=1000 for `fastica` and '
+                            'max_iter=500 for `infomax` and `picard`. The '
+                            'current default of max_iter=200 will be changed '
+                            'to "auto" in version 0.24.')
+            warn(depr_message, DeprecationWarning)
+            max_iter = 200
+        elif max_iter == 'auto':
+            if method == 'fastica':
+                max_iter = 1000
+            elif method in ['infomax', 'picard']:
+                max_iter = 500
         fit_params.setdefault('max_iter', max_iter)
         self.max_iter = max_iter
         self.fit_params = fit_params


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
@@ -72,6 +72,8 @@ def _skip_check_picard(method):
 
 @requires_sklearn
 @pytest.mark.parametrize("method", ["fastica", "picard"])
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_ica_full_data_recovery(method):
     """Test recovery of full data when no source is rejected."""
     # Most basic recovery
@@ -135,6 +137,8 @@ def test_ica_full_data_recovery(method):
 
 
 @pytest.mark.parametrize("method", ["fastica", "picard"])
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_ica_simple(method):
     """Test that ICA recovers the unmixing matrix in a simple case."""
     if method == "fastica":
@@ -165,6 +169,8 @@ def test_ica_simple(method):
 @pytest.mark.parametrize('n_components', (None, 0.9999, 8, 9, 10))
 @pytest.mark.parametrize('n_pca_components', [8, 9, 0.9999, 10])
 @pytest.mark.filterwarnings('ignore:FastICA did not converge.*:UserWarning')
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_ica_noop(n_components, n_pca_components, tmpdir):
     """Test that our ICA is stable even with a bad max_pca_components."""
     data = np.random.RandomState(0).randn(10, 1000)
@@ -214,8 +220,29 @@ def test_ica_noop(n_components, n_pca_components, tmpdir):
     _assert_ica_attributes(ica)
 
 
+@requires_sklearn
+@pytest.mark.parametrize("method, max_iter_default", [("fastica", 1000),
+                         ("infomax", 500), ("picard", 500)])
+def test_ica_max_iter_(method, max_iter_default):
+    """Test that ICA.max_iter is set to the right defaults."""
+    _skip_check_picard(method)
+
+    # check that deprecated default max_iter=200 comes out for no input
+    with pytest.warns(DeprecationWarning, match='max_iter.*will be changed'):
+        ica = ICA(n_components=3, method=method)
+        assert ica.max_iter == 200
+    # check that new defaults come out for 'auto'
+    ica = ICA(n_components=3, method=method, max_iter='auto')
+    assert ica.max_iter == max_iter_default
+    # check that user input comes out unchanged
+    ica = ICA(n_components=3, method=method, max_iter=2000)
+    assert ica.max_iter == 2000
+
+
 @requires_sklearn
 @pytest.mark.parametrize("method", ["infomax", "fastica", "picard"])
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_ica_n_iter_(method, tmpdir):
     """Test that ICA.n_iter_ is set after fitting."""
     _skip_check_picard(method)
@@ -376,6 +403,8 @@ def test_ica_reset(method):
 @pytest.mark.parametrize('n_components', (2, 0.6))
 @pytest.mark.parametrize('noise_cov', (False, True))
 @pytest.mark.parametrize('n_pca_components', [20])
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_ica_core(method, n_components, noise_cov, n_pca_components):
     """Test ICA on raw and epochs."""
     _skip_check_picard(method)
@@ -530,6 +559,8 @@ def short_raw_epochs():
 @requires_sklearn
 @pytest.mark.slowtest
 @pytest.mark.parametrize("method", ["picard", "fastica"])
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_ica_additional(method, tmpdir, short_raw_epochs):
     """Test additional ICA functionality."""
     _skip_check_picard(method)
@@ -883,6 +914,8 @@ def f(x, y):
     ('picard', test_cov_name),
     ('fastica', None),
 ])
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_ica_cov(method, cov, tmpdir, short_raw_epochs):
     """Test ICA with cov."""
     _skip_check_picard(method)
@@ -929,6 +962,8 @@ def test_ica_cov(method, cov, tmpdir, short_raw_epochs):
 @pytest.mark.parametrize("method", ("fastica", "picard", "infomax"))
 @pytest.mark.parametrize("idx", (None, -1, slice(2), [0, 1]))
 @pytest.mark.parametrize("ch_name", (None, 'MEG 1531'))
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_detect_artifacts_replacement_of_run_ica(method, idx, ch_name):
     """Test replacement workflow for run_ica() function."""
     _skip_check_picard(method)
@@ -942,6 +977,8 @@ def test_detect_artifacts_replacement_of_run_ica(method, idx, ch_name):
 
 @requires_sklearn
 @pytest.mark.parametrize("method", ["fastica", "picard"])
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_ica_reject_buffer(method):
     """Test ICA data raw buffer rejection."""
     _skip_check_picard(method)
@@ -962,6 +999,8 @@ def test_ica_reject_buffer(method):
 
 @requires_sklearn
 @pytest.mark.parametrize("method", ["fastica", "picard"])
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_ica_twice(method):
     """Test running ICA twice."""
     _skip_check_picard(method)
@@ -987,6 +1026,8 @@ def test_ica_twice(method):
 
 @requires_sklearn
 @pytest.mark.parametrize("method", ["fastica", "picard", "infomax"])
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_fit_params(method, tmpdir):
     """Test fit_params for ICA."""
     _skip_check_picard(method)
@@ -1025,6 +1066,8 @@ def test_fit_params(method, tmpdir):
 @requires_sklearn
 @pytest.mark.parametrize("method", ["fastica", "picard"])
 @pytest.mark.parametrize("allow_ref_meg", [True, False])
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_bad_channels(method, allow_ref_meg):
     """Test exception when unsupported channels are used."""
     _skip_check_picard(method)
@@ -1069,6 +1112,8 @@ def test_bad_channels(method, allow_ref_meg):
 
 @requires_sklearn
 @pytest.mark.parametrize("method", ["fastica", "picard"])
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_eog_channel(method):
     """Test that EOG channel is included when performing ICA."""
     _skip_check_picard(method)
@@ -1101,6 +1146,8 @@ def test_eog_channel(method):
 
 @requires_sklearn
 @pytest.mark.parametrize("method", ["fastica", "picard"])
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_n_components_none(method, tmpdir):
     """Test n_components=None."""
     _skip_check_picard(method)
@@ -1285,6 +1332,8 @@ def test_ica_eeg(fname, grade):
 
 
 @testing.requires_testing_data
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_read_ica_eeglab():
     """Test read_ica_eeglab function."""
     fname = op.join(test_base_dir, "EEGLAB", "test_raw.set")
@@ -1317,6 +1366,8 @@ def test_read_ica_eeglab():
 
 
 @testing.requires_testing_data
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_read_ica_eeglab_mismatch(tmpdir):
     """Test read_ica_eeglab function when there is a mismatch."""
     fname_orig = op.join(test_base_dir, "EEGLAB", "test_raw.set")
diff --git a/mne/viz/tests/test_ica.py b/mne/viz/tests/test_ica.py
--- a/mne/viz/tests/test_ica.py
+++ b/mne/viz/tests/test_ica.py
@@ -54,6 +54,8 @@ def _get_epochs():
 
 
 @requires_sklearn
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_plot_ica_components():
     """Test plotting of ICA solutions."""
     res = 8
@@ -207,6 +209,8 @@ def test_plot_ica_properties():
 
 
 @requires_sklearn
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_plot_ica_sources():
     """Test plotting of ICA panel."""
     raw = read_raw_fif(raw_fname).crop(0, 1).load_data()
@@ -295,6 +299,8 @@ def test_plot_ica_sources():
 
 @pytest.mark.slowtest
 @requires_sklearn
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_plot_ica_overlay():
     """Test plotting of ICA cleaning."""
     raw = _get_raw(preload=True)
@@ -335,6 +341,8 @@ def _get_geometry(fig):
 
 
 @requires_sklearn
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_plot_ica_scores():
     """Test plotting of ICA scores."""
     raw = _get_raw()
@@ -377,6 +385,8 @@ def test_plot_ica_scores():
 
 
 @requires_sklearn
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_plot_instance_components():
     """Test plotting of components as instances of raw and epochs."""
     raw = _get_raw()
diff --git a/mne/viz/tests/test_topomap.py b/mne/viz/tests/test_topomap.py
--- a/mne/viz/tests/test_topomap.py
+++ b/mne/viz/tests/test_topomap.py
@@ -578,6 +578,8 @@ def test_plot_topomap_nirs_overlap(fnirs_epochs):
 
 
 @requires_sklearn
+@pytest.mark.filterwarnings('ignore:.*max_iter.*will be changed.*:'
+                            'DeprecationWarning')
 def test_plot_topomap_nirs_ica(fnirs_epochs):
     """Test plotting nirs ica topomap."""
     from mne.preprocessing import ICA