2. VitalDB model check
This exemple compare the data from the open source vitalDB database and the model included in PAS.
[10]:
import matplotlib.pyplot as plt
import numpy as np
import python_anesthesia_simulator as pas
import vitaldb as vdb
import pandas as pd
2.1. Import data from vitalDB
All the documentation about the vitaldb package is available here.
[11]:
case_id = 47 # case id 47
ts = 1 # sampling time in seconds
track_name = ['BIS/BIS', 'Orchestra/PPF20_RATE', 'Orchestra/RFTN20_RATE',
'Orchestra/PPF20_CE', 'Orchestra/RFTN20_CE', 'Solar8000/ART_MBP',
'BIS/SQI', 'Solar8000/PLETH_HR', 'Orchestra/PPF20_CP',
'Orchestra/RFTN20_CP'] # track names to import
case = vdb.VitalFile(case_id, track_name) # load case
case_df = case.to_pandas(track_name, ts) # convert to pandas dataframe
# fill missing values
case_df['Orchestra/PPF20_RATE'].fillna(method='bfill', inplace=True)
case_df['Orchestra/RFTN20_RATE'].fillna(method='bfill', inplace=True)
/tmp/ipykernel_62354/3413879139.py:10: FutureWarning: A value is trying to be set on a copy of a DataFrame or Series through chained assignment using an inplace method.
The behavior will change in pandas 3.0. This inplace method will never work because the intermediate object on which we are setting values always behaves as a copy.
For example, when doing 'df[col].method(value, inplace=True)', try using 'df.method({col: value}, inplace=True)' or df[col] = df[col].method(value) instead, to perform the operation inplace on the original object.
case_df['Orchestra/PPF20_RATE'].fillna(method='bfill', inplace=True)
/tmp/ipykernel_62354/3413879139.py:10: FutureWarning: Series.fillna with 'method' is deprecated and will raise in a future version. Use obj.ffill() or obj.bfill() instead.
case_df['Orchestra/PPF20_RATE'].fillna(method='bfill', inplace=True)
/tmp/ipykernel_62354/3413879139.py:11: FutureWarning: A value is trying to be set on a copy of a DataFrame or Series through chained assignment using an inplace method.
The behavior will change in pandas 3.0. This inplace method will never work because the intermediate object on which we are setting values always behaves as a copy.
For example, when doing 'df[col].method(value, inplace=True)', try using 'df.method({col: value}, inplace=True)' or df[col] = df[col].method(value) instead, to perform the operation inplace on the original object.
case_df['Orchestra/RFTN20_RATE'].fillna(method='bfill', inplace=True)
/tmp/ipykernel_62354/3413879139.py:11: FutureWarning: Series.fillna with 'method' is deprecated and will raise in a future version. Use obj.ffill() or obj.bfill() instead.
case_df['Orchestra/RFTN20_RATE'].fillna(method='bfill', inplace=True)
2.2. Replay input data in the simulator
An instance of the Simulator is created for the patient with the specific information. Then we run the simulation using the drug rates as input (in the relevant units).
[12]:
perso_data = pd.read_csv("https://api.vitaldb.net/cases", decimal='.') # load personal data of the case
age = float(perso_data[perso_data['caseid'] == case_id]['age'].iloc[0])
height = float(perso_data[perso_data['caseid'] == case_id]['height'].iloc[0])
weight = float(perso_data[perso_data['caseid'] == case_id]['weight'].iloc[0])
gender = int(perso_data[perso_data['caseid'] == case_id]['sex'].iloc[0] == 'M') # F = 0, M = 1
Patient_simu = pas.Patient([age, height, weight, gender], ts=ts,
model_propo="Marsh_modified", model_remi="Minto") # create patient instance
case_length = case_df.shape[0] # length of the case
df_results = Patient_simu.full_sim(u_propo = case_df['Orchestra/PPF20_RATE']*20/3600,
u_remi = case_df['Orchestra/RFTN20_RATE']*20/3600) # run simulation
/home/aubouinb/Nextcloud2/Code/Python_Anesthesia_Simulator/.venv/lib/python3.12/site-packages/python_anesthesia_simulator/simulator.py:584: FutureWarning: The behavior of DataFrame concatenation with empty or all-NA entries is deprecated. In a future version, this will no longer exclude empty or all-NA columns when determining the result dtypes. To retain the old behavior, exclude the relevant entries before the concat operation.
self.dataframe = pd.concat((self.dataframe, pd.DataFrame(new_line, index=[1])), ignore_index=True)
2.3. Plot the results
[13]:
# plot the inputs
plt.figure()
plt.plot(df_results['Time']/60, case_df['Orchestra/PPF20_RATE']*20/3600,
'b', label='Propofol rate (mg/s)')
plt.plot(df_results['Time']/60, case_df['Orchestra/RFTN20_RATE']*20/3600,
'r', label='Orchestra/RFTN20_RATE (µg/s)')
plt.xlabel('time (min)')
plt.title('Inputs')
plt.legend()
plt.grid()
plt.show()
[14]:
# plot Propofol Effect site concentration
fig, ax1 = plt.subplots(2)
ax1[0].plot(df_results['Time']/60, case_df['Orchestra/PPF20_CP'], 'b', label='VitalDB')
ax1[0].plot(df_results['Time']/60, df_results['x_propo_1'], 'b--', label='Simulator')
ax1[0].plot(df_results['Time']/60, case_df['Orchestra/RFTN20_CP'], 'r', label='VitalDB')
ax1[0].plot(df_results['Time']/60, df_results['x_remi_1'], 'r--', label='Simulator')
ax1[0].set_xlabel('Time (min)')
ax1[0].set_ylabel('Blood')
ax1[0].legend()
ax1[0].grid()
ax1[1].plot(df_results['Time']/60, case_df['Orchestra/PPF20_CE'], 'b', label='VitalDB')
ax1[1].plot(df_results['Time']/60, df_results['x_propo_4'], 'b--', label='Simulator')
ax1[1].plot(df_results['Time']/60, case_df['Orchestra/RFTN20_CE'], 'r', label='VitalDB')
ax1[1].plot(df_results['Time']/60, df_results['x_remi_4'], 'r--', label='Simulator')
ax1[1].set_xlabel('Time (min)')
ax1[1].set_ylabel('Effect Site')
ax1[0].set_title('Concentration')
ax1[1].legend()
ax1[1].grid()
Both simulation and data from vitalDB are quite similar, This was expected since the concentration are competed in by the TCI device using the same models than in PAS. The little difference might came from missing data or numerical approximation in the TCI device.
[15]:
# plot BIS
plt.figure()
plt.plot(df_results['Time']/60, case_df['BIS/BIS'], 'b', label='VitalDB')
plt.plot(df_results['Time']/60, df_results['BIS'], 'r', label='Simulator')
plt.xlabel('Time (min)')
plt.ylabel('BIS')
plt.title('BIS')
plt.legend()
plt.grid()
plt.show()
Here we observe a huge difference between measured data and simulation. This can be explained by the fact that we are simulating a nominal model while the real patient have an individual reaction to drug. Moreover, the real patient is also subject to disturbance and the measure is subject to noise.