How to detect R of ECG based on the subtraction between two channels

Dear All,

I am struggling with a problem regarding ECG data analysis. I need your great help.
I am using Nihon Koden EEG system and import the data as below.'XXX.EEG',preload=True)

The data includes ECG data, recording with X1 and X2 (channel names)
Therefore, real ECG data is the subtraction from X1 to X2.
I would like to find ecg_events (R waves) as below.

ecg_events, _, _ = mne.preprocessing.find_ecg_events(eegdata,event_id = 999,ch_name='X2')

However, this code shows the events based on X2, not subtraction from X1 to X2.
I was wondering if it could be possible to let me know the best code for R detection.

  • MNE-Python version: 0.23.0
  • operating system: macOS Big Sur

I look forward to hearing from you.

Hi @Rokore, I think you should first create a new channel X_ECG based on the subtraction of your X1 from X2 (or the other way around). Then feed that new channel into the function.

As for “R detection”, I need more information to help.

See also:

Thanks, Sappelhoff.

I understood python and the difference from Matlab.
As for a question regarding R detection,
I am struggling with a new problem as below.

I use the following formula.

Some data with a perfect ECG data was analyzed well, but when other data was analyzed, I received the following plot.

While “downward” peaks in one part of ECG were at on 0 times,
“upward” peaks in other part of ECG were at on 0 times.

Is it possible to let me know how to solve this problem?

Many thanks,

To further help you, could you please turn your pipeline into a minimum working example? See below:

… minimal working example MWE to
replicate your problem, using one of the built-in datasets, preferably the
one called sample. If you can’t replicate on a built-in dataset, provide also
a link to a small, anonymized portion of your data that does yield the error.

posting such an example here will help us to figure out what’s wrong. :slight_smile:

two more points:

  1. Please use markdown formatting with your upcoming MWE
  2. The docs you are linking to are outdated (0.18), assuming that you use an up to date MNE-Python (which I highly recommend), you should be using the docs of version 0.23: MNE — MNE 0.23.0 documentation
1 Like

First, I am sorry that I don’t understand how to attach sample data here.

My pipeline was as below:

import mne
import matplotlib.pyplot as plt
import numpy as np

xxx = "ErrorSample.EEG",preload=True)  # xxx is a name of raw data.
eegdata.filter(1, 40., fir_design='firwin') 
eegdata.set_channel_types(mapping={'X1': 'ecg'})
eegdata.set_channel_types(mapping={'X2': 'ecg'})    

ecg.plot(start=20, duration=5)


picks = mne.pick_types(, meg=False, stim=False, eog=False,
                   include=['ECG'], exclude='bads')
ecg_events, _, _ = mne.preprocessing.find_ecg_events(ecg,event_id = 999,ch_name='ECG')
epochs = mne.Epochs(ecg, ecg_events, 999, -0.1, 0.1, picks=picks)
data = epochs.get_data()
plt.plot(1e3 * epochs.times, np.squeeze(data).T) 


Even if I analyzed the data based on X2 only as below:

picks = mne.pick_types(, meg=False, stim=False, eog=False,
                   include=['X2'], exclude='bads')

the plot was as below:


  • MNE-Python version: 0.23.0
  • operating system: macOS Big Sur
1 Like

Hi I made a link to sample data. Can you download it?
(although I will omit this link after a problem is solved)

Thanks for sharing the example @Rokore - note that I made a few edits that were needed to actually make your example work: Next time, please also include the imports that you need, double check that the formatting renders the codeblocks correctly, and try copy-pasting your example yourself to see if it runs “as is” → that can help other people who are trying to help you :slight_smile:

Having that said: I replicated your problem even on the current MNE-Python from the main branch.

Looking at your X1 and X2 channels I see that they are already properly referenced, one can clearly see the ECG signal → are you sure that you need to re-reference them?

However even by just using X1 and X2, I get the “misaligned” ECG epochs. I have no idea what’s happening here. Let’s ping some more people who may know about this: @cbrnr

