Computational mathematics comes in different flavours, symbolic, numerical and graphical. Numerical computations are the workhorse of computations. Graphical computations are great for visualizations. Symbolic or algebraic computations are great for getting an intuition for a mathematical problem. As an addition to the previous post, I’m going to examine SymPy and symbolic mathematics in Python and to help with guiding the conversation, SymPy will be used to create a simplified ECG model.
Electrocardiography is the process of producing an electrocardiogram (ECG or EKG), a recording – a graph of voltage versus time – of the electrical activity of the heart using electrodes placed on the skin. These electrodes detect the small electrical changes that are a consequence of cardiac muscle depolarization followed by repolarization during each cardiac cycle (heartbeat).
An ECG is a test that checks how your heart is functioning by recording the electrical activity that passed through your heart. Here is an example of ECG from a healthy individual.
ECG is periodic time-series data. You can see a pattern in the image above. Such an output would be compared to known health or unhealthy patterns to help in detecting heart problems.
For this post, we will only be focusing on modelling a normal sinus rhythm. A healthy individual pattern. Here is an example of a single heartbeat pattern with given segments in the waveform.
Create synthetic time-series data that models sinus rhythm for a human heart as seen on ECG. Include the different ECG segments in your model including the P, Q, R, S, T segments.
SymPy is a Python library that was created for symbolic mathematics. SymPy has a lot of features and it really shines when it comes to calculus. Before we dive into the code for ECG here is a basic example that shows the main features of SymPy.
The example creates a symbol “x” and the function “f” is defined with the symbol “x”. The following plot has been created from different values of “x”. After the function is differentiated, both the function and its derivative are plot together.
There are a few different methods used to generate ECG models. Here is the research paper that we use to show how one could use the formulas defined there and SymPy to develop a model.
First, we define the symbols required by the mathematical formulas.
# create symbols for sympy processing k,K_B,A_P,K_P,K_PQ,A_Q,K_Q,A_R,K_R,A_S,K_S,K_CS,s_m,A_T,K_T,K_ST,s_I,K_I= symbols('k K_B A_P K_P K_PQ A_Q K_Q A_R K_R A_S K_S K_CS s_m A_T K_T K_ST s_I K_I')
We then get to work defining the formulas in terms of the symbols.
# P wave P=-A_P/2*cos((2*pi*k + 15)/K_P)+A_P/2
Once we have the formula defined we can plug in some values and plot it. The Piecewise function is used to define an interval for the function.
# P wave I_K_B=124 V_A_P=0.01 I_K_P=79 P_SUB = P.subs([(A_P,V_A_P),(K_P,I_K_P)]) P_P = Piecewise((P_SUB, Interval(0, I_K_P).contains(k)), (0, True)) plot(P_P,(k,0,I_K_P))
The model defined in the research paper strives to be as accurate as possible and customizable for different heart conditions. By contrast, in our simplified model, our goal is to approximate only the ideal version of the model.
To reduce the complexity of the model we limit the segments of our model to simple sinusoidal signals. Using only the properties of trigonometric functions with time-shifting and time-scaling we approximate and generate an ideal version of the ECG model.
def createECGModel(): p=0.1*sin((pi*k+120)/80) p_p=Piecewise((p,Interval(120,200).contains(k)),(0,True)) q=-0.03*cos((pi*k-30)/20) q_p=Piecewise((q,Interval(240,260).contains(k)),(0,True)) r=1.1*cos((pi*k+31.2)/20) r_p=Piecewise((r,Interval(260,280).contains(k)),(0,True)) s=-0.06*sin(pi*k/10) s_p=Piecewise((s,Interval(280,290).contains(k)),(0,True)) t=0.4*sin((pi*k-215)/110) t_p=Piecewise((t,Interval(290,400).contains(k)),(0,True)) return p_p+q_p+r_p+s_p+t_p
We then graph it to see how close we are to the ideal version.
After we create the initial model, we want to use that in some data science tasks. We first prepare the SymPy formula and use it with our data analysis tool, pandas.
SymPy also supports vectorization, you can run the formulas directly with substitutions or you can define a vectorized version of the formula. Here is the non-vectorized version:
period = np.arange(0,500) data =  for t in period: data.append(ecg.subs(k,t))
The code loops through the values of k and generates a value for each data point. Using this code the ECG waveform generation function runs on average in 4 seconds. Here is the vectorized version:
ecg_lam = lambdify(k,ecg,'numpy') period = np.arange(0,500) data = ecg_lam(period)
Here we use the lambdify function to create a vectorized version of the formula. We then apply the formula to all the data points in the array. This version runs in 0.02 seconds.
Once we have the data for a single heartbeat in pandas, we duplicate the data to generate a continuous waveform for multiple heartbeats.
df = pd.DataFrame(data=data) # duplicate data - this is for visualization only df = df.append(df,ignore_index=True) df = df.append(df,ignore_index=True) df = df.append(df,ignore_index=True)
Here is how the ECG waveform looks, generated by our simplified model.
For comparison here is the output from my Apple watch ECG.
SymPy is one of my favourite libs to use and to play around with. Often you can reduce the complexity of your model and generate an idealized version of it. Such a model can be used to drive the initial development of your AI. It can also be used to benchmark your machine learning code.
January 6th, 2020⟵ Back
August 6th, 2023The Critical Need for Application Modernization in SMEs