Git Product home page Git Product logo

hrv-analysis's Introduction

Heart Rate Variability analysis

PyPI version Build Status codecov License: GPL v3 Downloads

hrvanalysis is a Python module for Heart Rate Variability analysis of RR-intervals built on top of SciPy, AstroPy, Nolds and NumPy and distributed under the GPLv3 license.

The development of this library started in July 2018 as part of Aura Healthcare project, in OCTO Technology R&D team and is maintained by Robin Champseix.

alt text

Full documentation : https://aura-healthcare.github.io/hrv-analysis

Website : https://www.aura.healthcare

Github : https://github.com/Aura-healthcare

Version : 1.0.4

Installation / Prerequisites

User installation

The easiest way to install hrv-analysis is using pip :

$ pip install hrv-analysis

you can also clone the repository:

$ git clone https://github.com/Aura-healthcare/hrv-analysis.git
$ python setup.py install

Dependencies

hrvanalysis requires the following:

  • Python (>= 3.5)
  • astropy >= 3.0.4
  • future >= 0.16.0
  • nolds >= 0.4.1
  • numpy >= 1.15.1
  • scipy >= 1.1.0

Note: The package can be used with all Python versions from 3.5 to latest version (currently Python 3.9).

Getting started

Outliers and ectopic beats filtering methods

This package provides methods to remove outliers and ectopic beats from signal for further analysis. Those methods are useful to get Normal to Normal Interval (NN-intervals) from RR-intervals. Please use this methods carefully as they might have a huge impact on features calculation.

from hrvanalysis import remove_outliers, remove_ectopic_beats, interpolate_nan_values

# rr_intervals_list contains integer values of RR-interval
rr_intervals_list = [1000, 1050, 1020, 1080, ..., 1100, 1110, 1060]

# This remove outliers from signal
rr_intervals_without_outliers = remove_outliers(rr_intervals=rr_intervals_list,  
                                                low_rri=300, high_rri=2000)
# This replace outliers nan values with linear interpolation
interpolated_rr_intervals = interpolate_nan_values(rr_intervals=rr_intervals_without_outliers,
                                                   interpolation_method="linear")

# This remove ectopic beats from signal
nn_intervals_list = remove_ectopic_beats(rr_intervals=interpolated_rr_intervals, method="malik")
# This replace ectopic beats nan values with linear interpolation
interpolated_nn_intervals = interpolate_nan_values(rr_intervals=nn_intervals_list)

You can find how to use the following methods, references and more details in the documentation:

  • remove_outliers
  • remove_ectopic_beats

Features calculation

There are 4 types of features you can get from NN-intervals:

Time domain features : Mean_NNI, SDNN, SDSD, NN50, pNN50, NN20, pNN20, RMSSD, Median_NN, Range_NN, CVSD, CV_NNI, Mean_HR, Max_HR, Min_HR, STD_HR

Geometrical domain features : Triangular_index, TINN

Frequency domain features : LF, HF, VLF, LH/HF ratio, LFnu, HFnu, Total_Power

Non Linear domain features : CSI, CVI, Modified_CSI, SD1, SD2, SD1/SD2 ratio, SampEn

As an exemple, what you can compute to get Time domain analysis is :

from hrvanalysis import get_time_domain_features

 # nn_intervals_list contains integer values of NN-interval
nn_intervals_list = [1000, 1050, 1020, 1080, ..., 1100, 1110, 1060]

time_domain_features = get_time_domain_features(nn_intervals_list)

>>> time_domain_features
{'mean_nni': 718.248,
 'sdnn': 43.113,
 'sdsd': 19.519,
 'nni_50': 24,
 'pnni_50': 2.4,
 'nni_20': 225,
 'pnni_20': 22.5,
 'rmssd': 19.519,
 'median_nni': 722.5,
 'range_nni': 249,
 'cvsd': 0.0272,
 'cvnni': 0.060,
 'mean_hr': 83.847,
 'max_hr': 101.694,
 'min_hr': 71.513,
 'std_hr': 5.196}

You can find how to use the following methods, references and details about each feature in the documentation:

  • get_time_domain_features
  • get_geometrical_features
  • get_frequency_domain_features
  • get_csi_cvi_features
  • get_poincare_plot_features
  • get_sampen