I agree that subtracting X1 and X2 shouldn’t be necessary, both channels seem to contain different ECG derivations already. I’d use X2 because the signal looks nicer. Here’s a slightly more condensed version of the example:

import mne
import matplotlib.pyplot as plt
import numpy as np

raw ="ErrorSample.EEG", preload=True)
raw.set_channel_types(mapping={'X1': 'ecg', 'X2': 'ecg'})

ecg = raw.pick_types(ecg=True)
ecg.plot(start=20, duration=5)

ecg_events, _, _ = mne.preprocessing.find_ecg_events(ecg, ch_name='X2')
epochs = mne.Epochs(ecg, ecg_events, 999, -0.1, 0.1, picks='X2')

This still yields four epochs that are shifted, but these might be caused by the peak detector getting these detections slightly wrong. I’d try to use a different detector to verify or to see if you get better results.

Coincidentally, I am about to release a Python package which implements a fast version of the Pan-Tompkins algorithm, but until this is available (should be next week) you could try GitHub - berndporr/py-ecg-detectors: Popular ECG R peak detectors written in python.

1 Like

Dear Sappelhoff and cbrnr

Thanks a lot.
Sample data includes only small part of all data, and many epochs, not only four, are shifted.
In addition, the shift is not at random and is based on downward peak, suggesting inverse detection of QRS, not simply wrong detection. I guess there should be solution to prevent the inverse.
Your recommended package looks good. I will try it. Thanks.

Dear cbrnr,

I have applied “detectors.pan_tompkins_detector(unfiltered_ecg)” in GitHub to my sample data, but unfortunately, R detection was miserable as below. Other detectors returned similar results.

I hope your new package is better.
Or I need another solution to prevent the inverse detection.


You could also try the various methods offered by NeuroKit2, see Functions — NeuroKit 0.1.2 documentation

@Rokore the detector in my package works much better, here is the plot of epochs around detected peaks:

As mentioned previously, X2 is a much better signal for peak detection. I’ll post a link to the package repo once we’ve released it this week.

1 Like

I just analyzed the ECG data using NeuroKit2 and it gives nice results:

# %%
from pathlib import Path
import neurokit2 as nk
import mne

base_dir = Path('~/Development/Support/mne-python/ECG').expanduser()
raw = / 'ErrorSample.EEG', preload=True)
raw.set_channel_types(mapping={'X1': 'ecg', 'X2': 'ecg'})

ecg_signal = raw.pick('X2').get_data().flatten()

signals, ecg_info = nk.ecg_process(

fig = nk.ecg_plot(

fig.set_size_inches((10, 5))

Using their implementation of the Pan-Tompkins (1985) algorithm (by passing method='pantompkins1985' to ecg_process()), the results don’t look as great, but still much better than what you got with MNE:

Edit: I think the R peak detection is actually quite a bit off in the second figure.

1 Like

could we improve MNE here in some way?


Maybe add a new parameter to mne.preprocessing.find_ecg_events() to select alternative R peak detectors? Do we use R peak detection anywhere else?

Yes that would be neat. Support for NeuroKit2 would be highly appreciated, also for EOG peak detection:

I am now trying recommended ways one by one. As I am new to python, it will take much time. SORRY. I will reply to your great help soon.

1 Like

I’ve just released SleepECG, which contains the R peak detector I was talking about. You can install it simply with pip install sleepecg and see if you get better results on your full data sets.

1 Like

Hi, may I ask why you said

real ECG data is the subtraction from X1 to X2.

because I also use Nihon Koden EEG system and there are two channels of ECG within the data. Sometimes, clinically, I saw clinicians use EKG2-EKG1.

Hi, a technician said EKG 2(X 2)-EKG (X1) was more real, although data from one channel was acceptable. However, considering several posted opinions, I have changed my mind. In this context (not clinical), easier R detection might be more important.