Skip to content

Add example for Python/R interoperability using mass univariate t-test#13729

Open
aman-coder03 wants to merge 17 commits intomne-tools:mainfrom
aman-coder03:doc-r-interop
Open

Add example for Python/R interoperability using mass univariate t-test#13729
aman-coder03 wants to merge 17 commits intomne-tools:mainfrom
aman-coder03:doc-r-interop

Conversation

@aman-coder03
Copy link
Contributor

Reference issue (if any)

closes #13721

What does this implement/fix?

adds a tutorial example that runs a 2 sample mass univariate t-test on Epochs data,.., first in python using scipy.stats then the same test in R via rpy2 showing that both give identical results. The goal is to show the users how to get data into R, run stats there, and then bring the results back into Python, as starting point for more complex R based workflows

Additional information

follows the approach suggested by @larsoner in the issue ...keeping it simple and focused on one clear use case rather than trying to build general purpose utility

@aman-coder03
Copy link
Contributor Author

windows 3.13 pip pre failure is unrelated to this PR, it's a missing nightly wheel for statsmodels>=0.15.0.dev697 on the scientific python nightly wheels index, which affects other PRs too

@larsoner
Copy link
Member

larsoner commented Mar 9, 2026

Being fixed in #13728

@larsoner
Copy link
Member

CircleCI failure is real

I think you need to add rpy2 to the doc dependency group in pyproject.toml

@aman-coder03
Copy link
Contributor Author

@larsoner the failure is because rpy2 requires R to be installed at build time, which isn't available in the CI environment. Would wrapping the rpy2 imports in a try/except to skip the R section gracefully when R isn't present be the right approach here?

@larsoner
Copy link
Member

Can you try updating the CI to have R installed? Would be doable with an apt command

@aman-coder03
Copy link
Contributor Author

towncrier fragments errors about "Famous Raj Bhat" are pre existing and unrelated to this PR.. @larsoner

@Famous077
Copy link
Contributor

towncrier fragments errors about "Famous Raj Bhat" are pre existing and unrelated to this PR.. @larsoner

Hi @aman-coder03 , Can you specify where you are getting an error. Would Love to co-ordinate with you to fix the issue.

@larsoner
Copy link
Member

Hi @aman-coder03 , Can you specify where you are getting an error. Would Love to co-ordinate with you to fix the issue.

No need, I fixed it in main and then merged main into this branch

@aman-coder03
Copy link
Contributor Author

thanks @larsoner !!

@@ -1,5 +1,7 @@
#!/bin/bash -ef

sudo apt-get install -y r-base libtirpc-dev
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't the best place, tools/circleci_bash_env.sh would make more sense

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!!

Copy link
Member

@larsoner larsoner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

@drammock drammock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the long run, I think it would be better / more interesting to showcase an example of something where R/rpy2 actually makes it easier to achieve the desired result (i.e., demonstrate something that is hard to do in Python but easy in R). But for now, however, let's stick with the t-test (subject to the modifications I propose in the below comments)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

file is in the wrong folder. This should be in stats folder, not preprocessing

@@ -0,0 +1 @@
Add example showing how to run a mass-univariate t-test in both Python and R using ``rpy2``, demonstrating Python–R interoperability with :class:`~mne.Epochs` data by `Aman Srivastava`_. (:gh:`13729`) No newline at end of file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Add example showing how to run a mass-univariate t-test in both Python and R using ``rpy2``, demonstrating Python–R interoperability with :class:`~mne.Epochs` data by `Aman Srivastava`_. (:gh:`13729`)
Add example showing how to interoperate with R using ``rpy2``, by `Aman Srivastava`_.

PR number will get added automatically, and the details (epochs, t-test) are not really the point.

Comment on lines +64 to +65
# We average over channels and time to get one value per epoch, then run
# a 2-sample t-test comparing the two conditions.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is... not something anyone would ever do in practice. What we show can be much simpler than what most real neuroscience studies do, but it should at least be plausible. Probably the shortest path to something plausible would be:

  1. pick a few sensors as an "ROI" (ones that should differ between auditory L vs R conditions)
  2. pick a typical ERP time window
  3. average over just those channels and those times
  4. do the t-test (1 observation per epoch)

Probably worthwhile to do a plot_compare_evokeds or similar, as a way to motivate what the stats are testing.

Comment on lines +80 to +88
with localconverter(default_converter + numpy2ri.converter):
r_left = ro.FloatVector(epochs_left)
r_right = ro.FloatVector(epochs_right)

r_ttest = ro.r["t.test"]
result = r_ttest(r_left, r_right, **{"var.equal": True})

t_r = float(result.rx2("statistic")[0])
p_r = float(result.rx2("p.value")[0])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these steps are the important "new learning" from this, so there should be more explanation/comments here. E.g., things like

  • rpy2.robjects (imported here as ro) has an attribute r that provides access to the R namespace, through a dictionary-like interface
  • Unlike Python, R functions can have parameter names that contain a ., so you may need to pass function arguments as a dict and unpack them with **
  • what is FloatVector and why do we need it
  • what is the rx2 attribute of the result, and why do we need the [0] after it

@aman-coder03
Copy link
Contributor Author

moved example to stats, added ROI + evoked plot, and explained the rpy2 API in more detail.

@aman-coder03 aman-coder03 requested a review from drammock March 18, 2026 11:52
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#
# We first plot the evoked responses to motivate the statistical test.
# Auditory left vs right stimuli should differ over lateral temporal sensors.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EEG059 is not really lateral/temporal. Use MEG 1323

# and a typical N1 time window (80–120 ms). This gives one value per epoch,
# which is a plausible neuroscience analysis.

roi_channels = ["EEG 059", "EEG 058", "EEG 057"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
roi_channels = ["EEG 059", "EEG 058", "EEG 057"]
roi_channels = ["MEG 1323"]

.crop(tmin_roi, tmax_roi)
.get_data(picks=roi_channels)
.mean(axis=(1, 2))
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessary copying. something this should be fine:

epochs.crop(tmin, tmax).pick(roi_chs)
epo_left = epochs["auditory/left"].get_data().mean(axis=(1, 2))
epo_right = epochs["auditory/right"].get_data().mean(axis=(1, 2))


mne.viz.plot_compare_evokeds(
{"auditory/left": evoked_left, "auditory/right": evoked_right},
picks="EEG 059",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
picks="EEG 059",
picks="MEG 1323",

.. _r-interop:
==============================================
Mass-univariate t-test: Python and R compared
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make the point of the tutorial clearer in the title

Suggested change
Mass-univariate t-test: Python and R compared
Integrating with R via rpy2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DOC: Document how to do stats in / inter-operate with R

4 participants