Plot functions

There are several plot functions that allow you to see, for example, the Power Spectral Density (PSD) for frequency domain features or Poincaré Plot for non linear domain features:

from hrvanalysis import plot_psd

# nn_intervals_list contains integer values of NN-interval
nn_intervals_list = [1000, 1050, 1020, 1080, ..., 1100, 1110, 1060]

plot_psd(nn_intervals_list, method="welch")
plot_psd(nn_intervals_list, method="lomb")

alt text

from hrvanalysis import plot_poincare

# nn_intervals_list contains integer values of NN-interval
nn_intervals_list = [1000, 1050, 1020, 1080, ..., 1100, 1110, 1060]

plot_poincare(nn_intervals_list)
plot_poincare(nn_intervals_list, plot_sd_features=True)

alt text

You can find how to use methods and details in the documentation:

  • plot_distrib
  • plot_timeseries
  • plot_psd
  • plot_poincare

Here is a high level view of the distinct building blocks of the package:

alt text

References

Here are the main references used to compute the set of features and for signal processing methods:

Heart rate variability - Standards of measurement, physiological interpretation, and clinical use, Task Force of The European Society of Cardiology and The North American Society of Pacing and Electrophysiology, 1996

Signal Processing Methods for Heart Rate Variability - Gari D. Clifford, 2002

Physiological time-series analysis using approximate entropy and sample entropy, Joshua S. Richman, J. Randall Moorman - 2000

Using Lorenz plot and Cardiac Sympathetic Index of heart rate variability for detecting seizures for patients with epilepsy, Jesper Jeppesen et al, 2014

Authors

