Extract and Visualize Individual Heartbeats

This example can be referenced by citing the package.

This example shows how to use NeuroKit to extract and visualize the QRS complexes (individual heartbeats) from an electrocardiogram (ECG).

[17]:
# Load NeuroKit and other useful packages
import neurokit2 as nk
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
[18]:
plt.rcParams['figure.figsize'] = [15, 9]  # Bigger images

Extract the cleaned ECG signal

In this example, we will use a simulated ECG signal. However, you can use any of your signal (for instance, extracted from the dataframe using the read_acqknowledge().

[19]:
# Simulate 30 seconds of ECG Signal (recorded at 250 samples / second)
ecg_signal = nk.ecg_simulate(duration=30, sampling_rate=250)

Once you have a raw ECG signal in the shape of a vector (i.e., a one-dimensional array), or a list, you can use ecg_process() to process it.

Note: It is critical that you specify the correct sampling rate of your signal throughout many processing functions, as this allows NeuroKit to have a time reference.

[20]:
# Automatically process the (raw) ECG signal
signals, info = nk.ecg_process(ecg_signal, sampling_rate=250)

This function outputs two elements, a dataframe containing the different signals (raw, cleaned, etc.) and a dictionary containing various additional information (peaks location, …).

Extract R-peaks location

The processing function does two important things for our purpose: 1) it cleans the signal and 2) it detects the location of the R-peaks. Let’s extract these from the output.

[21]:
# Extract clean ECG and R-peaks location
rpeaks = info["ECG_R_Peaks"]
cleaned_ecg = signals["ECG_Clean"]

Great. We can visualize the R-peaks location in the signal to make sure it got detected correctly by marking their location in the signal.

[22]:
# Visualize R-peaks in ECG signal
plot = nk.events_plot(rpeaks, cleaned_ecg)
../_images/examples_heartbeats_15_0.png

Once that we know where the R-peaks are located, we can create windows of signal around them (of a length of for instance 1 second, ranging from 400 ms before the R-peak), which we can refer to as epochs.

Segment the signal around the heart beats

You can now epoch all these individual heart beats, synchronized by their R peaks with the ecg_segment() function.

[23]:
# Plotting all the heart beats
epochs = nk.ecg_segment(cleaned_ecg, rpeaks=None, sampling_rate=250, show=True)
../_images/examples_heartbeats_19_0.png

This create a dictionary of dataframes for each ‘epoch’ (in this case, each heart beat).

Advanced Plotting

This section is written for a more advanced purpose of plotting and visualizing all the heartbeats segments. The code below uses packages other than NeuroKit2 to manually set the colour gradient of the signals and to create a more interactive experience for the user - by hovering your cursor over each signal, an annotation of the signal corresponding to the heart beat index is shown.

Custom colors and legend

Here, we define a function to create the epochs. It takes in cleaned as the cleaned signal dataframe, and peaks as the array of R-peaks locations.

[24]:
%matplotlib notebook
plt.rcParams['figure.figsize'] = [10, 6]  # resize

# Define a function to create epochs
def plot_heartbeats(cleaned, peaks, sampling_rate=None):
    heartbeats = nk.epochs_create(cleaned, events=peaks, epochs_start=-0.3, epochs_end=0.4, sampling_rate=sampling_rate)
    heartbeats = nk.epochs_to_df(heartbeats)
    return heartbeats
heartbeats = plot_heartbeats(cleaned_ecg, peaks=rpeaks, sampling_rate=250)
heartbeats.head()
[24]:
Signal Index Label Time
0 -0.193262 139 1 -0.300000
1 -0.187513 140 1 -0.295977
2 -0.181679 141 1 -0.291954
3 -0.175692 142 1 -0.287931
4 -0.169474 143 1 -0.283908

We then pivot the dataframe so that each column corresponds to the signal values of one channel, or Label.

[25]:
heartbeats_pivoted = heartbeats.pivot(index='Time', columns='Label', values='Signal')
heartbeats_pivoted.head()
[25]:
Label 1 10 11 12 13 14 15 16 17 18 ... 31 32 33 34 4 5 6 7 8 9
Time
-0.300000 -0.193262 -0.130034 -0.136912 -0.139149 -0.130810 -0.129424 -0.140368 -0.141949 -0.137200 -0.129263 ... -0.134434 -0.137504 -0.130364 -0.155541 -0.121164 -0.148815 -0.131790 -0.138593 -0.134619 -0.137870
-0.295977 -0.187513 -0.129303 -0.135751 -0.138231 -0.130387 -0.128518 -0.138962 -0.140974 -0.136386 -0.128326 ... -0.133507 -0.136182 -0.128863 -0.154694 -0.120614 -0.147637 -0.130774 -0.137616 -0.133792 -0.137158
-0.291954 -0.181679 -0.128410 -0.134396 -0.137167 -0.129875 -0.127490 -0.137368 -0.139873 -0.135463 -0.127291 ... -0.132467 -0.134675 -0.127122 -0.153670 -0.119863 -0.146230 -0.129614 -0.136513 -0.132834 -0.136297
-0.287931 -0.175692 -0.127313 -0.132779 -0.135925 -0.129223 -0.126319 -0.135557 -0.138622 -0.134400 -0.126087 ... -0.131265 -0.132927 -0.125104 -0.152414 -0.118853 -0.144558 -0.128243 -0.135241 -0.131704 -0.135261
-0.283908 -0.169474 -0.125951 -0.130811 -0.134471 -0.128376 -0.124978 -0.133487 -0.137182 -0.133150 -0.124632 ... -0.129833 -0.130870 -0.122765 -0.150849 -0.117519 -0.142586 -0.126583 -0.133737 -0.130349 -0.134013

5 rows × 34 columns

[26]:
# Import dependencies
import matplotlib.pyplot as plt

# Prepare figure
fig, ax = plt.subplots()
plt.close(fig)
ax.set_title("Individual Heart Beats")
ax.set_xlabel("Time (seconds)")

# Aesthetics
labels = list(heartbeats_pivoted)
labels = ['Channel ' + x for x in labels] # Set labels for each signal
cmap = iter(plt.cm.YlOrRd(np.linspace(0,1, int(heartbeats["Label"].nunique())))) # Get color map
lines = [] # Create empty list to contain the plot of each signal

for i, x, color in zip(labels, heartbeats_pivoted, cmap):
    line, = ax.plot(heartbeats_pivoted[x], label='%s' % i, color=color)
    lines.append(line)

# Show figure
fig

Interactivity

This section of the code incorporates the aesthetics and interactivity of the plot produced. Unfortunately, the interactivity is not active in this example but it should work in your console! As you hover your cursor over each signal, annotation of the channel that produced it is shown. The below figure that you see is a standstill image.

Note: you need to install the ``mplcursors`` package for the interactive part (``pip install mplcursors``)

[27]:
# Import packages
import ipywidgets as widgets
from ipywidgets import interact, interact_manual

import mplcursors
[28]:
# Obtain hover cursor
mplcursors.cursor(lines, hover=True, highlight=True).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label()))
# Return figure
fig