@@ -130,14 +130,27 @@ def _estimate_rank_from_s(s, tol="auto", tol_kind="absolute"):
130130
131131
132132def _estimate_rank_raw (
133- raw , picks = None , tol = 1e-4 , scalings = "norm" , with_ref_meg = False , tol_kind = "absolute"
133+ raw ,
134+ picks = None ,
135+ tol = 1e-4 ,
136+ scalings = "norm" ,
137+ with_ref_meg = False ,
138+ tol_kind = "absolute" ,
139+ on_few_samples = "warn" ,
134140):
135141 """Aid the transition away from raw.estimate_rank."""
136142 if picks is None :
137143 picks = _picks_to_idx (raw .info , picks , with_ref_meg = with_ref_meg )
138144 # conveniency wrapper to expose the expert "tol" option + scalings options
139145 return _estimate_rank_meeg_signals (
140- raw [picks ][0 ], pick_info (raw .info , picks ), scalings , tol , False , tol_kind
146+ raw [picks ][0 ],
147+ pick_info (raw .info , picks ),
148+ scalings ,
149+ tol ,
150+ False ,
151+ tol_kind ,
152+ log_ch_type = None ,
153+ on_few_samples = on_few_samples ,
141154 )
142155
143156
@@ -150,6 +163,7 @@ def _estimate_rank_meeg_signals(
150163 return_singular = False ,
151164 tol_kind = "absolute" ,
152165 log_ch_type = None ,
166+ on_few_samples = "warn" ,
153167):
154168 """Estimate rank for M/EEG data.
155169
@@ -173,6 +187,10 @@ def _estimate_rank_meeg_signals(
173187 to determine the rank.
174188 tol_kind : str
175189 Tolerance kind. See ``estimate_rank``.
190+ on_few_samples : str
191+ Can be 'warn' (default), 'ignore', or 'raise' to control behavior when
192+ there are fewer samples than channels, which can lead to inaccurate rank
193+ estimates.
176194
177195 Returns
178196 -------
@@ -183,11 +201,14 @@ def _estimate_rank_meeg_signals(
183201 thresholded to determine the rank are also returned.
184202 """
185203 picks_list = _picks_by_type (info )
186- if data .shape [1 ] < data .shape [0 ]:
187- ValueError (
188- "You've got fewer samples than channels, your "
189- "rank estimate might be inaccurate."
204+ assert data .ndim == 2 , data .shape
205+ n_channels , n_samples = data .shape
206+ if n_samples < n_channels :
207+ msg = (
208+ f"Too few samples ({ n_samples = } is less than { n_channels = } ), "
209+ "rank estimate may be unreliable"
190210 )
211+ _on_missing (on_few_samples , msg , "on_few_samples" )
191212 with _scaled_array (data , picks_list , scalings ):
192213 out = estimate_rank (
193214 data ,
@@ -214,6 +235,7 @@ def _estimate_rank_meeg_cov(
214235 return_singular = False ,
215236 * ,
216237 log_ch_type = None ,
238+ on_few_samples = "warn" ,
217239 verbose = None ,
218240):
219241 """Estimate rank of M/EEG covariance data, given the covariance.
@@ -236,6 +258,10 @@ def _estimate_rank_meeg_cov(
236258 return_singular : bool
237259 If True, also return the singular values that were used
238260 to determine the rank.
261+ on_few_samples : str
262+ Can be 'warn' (default), 'ignore', or 'raise' to control behavior when
263+ there are fewer samples than channels, which can lead to inaccurate rank
264+ estimates.
239265
240266 Returns
241267 -------
@@ -249,10 +275,11 @@ def _estimate_rank_meeg_cov(
249275 scalings = _handle_default ("scalings_cov_rank" , scalings )
250276 _apply_scaling_cov (data , picks_list , scalings )
251277 if data .shape [1 ] < data .shape [0 ]:
252- ValueError (
278+ msg = (
253279 "You've got fewer samples than channels, your "
254280 "rank estimate might be inaccurate."
255281 )
282+ _on_missing (on_few_samples , msg , "on_few_samples" )
256283 out = estimate_rank (data , tol = tol , norm = False , return_singular = return_singular )
257284 rank = out [0 ] if isinstance (out , tuple ) else out
258285 if log_ch_type is None :
@@ -325,7 +352,7 @@ def _compute_rank_int(inst, *args, **kwargs):
325352 # XXX eventually we should unify how channel types are handled
326353 # so that we don't need to do this, or we do it everywhere.
327354 # Using pca=True in compute_whitener might help.
328- return sum (compute_rank (inst , * args , ** kwargs ).values ())
355+ return sum (compute_rank (inst , * args , on_few_samples = "ignore" , ** kwargs ).values ())
329356
330357
331358@verbose
@@ -335,9 +362,11 @@ def compute_rank(
335362 scalings = None ,
336363 info = None ,
337364 tol = "auto" ,
365+ * ,
338366 proj = True ,
339367 tol_kind = "absolute" ,
340368 on_rank_mismatch = "ignore" ,
369+ on_few_samples = None ,
341370 verbose = None ,
342371):
343372 """Compute the rank of data or noise covariance.
@@ -363,6 +392,13 @@ def compute_rank(
363392 considered when ``rank=None`` or ``rank='info'``.
364393 %(tol_kind_rank)s
365394 %(on_rank_mismatch)s
395+ on_few_samples : str | None
396+ Can be 'warn', 'ignore', or 'raise' to control behavior when
397+ there are fewer samples than channels, which can lead to inaccurate rank
398+ estimates. None (default) means "ignore" if ``inst`` is a
399+ :class:`mne.Covariance` or ``rank in ("info", "full")``, and "warn" otherwise.
400+
401+ .. versionadded:: 1.11
366402 %(verbose)s
367403
368404 Returns
@@ -384,6 +420,7 @@ def compute_rank(
384420 proj = proj ,
385421 tol_kind = tol_kind ,
386422 on_rank_mismatch = on_rank_mismatch ,
423+ on_few_samples = on_few_samples ,
387424 )
388425
389426
@@ -398,6 +435,7 @@ def _compute_rank(
398435 proj = True ,
399436 tol_kind = "absolute" ,
400437 on_rank_mismatch = "ignore" ,
438+ on_few_samples = None ,
401439 log_ch_type = None ,
402440 verbose = None ,
403441):
@@ -441,6 +479,12 @@ def _compute_rank(
441479 if rank is None :
442480 rank = dict ()
443481
482+ if on_few_samples is None :
483+ if inst_type != "covariance" and rank_type == "estimated" :
484+ on_few_samples = "warn"
485+ else :
486+ on_few_samples = "ignore"
487+
444488 simple_info = _simplify_info (info )
445489 picks_list = _picks_by_type (info , meg_combined = True , ref_meg = False , exclude = "bads" )
446490 for ch_type , picks in picks_list :
@@ -503,6 +547,7 @@ def _compute_rank(
503547 False ,
504548 tol_kind ,
505549 log_ch_type = log_ch_type ,
550+ on_few_samples = on_few_samples ,
506551 )
507552 else :
508553 assert isinstance (inst , Covariance )
@@ -520,6 +565,7 @@ def _compute_rank(
520565 tol ,
521566 return_singular = True ,
522567 log_ch_type = log_ch_type ,
568+ on_few_samples = on_few_samples ,
523569 verbose = est_verbose ,
524570 )
525571 if ch_type in rank :
0 commit comments