Skip to content

Heart Rate Variability (HRV)

Heart rate variability (HRV) is the variation in the time interval between consecutive heartbeats. HRV is a measure of the autonomic nervous system (ANS) and is often used as a proxy for stress. HRV is also used to assess the risk of cardiovascular disease and sudden cardiac death.

physiokit.hrv.defines

HrvFrequencyBandMetrics dataclass

HRV Frequency domain metrics dataclass.

Source code in physiokit/hrv/defines.py
@dataclass
class HrvFrequencyBandMetrics:
    """HRV Frequency domain metrics dataclass."""

    peak_frequency: float = 0  # Peak of frequency band in Hz
    peak_power: float = 0  # Power of frequency band in ms^2
    total_power: float = 0  # Total power in ms^2

HrvFrequencyMetrics dataclass

Frequency domain HRV metric dataclass.

Source code in physiokit/hrv/defines.py
@dataclass
class HrvFrequencyMetrics:
    """Frequency domain HRV metric dataclass."""

    bands: list[HrvFrequencyBandMetrics] = field(default_factory=list)
    total_power: float = 0  # Total power in ms^2

HrvNonlinearMetrics dataclass

Non-linear HRV metric dataclass.

Source code in physiokit/hrv/defines.py
@dataclass
class HrvNonlinearMetrics:
    """Non-linear HRV metric dataclass."""

    sd1: float = 0  # Short-term variability
    sd2: float = 0  # Long-term variability
    sd1_sd2_ratio: float = 0  # Ratio of short-term to long-term variability

HrvTimeMetrics dataclass

Time domain HRV metric dataclass.

Source code in physiokit/hrv/defines.py
@dataclass
class HrvTimeMetrics:
    """Time domain HRV metric dataclass."""

    # Deviation-based
    mean_nn: float = 0  # Mean of normal intervals
    sd_nn: float = 0  # St. Dev of normal intervals

    # Difference-based
    rms_sd: float = 0  # RMS of successive differences between normal intervals
    sd_sd: float = 0  # St. Dev of successive differences between normal intervals

    # Normalized
    cv_nn: float = 0
    cv_sd: float = 0

    # Robust
    median_nn: float = 0  # Median of normal intervals
    mad_nn: float = 0  # Median absolute deviation of normal intervals
    mcv_nn: float = 0  # Median coefficient of variation of normal intervals
    iqr_nn: float = 0  # Interquartile range of normal intervals
    prc20_nn: float = 0  # 20th percentile of normal intervals
    prc80_nn: float = 0  # 80th percentile of normal intervals

    # Extrema
    nn50: int = 0  # Number of intervals > 50 ms
    nn20: int = 0  # Number of intervals > 20 ms
    pnn50: float = 0  # Percentage of intervals > 50 ms
    pnn20: float = 0  # Percentage of intervals > 20 ms
    min_nn: float = 0  # Minimum of normal intervals
    max_nn: float = 0  # Maximum of normal intervals

physiokit.hrv.time

compute_hrv_time(rr_intervals, sample_rate=1000)

Compute time domain HRV metrics.

Parameters:

  • rr_intervals (NDArray) –

    RR intervals.

  • sample_rate (float, default: 1000 ) –

    Sampling rate. Defaults to 1000 Hz.

Returns:

