<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Analysing PPG signals from smart rings
There's a range of Smart Rings that recently hit the market. Among other things they also record PPG signals on the finger, so let's dive into how to analyse these with HeartPy.
The example file I've included contains only the PPG signal in order to keep filesize low. Many smart rings also record skin response, acceleration and gyroscopic data. It was recorded at 32Hz
Step1:  First let's take a look at the whole signal
Step2:  I spot a few gaps.
This means periods where the device outputs NaN values. Let's replace with numerical data (0's) otherwise some numerical parts of the analysis will fail.
Also let's plot the first 5 minutes and get a look at the signal
Step3:  Doesn't seem like much of a signal is going on. Let's go to the second five minutes and zoom in a bit
Step4:  It's not great but we definitely got a signal in this segment!
Analysing is relatively straightforward. We need to do a few standard signal processing tricks
Step5:  Well that does look a lot better
Step6:  Let's zoom in on the 5 minute segment minute by minute
Step7:  Even though the signal is quite faint, it is still possible to extract the vast majority of peaks from it.
Now the last segment is noisy even after filtering, which in combination with the smart ring as a source (low amplitude, low sample rate!) results in some incorrect acceptances. This causes high values of RMSSD, SDNN and SDSD, pNN20 and pNN50, which are sensitive to outliers.
HeartPy comes with an option to clean the peak-peak intervals prior to analysis. It will attempt to identify and reject outliers. This works better the longer the signal is! After all, if there's many 'good' peak-peak intervals to relatively few 'bad' ones, outlier detection works better.
Available methods to detect outiers are 'quotient-filter', 'iqr' (based on the interquartile range) and 'z-score' (based on the modified z-score method). Default is 'quotient-filter'
<ASSISTANT_TASK:>
Python Code:
#Let's import some packages first
import numpy as np
import matplotlib.pyplot as plt
import heartpy as hp
sample_rate = 32
#load the example file
data = hp.get_data('ring_data.csv')
Explanation: Analysing PPG signals from smart rings
There's a range of Smart Rings that recently hit the market. Among other things they also record PPG signals on the finger, so let's dive into how to analyse these with HeartPy.
The example file I've included contains only the PPG signal in order to keep filesize low. Many smart rings also record skin response, acceleration and gyroscopic data. It was recorded at 32Hz
End of explanation
plt.figure(figsize=(12,6))
plt.plot(data)
plt.show()
Explanation: First let's take a look at the whole signal
End of explanation
#missing pieces! Let's replace
data = np.nan_to_num(data)
#plot first 5 minutes
plt.figure(figsize=(12,6))
plt.plot(data[0:(5 * 60) * sample_rate])
plt.ylim(15000, 17000)
plt.show()
Explanation: I spot a few gaps.
This means periods where the device outputs NaN values. Let's replace with numerical data (0's) otherwise some numerical parts of the analysis will fail.
Also let's plot the first 5 minutes and get a look at the signal
End of explanation
plt.figure(figsize=(12,6))
plt.plot(data[(5 * 60) * sample_rate:(10 * 60) * sample_rate])
plt.show()
plt.figure(figsize=(12,6))
plt.title('zoomed in!')
plt.plot(data[(5 * 60) * sample_rate:(6 * 60) * sample_rate])
plt.show()
Explanation: Doesn't seem like much of a signal is going on. Let's go to the second five minutes and zoom in a bit
End of explanation
#First let's filter, there's a standard butterworth
#filter implementations available in HeartPy under
#filtersignal(). We will use the bandpass variant.
#we filter out frequencies below 0.8Hz (<= 48 bpm)
#and above 3Hz (>= 180 bpm)
filtered_ppg = hp.filter_signal(data[(5 * 60) * sample_rate:
                                     (10 * 60) * sample_rate], 
                                cutoff = [0.8, 2.5], 
                                filtertype = 'bandpass',
                                sample_rate = sample_rate, 
                                order = 3,
                                return_top = False)
#And let's plot the same segment as under 'zoomed in!' above
plt.figure(figsize=(12,6))
plt.plot(filtered_ppg[0:((2*60)*32)])
plt.show()
Explanation: It's not great but we definitely got a signal in this segment!
Analysing is relatively straightforward. We need to do a few standard signal processing tricks:
filter the signal
run the analysis
visualise the analysis
End of explanation
#Run the analysis. Using 'high_precision' means a spline will
#be fitted to all peaks and then the maximum determined.
#this means we can have a much higher peak position accuracy than 
#the 32Hz would allow
wd, m = hp.process(filtered_ppg, sample_rate=sample_rate,
                   high_precision = True)
plt.figure(figsize=(12,6))
hp.plotter(wd, m)
for key in m.keys():
    print('%s: %f' %(key, m[key]))
Explanation: Well that does look a lot better
End of explanation
plt.figure(figsize=(12,6))
plt.xlim(0, (1 * 60) * sample_rate)
hp.plotter(wd, m, title='first minute')
plt.figure(figsize=(12,6))
plt.xlim((1 * 60) * sample_rate, (2 * 60) * sample_rate)
hp.plotter(wd, m, title='second minute')
plt.figure(figsize=(12,6))
plt.xlim((2 * 60) * sample_rate, (3 * 60) * sample_rate)
hp.plotter(wd, m, title='third minute')
plt.figure(figsize=(12,6))
plt.xlim((3 * 60) * sample_rate, (4 * 60) * sample_rate)
hp.plotter(wd, m, title='fourth minute')
plt.figure(figsize=(12,6))
plt.xlim((4 * 60) * sample_rate, (5 * 60) * sample_rate)
hp.plotter(wd, m, title='fifth minute')
Explanation: Let's zoom in on the 5 minute segment minute by minute
End of explanation
wd, m = hp.process(filtered_ppg, sample_rate=sample_rate,
                   high_precision = True, clean_rr = True)
plt.figure(figsize=(12,6))
hp.plotter(wd, m)
for key in m.keys():
    print('%s: %f' %(key, m[key]))
    
#and plot poincare
hp.plot_poincare(wd, m)
Explanation: Even though the signal is quite faint, it is still possible to extract the vast majority of peaks from it.
Now the last segment is noisy even after filtering, which in combination with the smart ring as a source (low amplitude, low sample rate!) results in some incorrect acceptances. This causes high values of RMSSD, SDNN and SDSD, pNN20 and pNN50, which are sensitive to outliers.
HeartPy comes with an option to clean the peak-peak intervals prior to analysis. It will attempt to identify and reject outliers. This works better the longer the signal is! After all, if there's many 'good' peak-peak intervals to relatively few 'bad' ones, outlier detection works better.
Available methods to detect outiers are 'quotient-filter', 'iqr' (based on the interquartile range) and 'z-score' (based on the modified z-score method). Default is 'quotient-filter'
End of explanation
<END_TASK>