Robin Champseix - (https://github.com/robinchampseix)

License

This project is licensed under the GNU GENERAL PUBLIC License - see the LICENSE.md file for details

How to contribute

Please refer to How To Contribute

Acknowledgments

I hereby thank Laurent Ribière and Clément Le Couedic, my coworkers who gave me time to Open Source this library. I also thank Fabien Arcellier for his advices on to how build a library in PyPi.

hrv-analysis's People

Contributors

cedricgilon avatar clecoued avatar flennic avatar marielledado avatar minsooyeo avatar robinchampseix avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hrv-analysis's Issues

Suggestion - DFA α1 analysis ?

Since HRV related dfa a1 algorithm would be nice. Used frequently in endurance sports after discovery of it's correlation with LT1, LT2 thresholds

plot_distrib fails

When I run plot.plot_distrib(rr), where rr is a float list or a numpy array, I get the following error:

Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "C:\Users\berief\Python\Python37\lib\site-packages\hrvanalysis\plot.py", line 78, in plot_distrib
    plt.hist(nn_intervals, bins=range(min_nn_i - 10, max_nn_i + 10, bin_length))
TypeError: 'float' object cannot be interpreted as an integer

Possible fix:
Just cast max_nn_i and max_nn_i to int or use np.arange.

is_outlier - returns true when it's not an outlier

Hey,
Was just perusing your code, looking to learn more about HRV data cleanup, classifying and cleaning ectopic beats etc...
I stared at this code for like 15 minutes scratching my head until I realized...
Did you misname your is_outlier method, or do I not understand your code?
It looks like, even from the description that is_outlier returns true on a valid RR-interval... i.e. not an outlier?
In remove_ectopic_beats you are testing is_outlier, then only inmcrementing outlier count if its false.

Anyways, if I was correct here, you might want to clean it up for readability.
Thanks, and thanks for sharing your code!!

def is_outlier(rr_interval: int, next_rr_interval: float, method: str = "malik",

Unit of nn_intervals

Hi,

After encountering an UserWarning form spicy.signal.spectral (UserWarning: nperseg = 256 is greater than input length = 5, using nperseg = 5), I check the code and it seems that the function get_frequency_domain_features only takes nn_intervals in ms.

Please run the following script to replicate the results:

from hrvanalysis import get_frequency_domain_features
import pyhrv.frequency_domain as fd. # used as a reference

nn_intervals_in_s = np.random.uniform(0.4,1.8, size=(2000,))
print('frequency domain of nn_intervals in seconds')
print(get_frequency_domain_features(nn_intervals_in_s))

nn_intervals_in_ms = nn_intervals_in_s * 1000
print('frequency domain of nn_intervals in miliseconds')
print(get_frequency_domain_features(nn_intervals_in_ms))

print('frequency domain of nn_intervals in miliseconds by pyhrv')
fd.welch_psd(nn_intervals_in_ms)

print('frequency domain of nn_intervals in seconds by pyhrv')
fd.welch_psd(nn_intervals_in_s)

If this is the case, please specify the unit of nn_intervals in the documentation.

Fix Error: Cannot import LombScargle from astropy.stats

Hello,

I encountered an ImportError while using the hrvanalysis package due to an outdated import statement for LombScargle. The LombScargle class has been moved from astropy.stats to astropy.timeseries in newer versions of astropy.

Error message: ImportError: cannot import name 'LombScargle' from 'astropy.stats' (/usr/local/lib/python3.10/dist-packages/astropy/stats/init.py)

Request to update the import statement in the hrvanalysis codebase from:

"from astropy.stats import LombScargle" to "from astropy.timeseries import LombScargle"

This change resolved the import issues in my environment, and I believe updating this in the package would help maintain compatibility with current and future versions of 'astropy'.

UserWarning from spectral.py when using get_time_domain_features

I am getting the following warning when passing a list of heart rate PPG values to get_time_domain_features.

/Users/mqm0624/miniconda3/envs/mist/lib/python3.8/site-packages/scipy/signal/spectral.py:1961: UserWarning: nperseg = 256 is greater than input length  = 10, using nperseg = 10
  warnings.warn('nperseg = {0:d} is greater than input length '

Calculation of pNN50 wrong?

I'm calculating the pNN50, but it seems like there is a mistake:

import numpy as np
import hrvanalysis

# create fake NNs, all with >60ms diff
NNs = np.arange(1100, 2000, 60)

pNN50 = hrvanalysis.get_time_domain_features(NNs)['pnni_50']
# should be 100%, but is 93%

I also found the reason:

https://github.com/Aura-healthcare/hrvanalysis/blame/2aca66ee65e2bf4867a6badc17322197c196d70d/hrvanalysis/extract_features.py#L109

You take a np.diff(RR) and then divide the sum of >50 by len(RR). However, np.diff(RR) will have one element less than RR.

Edit: I wrote some things in a non-friendly way, please accept my apologies for doing so, I edited them out.

The issue about heart beat interval preprocessing for interpolation

I am on studying heart rate variability with your great packages, I sincerely appreciate your efforts for this package

I have founded a problem on pre-processing in heart rate variability below toy codes

from hrvanalysis import preprocessing as pre

hrv_sample_1 = [700, 800, 10000, 10000, 650, 700, 750, 540]
hrv_sample_2 = [10000, 10000, 800, 700, 800, 900, 10000, 10000, 650, 700]
hrv_sample_3 = [10000, 10000, 800, 700, 800, 900, 10000, 10000, 650, 700, 10000, 10000]

hrv_sample_1 = pre.interpolate_nan_values(pre.remove_outliers(hrv_sample_1))
hrv_sample_2 = pre.interpolate_nan_values(pre.remove_outliers(hrv_sample_2))
hrv_sample_3 = pre.interpolate_nan_values(pre.remove_outliers(hrv_sample_3))

print("hrv sample 1: {}".format(hrv_sample_1))
print("hrv sample 2: {}".format(hrv_sample_2))
print("hrv sample 3: {}".format(hrv_sample_3))

>>> hrv sample 1: [700.0, 800.0, 750.0, 700.0, 650.0, 700.0, 750.0, 540.0]
>>> hrv sample 2: [nan, nan, 800.0, 700.0, 800.0, 900.0, 816.6666666666666, 733.3333333333334, 650.0, 700.0]
>>> hrv sample 3: [nan, nan, 800.0, 700.0, 800.0, 900.0, 816.6666666666666, 733.3333333333334, 650.0, 700.0, 700.0, 700.0]

you can see this code, 10000 value is abnormal heart beat interval.
a variable "hrv_sample_1" was prepossessed normally, however, hrv_sample_2 and hrv_sample_3 still include nan value if first data is abnormal value

to solve it, i proposes a simple method.

you can see code, variable hrv_sample_3 has abnormal value on end point, it was replaced to previous value.
Similar to this method, i add code in interpolate_nan_values function below

from typing import Tuple
from typing import List
import pandas as pd
import numpy as np

# Static name for methods params
MALIK_RULE = "malik"
KARLSSON_RULE = "karlsson"
KAMATH_RULE = "kamath"
ACAR_RULE = "acar"
CUSTOM_RULE = "custom"


def interpolate_nan_values(rr_intervals: list,
                           interpolation_method: str = "linear",
                           limit_area: str = None,
                           limit_direction: str = "forward",
                           limit=None, ) -> list:
    """
    Function that interpolate Nan values with linear interpolation
    Parameters
    ---------
    rr_intervals : list
        RrIntervals list.
    interpolation_method : str
        Method used to interpolate Nan values of series.
    limit_area: str
        If limit is specified, consecutive NaNs will be filled with this restriction.
    limit_direction: str
        If limit is specified, consecutive NaNs will be filled in this direction.
    limit: int
        TODO
    Returns
    ---------
    interpolated_rr_intervals : list
        new list with outliers replaced by interpolated values.
    """
    # search first nan data and fill it post value until it is not nan
    if np.isnan(rr_intervals[0]):
        start_idx = 0

        while np.isnan(rr_intervals[start_idx]):
            start_idx += 1

        rr_intervals[0:start_idx] = [rr_intervals[start_idx]] * start_idx
    else:
        pass
    # change rr_intervals to pd series
    series_rr_intervals_cleaned = pd.Series(rr_intervals)
    # Interpolate nan values and convert pandas object to list of values
    interpolated_rr_intervals = series_rr_intervals_cleaned.interpolate(method=interpolation_method,
                                                                        limit=limit,
                                                                        limit_area=limit_area,
                                                                        limit_direction=limit_direction)
    return interpolated_rr_intervals.values.tolist()



from hrvanalysis import preprocessing as pre

hrv_sample_1 = [700, 800, 10000, 10000, 650, 700, 750, 540]
hrv_sample_2 = [10000, 10000, 800, 700, 800, 900, 10000, 10000, 650, 700]
hrv_sample_3 = [10000, 10000, 800, 700, 800, 900, 10000, 10000, 650, 700, 10000, 10000]

hrv_sample_1 = interpolate_nan_values(pre.remove_outliers(hrv_sample_1))
hrv_sample_2 = interpolate_nan_values(pre.remove_outliers(hrv_sample_2))
hrv_sample_3 = interpolate_nan_values(pre.remove_outliers(hrv_sample_3))

print("hrv sample 1: {}".format(hrv_sample_1))
print("hrv sample 2: {}".format(hrv_sample_2))
print("hrv sample 3: {}".format(hrv_sample_3))


out:
>>> hrv sample 1: [700.0, 800.0, 750.0, 700.0, 650.0, 700.0, 750.0, 540.0]
>>> hrv sample 2: [800.0, 800.0, 800.0, 700.0, 800.0, 900.0, 816.6666666666666, 733.3333333333334, 650.0, 700.0]
>>> hrv sample 3: [800.0, 800.0, 800.0, 700.0, 800.0, 900.0, 816.6666666666666, 733.3333333333334, 650.0, 700.0, 700.0, 700.0]

it is change abnormal 2 start point to post value correctly,
I made pull-request

thank you.

The code is running with an error

Hello, I'm getting this error in my running code and would like to know what's causing it:FileNotFoundError: [Errno 2] No such file or directory: 'D:\apnea-ecg-database-1.0.0\apnea-prediction_1min_ahead.pkl'
I don't know how to go about creating this file apnea-prediction_1min_ahead.pkl.I hope you can help me with this, I would appreciate it!

Error with Frequency domain

Hi,

I have issue with frequency domain, if I resample my RR intervals into segments less than 50 minutes.

For segments greather than 50 minutes it's OK, but if I use less, for example 5-minutes intervals I got error:

IndexError: index out of bounds

Do you know why and how to solve it?

Calculating rr-interval list

Hi Robin,

Do we need to compute the rr_intervals_list from the raw ecg signal or the rr_intervals_list refers to raw signal? I did not get it correctly?

Add support to compute ULF frequency band

The ultra-low-frequency (ULF) band (≤0.003 Hz) requires a recording period of at least 24 h (12) and is highly correlated with the SDANN time-domain index (44).

Correct SD2/SD1 ratio to SD1/SD2 ratio

We have to fix the SD2/SD1 to SD1/SD2 ratio standard measurement

The ratio of SD1/SD2, which measures the unpredictability of the RR time series, is used to measure autonomic balance when the monitoring period is sufficiently long and there is sympathetic activation. SD1/SD2 is correlated with the LF/HF ratio

Wrong divisor in computing pnni_50/pnni_20

Hi :)

I think I have just spotted a minor error when computing pnni_xx. Currently you have:

diff_nni = np.diff(nn_intervals)
length_int = len(nn_intervals)

nni_50 = sum(np.abs(diff_nni) > 50)
pnni_50 = 100 * nni_50 / length_int
nni_20 = sum(np.abs(diff_nni) > 20)
pnni_20 = 100 * nni_20 / length_int

The problem is that the divisor is length_int. I believe the divisor should be len(diff_nni) instead?

This error is also in the description:

- **nni_50**: Number of interval differences of successive RR-intervals greater than 50 ms.

- **pnni_50**: The proportion derived by dividing nni_50 (The number of interval differences \
of successive RR-intervals greater than 50 ms) by the total number of RR-intervals.

- **nni_20**: Number of interval differences of successive RR-intervals greater than 20 ms.

- **pnni_20**: The proportion derived by dividing nni_20 (The number of interval differences \
of successive RR-intervals greater than 20 ms) by the total number of RR-intervals.

Cheers!

'>=' not supported between instances of 'int' and 'ellipsis'

running the line below with the demo code produces the above-stated error in preprocessing.py

This remove outliers from signal

rr_intervals_without_outliers = remove_outliers(rr_intervals=rr_intervals_list,
low_rri=300, high_rri=2000)

I use python 3.6. Is that a Python version conflict issue? There are other errors thrown in the subsequent methods function_base.py and fromnumeric.py all related to TypeError: unsupported operand type(s) for -: 'ellipsis' and 'int'.

Full error message:


TypeError Traceback (most recent call last)
in ()
1 # This remove outliers from signal
2 rr_intervals_without_outliers = remove_outliers(rr_intervals=rr_intervals_list,
----> 3 low_rri=300, high_rri=2000)
4 # This replace outliers nan values with linear interpolation
5 interpolated_rr_intervals = interpolate_nan_values(rr_intervals=rr_intervals_without_outliers,

~/anaconda3/lib/python3.6/site-packages/hrvanalysis/preprocessing.py in remove_outliers(rr_intervals, verbose, low_rri, high_rri)
58 # Conversion RrInterval to Heart rate ==> rri (ms) = 1000 / (bpm / 60)
59 # rri 2000 => bpm 30 / rri 300 => bpm 200
---> 60 rr_intervals_cleaned = [rri if high_rri >= rri >= low_rri else np.nan for rri in rr_intervals]
61
62 if verbose:

~/anaconda3/lib/python3.6/site-packages/hrvanalysis/preprocessing.py in (.0)
58 # Conversion RrInterval to Heart rate ==> rri (ms) = 1000 / (bpm / 60)
59 # rri 2000 => bpm 30 / rri 300 => bpm 200
---> 60 rr_intervals_cleaned = [rri if high_rri >= rri >= low_rri else np.nan for rri in rr_intervals]
61
62 if verbose:

TypeError: '>=' not supported between instances of 'int' and 'ellipsis'

Question of citation

Hello.

I'm writing a research paper using this library. Are there any citations to quote?

Regards,
Seungwoo

Create Tutorial in Sphinx documentation

Create Tutorial in Sphinx documentation for the following use-cases:

  • preprocessing
  • time domain analysis.
  • frequency domain analysis.
  • non linear domain analysis

Cannot import LombScargle in extract_features.py

Once trying to import the module in the script extract_features.py:
from astropy.stats import LombScargle

Get a message: module LombScargle not found.

So I found a workaround as the following:

try:
from astropy.timeseries import LombScargle
except ImportError:
from astropy.stats import LombScargle

Any possibility to implement this?

Thanks.

Francis

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.