Source code in physiokit/hrv/time.py
def compute_hrv_time(
    rr_intervals: npt.NDArray,
    sample_rate: float = 1000,
) -> HrvTimeMetrics:
    """Compute time domain HRV metrics.

    Args:
        rr_intervals (npt.NDArray): RR intervals.
        sample_rate (float, optional): Sampling rate. Defaults to 1000 Hz.

    Returns:
        HrvTimeMetrics: Time domain HRV metrics.
    """
    rri_ms = rr_intervals / sample_rate * 1000

    diff_rri_ms = np.diff(rri_ms)

    # Deviation based
    mean_nn = np.nanmean(rri_ms)
    sd_nn = np.nanstd(rri_ms, ddof=1)

    # Difference-based
    rms_sd = np.sqrt(np.nanmean(diff_rri_ms**2))
    sd_sd = np.nanstd(diff_rri_ms, ddof=1)

    # Normalized
    cv_nn = sd_nn / mean_nn
    cv_sd = rms_sd / mean_nn

    # Robust
    median_nn = np.nanmedian(rri_ms)
    mad_nn = 1.4826 * np.nanmedian(np.abs(rri_ms - median_nn))
    mcv_nn = mad_nn / median_nn
    iqr_nn = scipy.stats.iqr(rri_ms)
    prc20_nn = np.nanpercentile(rri_ms, q=20)
    prc80_nn = np.nanpercentile(rri_ms, q=80)

    # Extrema
    nn50 = np.sum(np.abs(diff_rri_ms) > 50)
    nn20 = np.sum(np.abs(diff_rri_ms) > 20)
    pnn50 = nn50 / (len(diff_rri_ms) + 1) * 100
    pnn20 = nn20 / (len(diff_rri_ms) + 1) * 100
    min_nn = np.nanmin(rri_ms)
    max_nn = np.nanmax(rri_ms)

    return HrvTimeMetrics(
        mean_nn=mean_nn,
        sd_nn=sd_nn,
        rms_sd=rms_sd,
        sd_sd=sd_sd,
        cv_nn=cv_nn,
        cv_sd=cv_sd,
        median_nn=median_nn,
        mad_nn=mad_nn,
        mcv_nn=mcv_nn,
        iqr_nn=iqr_nn,
        prc20_nn=prc20_nn,
        prc80_nn=prc80_nn,
        nn50=nn50,
        nn20=nn20,
        pnn50=pnn50,
        pnn20=pnn20,
        min_nn=min_nn,
        max_nn=max_nn,
    )

physiokit.hrv.frequency

compute_hrv_frequency(peaks, rri, bands, sample_rate=1000)

Compute the frequency domain HRV features.

Parameters:

  • peaks (array) –

    R peaks.

  • rri (array) –

    RR intervals.

  • bands (list) –

    List of frequency bands.

  • sample_rate (float, default: 1000 ) –

    Sampling rate in Hz. Defaults to 1000 Hz.

Returns:

Source code in physiokit/hrv/frequency.py
def compute_hrv_frequency(
    peaks: npt.NDArray,
    rri: npt.NDArray,
    bands: list[tuple[float, float]],
    sample_rate: float = 1000,
) -> HrvFrequencyMetrics:
    """Compute the frequency domain HRV features.

    Args:
        peaks (array): R peaks.
        rri (array): RR intervals.
        bands (list): List of frequency bands.
        sample_rate (float, optional): Sampling rate in Hz. Defaults to 1000 Hz.

    Returns:
        HrvFrequencyMetrics: Frequency domain HRV features.
    """

    # Interpolate to get evenly spaced samples
    ts = np.arange(peaks[0], peaks[-1], 1)
    rri_int = scipy.interpolate.interp1d(peaks, rri, kind="linear")(ts)

    # NOTE: Use bands to determine amount of zero padding for freq bins
    fft_len = int(2 ** np.ceil(np.log2(max(ts.size, 32 * sample_rate))))

    freqs, rri_fft = compute_fft(rri_int, sample_rate=sample_rate, fft_len=fft_len, window="blackman", axis=-1)
    rri_ps = 2 * np.abs(rri_fft)

    metrics = HrvFrequencyMetrics()
    for lowcut, highcut in bands:
        l_idx = np.where(freqs >= lowcut)[0][0]
        r_idx = np.where(freqs >= highcut)[0][0]
        f_idx = rri_ps[l_idx:r_idx].argmax() + l_idx
        metrics.bands.append(
            HrvFrequencyBandMetrics(
                peak_frequency=freqs[f_idx],
                peak_power=rri_ps[f_idx],
                total_power=rri_ps[l_idx:r_idx].sum(),
            )
        )
    # END FOR
    metrics.total_power = reduce(lambda x, y: x + y.total_power, metrics.bands, 0)
    return metrics