In [2]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.optimize import curve_fit

1. Определение концентрации фермента.

In [3]:
protein_volume = 1.5e-6 # л
protein_mass_concentration = 0.5 # мг/мл = г/л
protein_molecular_mass = 6.5e4 # Да = г/моль
cell_volume = 1.25e-3 # л
protein_mass = protein_volume * protein_mass_concentration # 7.5е-7 г
protein_amount = protein_mass / protein_molecular_mass # 1.1538е-14 моль
protein_concentration = protein_amount / cell_volume # 9.23е-9 моль/л
In [4]:
protein_concentration
Out[4]:
9.23076923076923e-09

Data upload

In [5]:
data = pd.read_csv("kinetics_data.csv", sep='\t', index_col=None, header=[0,1])
In [6]:
data.head()
Out[6]:
ABTS, 7 uM ABTS, 10 uM ABTS, 11.5 uM ABTS, 15 uM ABTS, 24 uM ABTS, 33 uM ABTS, 50 uM ABTS, 100 uM ABTS, 200 uM ABTS, 500 uM
time consumption.S time consumption.S time consumption.S time consumption.S time consumption.S time consumption.S time consumption.S time consumption.S time consumption.S time consumption.S
0 0.000 -0.00539 0.000 -0.10414 0.000 0.00309 0.000 0.00482 0.000 0.00321 0.000 0.00174 0.000 -0.00344 0.000 0.00162 0.000 0.00050 0.000 -0.000273
1 0.250 -0.13522 0.187 -0.01704 0.250 0.39697 0.188 0.07179 0.250 0.06314 0.188 -0.08255 0.250 -0.43274 0.187 0.02695 0.187 -0.17024 0.250 0.257980
2 0.437 0.01784 0.437 -0.02020 0.500 0.23845 0.438 0.09275 0.437 0.42474 0.438 0.14203 0.500 0.07041 0.437 0.45392 0.437 0.35591 0.438 0.637440
3 0.687 0.26791 0.625 0.51217 0.687 0.24885 0.625 0.24044 0.687 0.44283 0.625 0.34476 0.687 0.07636 0.687 0.98718 0.625 0.85406 0.688 0.988040
4 0.937 0.34297 0.875 0.53341 0.937 0.24064 0.875 0.22188 0.875 0.51364 0.875 0.52953 0.937 0.14609 0.875 0.86526 0.875 0.61390 0.938 1.101360
In [7]:
abts_concentrations = [7, 10, 11.5, 15, 24, 33, 50, 100, 200, 500]

Задание 2.

2.1. Линейная аппроксимация начальных скоростей.

In [8]:
len(abts_concentrations)
Out[8]:
10
In [9]:
plt.figure(figsize=(10, 25))
for i, conc in enumerate(abts_concentrations):
    plt.subplot(5,2,i+1)
    plt.plot(data[f'ABTS, {conc} uM']['time'], data[f'ABTS, {conc} uM']['consumption.S'], 'o')
    plt.title(f'ABTS, {conc} uM')
    plt.xlabel('time, sec')
    plt.ylabel('O2 concentration, uM')
plt.tight_layout()

Видно, что при малых концентрациях ABTS нет красивой линейной зависимости. Посмотрим на первые 5 секунд реакции.

In [10]:
plt.figure(figsize=(10, 25))
for i, conc in enumerate(abts_concentrations):
    plt.subplot(5,2,i+1)
    plt.plot(data[f'ABTS, {conc} uM'].query('time <= 5')['time'],
             data[f'ABTS, {conc} uM'].query('time <= 5')['consumption.S'], 'o')
    plt.title(f'ABTS, {conc} uM, initial')
    plt.xlabel('time, sec')
    plt.ylabel('O2 concentration, uM')
plt.tight_layout()

Всё равно выглядит диковато, но пусть так.

In [11]:
o2_speed = list()
for conc in abts_concentrations:
    slope, intercept = np.polyfit(x=data[f'ABTS, {conc} uM'].query('time <= 5')['time'],
                                  y=data[f'ABTS, {conc} uM'].query('time <= 5')['consumption.S'],
                                  deg=1)
    o2_speed.append([slope, intercept])
o2_speed = np.array(o2_speed, dtype=float)
In [12]:
o2_speed
Out[12]:
array([[ 0.19155979, -0.04176421],
       [ 0.2810016 ,  0.18773772],
       [ 0.25024738,  0.1005705 ],
       [ 0.21473013,  0.08441781],
       [ 0.43492624,  0.17283992],
       [ 0.48544782,  0.06119439],
       [ 0.5060206 , -0.21700788],
       [ 0.63245982,  0.22859424],
       [ 0.75382472,  0.02906672],
       [ 0.68107965,  0.38222859]])
In [13]:
plt.plot(abts_concentrations, o2_speed[:, 0])
plt.xlabel("ABTS concentration, uM")
plt.ylabel("Speed")
plt.show()

Мы видим некоторое расхождение в известной нам зависимости, связано, по-видимому, с особенностями аппроксимации, а именно с большой дисперсией поглощения кислорода при концентрации ABTS 7 uM, 11.5 uM, 15 uM. Но в целом, очертания похожи на правду.

In [14]:
initial_speed = pd.DataFrame({'concentration': abts_concentrations, 'speed': o2_speed[:, 0] * 4}) # домножили на 4, так как ABTS в 4 раза больше расходуется, чем выделяется О2 по уравнению реакции.
In [15]:
initial_speed
Out[15]:
concentration speed
0 7.0 0.766239
1 10.0 1.124006
2 11.5 1.000990
3 15.0 0.858921
4 24.0 1.739705
5 33.0 1.941791
6 50.0 2.024082
7 100.0 2.529839
8 200.0 3.015299
9 500.0 2.724319

Теперь посмотрим, как зафитились наши кривые в начале.

In [16]:
plt.figure(figsize=(10, 25))
for i, conc in enumerate(abts_concentrations):
    plt.subplot(5,2,i+1)
    plt.plot(data[f'ABTS, {conc} uM'].query('time <= 5')['time'],
             data[f'ABTS, {conc} uM'].query('time <= 5')['consumption.S'],
             'o', label='data')
    plt.plot(data[f'ABTS, {conc} uM'].query('time <= 5')['time'],
             data[f'ABTS, {conc} uM'].query('time <= 5')['time'] * o2_speed[i, 0] + o2_speed[i, 1],
             color='red', label='fitted line')
    plt.legend()
    plt.title(f'ABTS, {conc} uM, initial')
    plt.xlabel('time, sec')
    plt.ylabel('O2 concentration, uM')
plt.tight_layout()

Чем больше концентрация ABTS, тем более логичной выглядит аппроксимация.

2.2. Аппроксимация экспонентой.

In [17]:
def expassoc(x, y0, a1, t1, a2, t2):
    return y0 + a1 * (1 - np.exp(-x / t1)) + a2 * (1 - np.exp(-x / t2))

По совету преподавателя введём ограничение для данных — первые 60 секунд.

In [23]:
params_list = list()
params_names = ['y0', 'a1', 't1', 'a2', 't2']
for conc in abts_concentrations:
    try:
        exp_ind = data[f'ABTS, {conc} uM'].dropna()['time'] < 60
        params, pcov = curve_fit(expassoc,
                                 data[f'ABTS, {conc} uM'].dropna()['time'][exp_ind],
                                 data[f'ABTS, {conc} uM'].dropna()['consumption.S'][exp_ind],
                                 bounds=([-np.inf, -np.inf, 0, -np.inf, 0], np.inf))
    except RuntimeError:
        params = [0 for _ in range(5)]
        print(f"{conc} didn't fit params")
    params_list.append(dict(zip(params_names, params)))
24 didn't fit params
33 didn't fit params
100 didn't fit params

Удивительно, но 200 и 500 не зафитились, несмотря на то, что установил ограничения на параметры, как было написано в документации. Возможно, проблема в том, что первоначальные значения все равны 1 и Trust Region Reflective algorithm не смог подобрать правильные параметры. В Origin используют алгоритм Levenberg-Marquardt, но в scipy он не умеет работать, если заданы ограничения. Тогда можно попробовать dogleg algorithm или поменять первоначальные значения.

In [90]:
pd.DataFrame(params_list)
Out[90]:
a1 a2 t1 t2 y0
0 -58.093763 59.830878 10.089263 9.910464 -0.124724
1 2.809027 481.402527 5.993436 293583.896887 0.023699
2 -416.815505 419.294970 3.159303 3.181125 0.304376
3 244.597785 -240.517503 16.616377 16.799188 -0.229161
4 -357.298005 363.424705 13.904927 13.795958 -0.145573
5 4.609045 4.804377 11.441321 11.441733 -0.598650
6 8.052366 5.662072 14.708912 14.709158 -1.399644
7 2.061789 25.628443 21.257931 21.256915 -2.931910
8 0.000000 0.000000 0.000000 0.000000 0.000000
9 0.000000 0.000000 0.000000 0.000000 0.000000
In [87]:
params_list = list()
params_names = ['y0', 'a1', 't1', 'a2', 't2']
for conc in abts_concentrations:
    try:
        params, pcov = curve_fit(expassoc,
                                 data[f'ABTS, {conc} uM'].dropna()['time'],
                                 data[f'ABTS, {conc} uM'].dropna()['consumption.S'],
                                 bounds=([-np.inf, -np.inf, 0, -np.inf, 0], np.inf),
                                 method='dogbox')
    except RuntimeError:
        params = [0 for _ in range(5)]
        print(f"{conc} didn't fit params")
    params_list.append(dict(zip(params_names, params)))
11.5 didn't fit params
15 didn't fit params
24 didn't fit params
/home/dmitry/miniconda3/lib/python3.7/site-packages/scipy/optimize/_lsq/dogbox.py:199: RuntimeWarning: invalid value encountered in less
  active_set = on_bound * g < 0
/home/dmitry/miniconda3/lib/python3.7/site-packages/numpy/core/_methods.py:28: RuntimeWarning: invalid value encountered in reduce
  return umr_maximum(a, axis, None, out, keepdims, initial)
---------------------------------------------------------------------------
LinAlgError                               Traceback (most recent call last)
<ipython-input-87-8402142ad3fa> in <module>
      7                                  data[f'ABTS, {conc} uM'].dropna()['consumption.S'],
      8                                  bounds=([-np.inf, -np.inf, 0, -np.inf, 0], np.inf),
----> 9                                  method='dogbox')
     10     except RuntimeError:
     11         params = [0 for _ in range(5)]

~/miniconda3/lib/python3.7/site-packages/scipy/optimize/minpack.py in curve_fit(f, xdata, ydata, p0, sigma, absolute_sigma, check_finite, bounds, method, jac, **kwargs)
    753 
    754         res = least_squares(func, p0, jac=jac, bounds=bounds, method=method,
--> 755                             **kwargs)
    756 
    757         if not res.success:

~/miniconda3/lib/python3.7/site-packages/scipy/optimize/_lsq/least_squares.py in least_squares(fun, x0, jac, bounds, method, ftol, xtol, gtol, x_scale, loss, f_scale, diff_step, tr_solver, tr_options, jac_sparsity, max_nfev, verbose, args, kwargs)
    916         result = dogbox(fun_wrapped, jac_wrapped, x0, f0, J0, lb, ub, ftol,
    917                         xtol, gtol, max_nfev, x_scale, loss_function,
--> 918                         tr_solver, tr_options, verbose)
    919 
    920     result.message = TERMINATION_MESSAGES[result.status]

~/miniconda3/lib/python3.7/site-packages/scipy/optimize/_lsq/dogbox.py in dogbox(fun, jac, x0, f0, J0, lb, ub, ftol, xtol, gtol, max_nfev, x_scale, loss_function, tr_solver, tr_options, verbose)
    223         if tr_solver == 'exact':
    224             J_free = J[:, free_set]
--> 225             newton_step = lstsq(J_free, -f, rcond=-1)[0]
    226 
    227             # Coefficients for the quadratic model along the anti-gradient.

~/miniconda3/lib/python3.7/site-packages/numpy/linalg/linalg.py in lstsq(a, b, rcond)
   2154     signature = 'DDd->Ddid' if isComplexType(t) else 'ddd->ddid'
   2155     extobj = get_linalg_error_extobj(_raise_linalgerror_lstsq)
-> 2156     x, resids, rank, s = gufunc(a, b, rcond, signature=signature, extobj=extobj)
   2157 
   2158     # remove the axis we added

~/miniconda3/lib/python3.7/site-packages/numpy/linalg/linalg.py in _raise_linalgerror_lstsq(err, flag)
     99 
    100 def _raise_linalgerror_lstsq(err, flag):
--> 101     raise LinAlgError("SVD did not converge in Linear Least Squares")
    102 
    103 def get_linalg_error_extobj(callback):

LinAlgError: SVD did not converge in Linear Least Squares

Ха, dogbox вообще упал. Тогда возвращается к дефолту, меняем первоначальные параметры. Допустим, y0 = -1, t1 = t2 = 10, A1 = A2 = 5 исходя из предыдущего результата.

In [92]:
params_list = list()
params_names = ['y0', 'a1', 't1', 'a2', 't2']
for conc in abts_concentrations:
    try:
        params, pcov = curve_fit(expassoc,
                                 data[f'ABTS, {conc} uM'].dropna()['time'],
                                 data[f'ABTS, {conc} uM'].dropna()['consumption.S'],
                                 bounds=([-np.inf, -np.inf, 0, -np.inf, 0], np.inf),
                                 p0=[-1, 5, 10, 5, 10])
    except RuntimeError:
        params = [0 for _ in range(5)]
        print(f"{conc} didn't fit params")
    params_list.append(dict(zip(params_names, params)))
50 didn't fit params
500 didn't fit params

Ну, мы зафитили 200, но зато не смогли 50. Что же это такое :(

In [93]:
pd.DataFrame(params_list)
Out[93]:
a1 a2 t1 t2 y0
0 56.150186 -54.413070 9.904652 10.095340 -0.124725
1 13.675503 -10.824332 6.348754 6.348844 0.052810
2 255.028219 -252.239913 11.959242 12.068010 -0.155085
3 237.157258 -233.076976 16.613509 16.802106 -0.229162
4 363.228658 -357.101958 13.795927 13.904951 -0.145573
5 -644.457992 653.058859 6.964714 7.028604 0.157127
6 0.000000 0.000000 0.000000 0.000000 0.000000
7 6.741747 20.948528 21.256590 21.257073 -2.931954
8 38.694843 20.984492 42.732919 42.732690 -6.105833
9 0.000000 0.000000 0.000000 0.000000 0.000000

Давайте попробуем с дефолтными первоначальными значениями увеличить число вычислений функции. По умолчанию, это 100 n, где n -- количество точек. Сделаем это 1000 n.

In [160]:
params_list = list()
params_names = ['y0', 'a1', 't1', 'a2', 't2']
for conc in abts_concentrations:
    try:
        params, pcov = curve_fit(expassoc,
                                 data[f'ABTS, {conc} uM'].dropna()['time'],
                                 data[f'ABTS, {conc} uM'].dropna()['consumption.S'],
                                 bounds=([-np.inf, -np.inf, 0, -np.inf, 0], np.inf),
                                 max_nfev=1000 * data[f'ABTS, {conc} uM'].dropna()['time'].shape[0])
    except RuntimeError:
        params = [0 for _ in range(5)]
        print(f"{conc} didn't fit params")
    params_list.append(dict(zip(params_names, params)))
In [161]:
exp_fit = pd.DataFrame(params_list)

Ура, всё зафитилось, давайте нарисуем.

In [162]:
plt.figure(figsize=(10, 25))
for i, conc in enumerate(abts_concentrations):
    plt.subplot(5,2,i+1)
    plt.plot(data[f'ABTS, {conc} uM']['time'], data[f'ABTS, {conc} uM']['consumption.S'], 'o', label='data')
    plt.plot(data[f'ABTS, {conc} uM']['time'],
             expassoc(data[f'ABTS, {conc} uM']['time'], **exp_fit.loc[i, :]), color='red', label='fitted line')
    plt.legend()
    plt.title(f'ABTS, {conc} uM')
    plt.xlabel('time, sec')
    plt.ylabel('O2 concentration, uM')
plt.tight_layout()
In [163]:
exp_fit
Out[163]:
a1 a2 t1 t2 y0
0 -58.093763 59.830878 10.089263 9.910464 -0.124724
1 2.809027 481.402527 5.993436 293583.896887 0.023699
2 -416.815505 419.294970 3.159303 3.181125 0.304376
3 244.597785 -240.517503 16.616377 16.799188 -0.229161
4 -357.298005 363.424705 13.904927 13.795958 -0.145573
5 4.609045 4.804377 11.441321 11.441733 -0.598650
6 8.052366 5.662072 14.708912 14.709158 -1.399644
7 2.061789 25.628443 21.257931 21.256915 -2.931910
8 -7876.862896 7909.918547 112.928008 111.620205 -3.806293
9 18651.161051 -18504.458011 79.007858 78.546488 3.011008

Пока дисперсия точек большая, кривая фитится весьма красиво. Но при больших концентрациях становится видно, что сумма двух экспонент не очень-то правильно описывает эксперимент.

In [164]:
der_speed = pd.DataFrame({'concentration': abts_concentrations, 'speed': (exp_fit['a1'] / exp_fit['t1'] + exp_fit['a2'] / exp_fit['t2']) * 4})
In [165]:
plt.plot(der_speed['concentration'], der_speed['speed'])
Out[165]:
[<matplotlib.lines.Line2D at 0x7f8c5c0b56d8>]

Зависимость вообще получается какая-то не очень красивая. По-видимому, это связано с тем, что сумма экспонент не очень хорошо фитирует кривые. А ещё при концентрации в 15 uM скорость отрицательная. Это может потом вредно сказать на фитинге данных по Михаэлису-Ментен.

Может, удастся лучше зафитить, если в качестве первоначального приближения задать какие-то числа?

In [166]:
params_list = list()
params_names = ['y0', 'a1', 't1', 'a2', 't2']
for conc in abts_concentrations:
    try:
        params, pcov = curve_fit(expassoc,
                                 data[f'ABTS, {conc} uM'].dropna()['time'],
                                 data[f'ABTS, {conc} uM'].dropna()['consumption.S'],
                                 bounds=([-np.inf, -np.inf, 0, -np.inf, 0], np.inf),
                                 max_nfev=1000 * data[f'ABTS, {conc} uM'].dropna()['time'].shape[0],
                                 p0=[-1, 5, 10, 5, 10])
    except RuntimeError:
        params = [0 for _ in range(5)]
        print(f"{conc} didn't fit params")
    params_list.append(dict(zip(params_names, params)))
In [167]:
exp_fit = pd.DataFrame(params_list)
In [168]:
plt.figure(figsize=(10, 25))
for i, conc in enumerate(abts_concentrations):
    plt.subplot(5,2,i+1)
    plt.plot(data[f'ABTS, {conc} uM']['time'], data[f'ABTS, {conc} uM']['consumption.S'], 'o', label='data')
    plt.plot(data[f'ABTS, {conc} uM']['time'],
             expassoc(data[f'ABTS, {conc} uM']['time'], **exp_fit.loc[i, :]), color='red', label='fitted line')
    plt.legend()
    plt.title(f'ABTS, {conc} uM')
    plt.xlabel('time, sec')
    plt.ylabel('O2 concentration, uM')
plt.tight_layout()

На первый взгляд, зафитил так же.

In [169]:
der_speed = pd.DataFrame({'concentration': abts_concentrations,
                          'speed': (exp_fit['a1'] / exp_fit['t1'] + exp_fit['a2'] / exp_fit['t2']) * 4})
In [202]:
plt.plot(der_speed['concentration'], der_speed['speed'])
plt.xlabel("ABTS concentration, uM")
plt.ylabel("Speed")
plt.show()

Но на деле нет отрицательной скорости.

In [173]:
der_speed
Out[173]:
concentration speed
0 7.0 1.116609
1 10.0 1.796462
2 11.5 1.692988
3 15.0 1.612296
4 24.0 2.588190
5 33.0 1.530332
6 50.0 3.367542
7 100.0 5.210582
8 200.0 5.586274
9 500.0 1.924286
In [174]:
plt.plot(initial_speed['speed'], der_speed['speed'], 'o')
plt.xlabel("Speed from initial speed approximation")
plt.ylabel("Speed from exponential approximation")
plt.show()

Скорости несильно похожи друг на друга. Наверняка это связано с плохим качеством одной из аппроксимаций. Почему-то кажется, что проблемы именно в начальных скоростях.

Посмотрим на оценки MSE для каждого из методов для каждой концентрации, чтобы были основания для оценки алгоритмов.

In [175]:
mse_asses = list()
for i, conc in enumerate(abts_concentrations):
    mse_linear = ((data[f'ABTS, {conc} uM'].query('time <= 5')['time'] * o2_speed[i, 0] + o2_speed[i, 1] - data[f'ABTS, {conc} uM'].query('time <= 5')['consumption.S']) ** 2).mean()
    mse_expassoc = ((expassoc(data[f'ABTS, {conc} uM']['time'], **exp_fit.loc[i, :]) - data[f'ABTS, {conc} uM']['consumption.S']) ** 2).mean()
    mse_expassoc_init = ((expassoc(data[f'ABTS, {conc} uM'].query('time <=5')['time'], **exp_fit.loc[i, :]) - data[f'ABTS, {conc} uM'].query('time <=5')['consumption.S']) ** 2).mean()
    mse_asses.append({'concentration': conc, 'initial speed': mse_linear, 'expassoc': mse_expassoc, 'expassoc_init': mse_expassoc_init})
mse_pd = pd.DataFrame(mse_asses)
In [176]:
plt.plot(mse_pd['concentration'], mse_pd['initial speed'], 'o', label='initial speed', alpha=.5)
plt.plot(mse_pd['concentration'], mse_pd['expassoc'], 'o', label='expassoc all', color='red', alpha=.5)
plt.plot(mse_pd['concentration'], mse_pd['expassoc_init'], 'o', label='expassoc initial', color='green', alpha=.5)
plt.legend(loc='upper right')
plt.xlabel('concentration')
plt.ylabel('MSE')
plt.show()

При малых концентрациях ABTS MSE обоих методов примерно одинакова, но затем линейная аппроксимация остаётся стабильно хорошей, а вот expassoc уходит вверх. Это связано с тем, что expassoc не смогла повторить контуры кривых при высоких концентрациях при большом времени. Но здесь у нас была линейная аппроксимация начальных скоростей до 5 секунд, а expassoc фитировала весь датасет. Если посмотреть на expassoc на первых 5 секундах, то ситуация становится хуже.

2.* Пересчитываем expassoc

По совету преподавателя, пересчитываем expassoc по первым 60 секундам.

In [22]:
params_list = list()
params_names = ['y0', 'a1', 't1', 'a2', 't2']
for conc in abts_concentrations:
    try:
        exp_ind = data[f'ABTS, {conc} uM'].dropna()['time'] < 60
        params, pcov = curve_fit(expassoc,
                                 data[f'ABTS, {conc} uM'].dropna()['time'][exp_ind],
                                 data[f'ABTS, {conc} uM'].dropna()['consumption.S'][exp_ind],
                                 bounds=([-np.inf, -np.inf, 0, -np.inf, 0], np.inf))
    except RuntimeError:
        params = [0 for _ in range(5)]
        print(f"{conc} didn't fit params")
    params_list.append(dict(zip(params_names, params)))
24 didn't fit params
33 didn't fit params
100 didn't fit params

Не зафитились 24, 33, 100 мкМ, что очень грустно. Посмотрим на последний успешный фиттинг по всему времени (с увеличенным количеством итераций и начальными параметрами).

In [30]:
params_list = list()
params_names = ['y0', 'a1', 't1', 'a2', 't2']
for conc in abts_concentrations:
    try:
        exp_ind = data[f'ABTS, {conc} uM'].dropna()['time'] < 60
        params, pcov = curve_fit(expassoc,
                                 data[f'ABTS, {conc} uM'].dropna()['time'][exp_ind],
                                 data[f'ABTS, {conc} uM'].dropna()['consumption.S'][exp_ind],
                                 bounds=([-np.inf, -np.inf, 0, -np.inf, 0], np.inf),
                                 max_nfev=1000 * np.sum(exp_ind),
                                 p0=[-1, 5, 10, 5, 10])
    except RuntimeError:
        params = [0 for _ in range(5)]
        print(f"{conc} didn't fit params")
    params_list.append(dict(zip(params_names, params)))

Ну тут всё зафитилось. Посмотрим, как.

In [31]:
exp_fit = pd.DataFrame(params_list)
plt.figure(figsize=(10, 25))
for i, conc in enumerate(abts_concentrations):
    plt.subplot(5,2,i+1)
    plt.plot(data[f'ABTS, {conc} uM']['time'], data[f'ABTS, {conc} uM']['consumption.S'], 'o', label='data')
    plt.plot(data[f'ABTS, {conc} uM']['time'],
             expassoc(data[f'ABTS, {conc} uM']['time'], **exp_fit.loc[i, :]), color='red', label='fitted line')
    plt.legend()
    plt.title(f'ABTS, {conc} uM')
    plt.xlabel('time, sec')
    plt.ylabel('O2 concentration, uM')
plt.tight_layout()

В целом, начальные участки описаны хорошо.

In [32]:
exp_fit
Out[32]:
a1 a2 t1 t2 y0
0 56.150186 -54.413070 9.904652 10.095340 -0.124725
1 13.155444 -10.304575 6.297727 6.297778 0.047341
2 201.888603 -199.311260 13.715801 13.916080 -0.110559
3 6.937021 -3.921582 15.318626 49.027416 -0.156319
4 324.664489 -319.041346 17.180005 17.435424 -0.049528
5 783.886095 -775.347306 6.876933 6.823467 0.182644
6 4.270480 9.608162 16.125950 16.128080 -1.157920
7 3426.056198 -3399.879484 14.875700 14.781419 0.829512
8 99.643482 -4.714491 106.430185 16.983720 0.218748
9 289.899359 -0.426751 371.094203 4.320244 0.339081

Но параметры неоднородны.

In [33]:
der_speed = pd.DataFrame({'concentration': abts_concentrations,
                          'speed': (exp_fit['a1'] / exp_fit['t1'] + exp_fit['a2'] / exp_fit['t2']) * 4})
plt.plot(der_speed['concentration'], der_speed['speed'])
plt.xlabel("ABTS concentration, uM")
plt.ylabel("Speed")
plt.show()
In [38]:
der_speed
Out[38]:
concentration speed
0 7.0 1.116651
1 10.0 1.889603
2 11.5 2.104387
3 15.0 1.840913
4 24.0 2.397418
5 33.0 2.745546
6 50.0 3.442233
7 100.0 1.207659
8 200.0 2.634585
9 500.0 2.729625

А тут вообще дичь какая-то со скоростями.

In [34]:
params_list = list()
params_names = ['y0', 'a1', 't1', 'a2', 't2']
for conc in abts_concentrations:
    try:
        exp_ind = data[f'ABTS, {conc} uM'].dropna()['time'] < 60
        params, pcov = curve_fit(expassoc,
                                 data[f'ABTS, {conc} uM'].dropna()['time'][exp_ind],
                                 data[f'ABTS, {conc} uM'].dropna()['consumption.S'][exp_ind],
                                 bounds=([-np.inf, -np.inf, 0, -np.inf, 0], np.inf),
                                 max_nfev=1000 * np.sum(exp_ind))
    except RuntimeError:
        params = [0 for _ in range(5)]
        print(f"{conc} didn't fit params")
    params_list.append(dict(zip(params_names, params)))

Ну тут всё зафитилось. Посмотрим, как.

In [36]:
exp_fit = pd.DataFrame(params_list)
plt.figure(figsize=(10, 25))
for i, conc in enumerate(abts_concentrations):
    plt.subplot(5,2,i+1)
    plt.plot(data[f'ABTS, {conc} uM']['time'], data[f'ABTS, {conc} uM']['consumption.S'], 'o', label='data')
    plt.plot(data[f'ABTS, {conc} uM']['time'],
             expassoc(data[f'ABTS, {conc} uM']['time'], **exp_fit.loc[i, :]), color='red', label='fitted line')
    plt.legend()
    plt.title(f'ABTS, {conc} uM')
    plt.xlabel('time, sec')
    plt.ylabel('O2 concentration, uM')
plt.tight_layout()
In [37]:
der_speed = pd.DataFrame({'concentration': abts_concentrations,
                          'speed': (exp_fit['a1'] / exp_fit['t1'] + exp_fit['a2'] / exp_fit['t2']) * 4})
plt.plot(der_speed['concentration'], der_speed['speed'])
plt.xlabel("ABTS concentration, uM")
plt.ylabel("Speed")
plt.show()
In [39]:
der_speed
Out[39]:
concentration speed
0 7.0 1.116651
1 10.0 1.889603
2 11.5 2.104387
3 15.0 1.840913
4 24.0 2.397418
5 33.0 2.745546
6 50.0 3.442233
7 100.0 1.207659
8 200.0 2.634585
9 500.0 2.729625

Какая-то дичь со скоростями. Из графиков видно, что для больших концентраций правый хвост плохо аппроксимируется. Может, стоит увеличить время подсчёта так, чтобы захватывали линейный кусок.

In [60]:
right_bounds = {7: 60,
                10: 60,
                11.5: 60,
                15: 70,
                24: 70,
                33: 80,
                50: 80,
                100: 80,
                200: 150,
                500: 250
                }
In [61]:
params_list = list()
params_names = ['y0', 'a1', 't1', 'a2', 't2']
for conc in abts_concentrations:
    try:
        exp_ind = data[f'ABTS, {conc} uM'].dropna()['time'] < right_bounds[conc]
        params, pcov = curve_fit(expassoc,
                                 data[f'ABTS, {conc} uM'].dropna()['time'][exp_ind],
                                 data[f'ABTS, {conc} uM'].dropna()['consumption.S'][exp_ind],
                                 bounds=([-np.inf, -np.inf, 0, -np.inf, 0], np.inf),
                                 max_nfev=1000 * np.sum(exp_ind))
    except RuntimeError:
        params = [0 for _ in range(5)]
        print(f"{conc} didn't fit params")
    params_list.append(dict(zip(params_names, params)))
In [62]:
exp_fit = pd.DataFrame(params_list)
plt.figure(figsize=(10, 25))
for i, conc in enumerate(abts_concentrations):
    plt.subplot(5,2,i+1)
    plt.plot(data[f'ABTS, {conc} uM']['time'], data[f'ABTS, {conc} uM']['consumption.S'], 'o', label='data')
    plt.plot(data[f'ABTS, {conc} uM']['time'],
             expassoc(data[f'ABTS, {conc} uM']['time'], **exp_fit.loc[i, :]), color='red', label='fitted line')
    plt.legend()
    plt.title(f'ABTS, {conc} uM')
    plt.xlabel('time, sec')
    plt.ylabel('O2 concentration, uM')
plt.tight_layout()
In [63]:
der_speed = pd.DataFrame({'concentration': abts_concentrations,
                          'speed': (exp_fit['a1'] / exp_fit['t1'] + exp_fit['a2'] / exp_fit['t2']) * 4})
plt.plot(der_speed['concentration'], der_speed['speed'])
plt.xlabel("ABTS concentration, uM")
plt.ylabel("Speed")
plt.show()

100 мкМ нормально не зафитировалась, но хотя бы скорости более-менее приличные теперь.

Задание 3. Вычисление Km и Vm.

Согласно модели Михаэлиса-Ментен Vm = k2 * E0, поэтому мы можем посчитать заодно и k2.

3.1. Прямые координаты.

In [64]:
def hyperbl(x, p1, p2):
    return p1 * x / (p2 + x)
In [65]:
hyperbl_init, cov = curve_fit(hyperbl, initial_speed['concentration'], initial_speed['speed'])
hyperbl_exp, cov = curve_fit(hyperbl, der_speed['concentration'], der_speed['speed'])
In [83]:
print("Hyperbolic fitting V(S)")
print("Type\tKm\tVm\tk2")
print(f"Init\t{hyperbl_init[1]:.4f}\t{hyperbl_init[0]:.4f}\t{hyperbl_init[0]/protein_concentration / 1e6:.4f}")
print(f"Exp\t{hyperbl_exp[1]:.4f}\t{hyperbl_exp[0]:.4f}\t{hyperbl_exp[0]/protein_concentration / 1e6:.4f}")
Hyperbolic fitting V(S)
Type	Km	Vm	k2
Init	22.2835	3.0715	332.7465
Exp	9.3830	3.4312	371.7134
In [67]:
plt.figure(figsize=(10, 5))
plt.subplot(1,2,1)
plt.plot(initial_speed['concentration'], initial_speed['speed'], 'o', label='data')
plt.plot(initial_speed['concentration'], hyperbl(initial_speed['concentration'], *hyperbl_init), label='fit', color='red')
plt.xlabel('S')
plt.ylabel('V')
plt.legend(loc='lower right')
plt.title('Hyperbolic fitting of initial speed data')
plt.subplot(1,2,2)
plt.plot(der_speed['concentration'], der_speed['speed'], 'o', label='data')
plt.plot(der_speed['concentration'], hyperbl(der_speed['concentration'], *hyperbl_exp), label='fit', color='red')
plt.xlabel('S')
plt.ylabel('V')
plt.legend(loc='lower right')
plt.title('Hyperbolic fitting of expassoc data')
plt.tight_layout()

3.2. Координаты Хейнса.

In [78]:
B_init, A_init = np.polyfit(initial_speed['concentration'], initial_speed['concentration'] / initial_speed['speed'], deg=1)
B_exp, A_exp = np.polyfit(der_speed['concentration'], der_speed['concentration'] / der_speed['speed'], deg=1)
print("Hance fitting V/S(S)")
print("Type\tKm\tVm\tk2")
print(f"Init\t{A_init / B_init:.4f}\t{1 / B_init:.4f}\t{1 / B_init / protein_concentration / 1e6:.4f}")
print(f"Exp\t{A_exp / B_exp:.4f}\t{1 / B_exp:.4f}\t{1 / B_exp / protein_concentration / 1e6:.4f}")
Hance fitting V/S(S)
Type	Km	Vm	k2
Init	17.8182	2.8803	312.0297
Exp	-1.5451	2.7839	301.5922
In [69]:
plt.figure(figsize=(10, 5))
plt.subplot(1,2,1)
plt.plot(initial_speed['concentration'], initial_speed['concentration'] / initial_speed['speed'], 'o', label='data')
plt.plot(initial_speed['concentration'], A_init + B_init * initial_speed['concentration'], label='fit', color='red')
plt.xlabel('S')
plt.ylabel('S/V')
plt.legend(loc='lower right')
plt.title('Hance fitting of initial speed data')
plt.subplot(1,2,2)
plt.plot(der_speed['concentration'], der_speed['concentration'] / der_speed['speed'], 'o', label='data')
plt.plot(der_speed['concentration'], A_exp + B_exp * der_speed['concentration'], label='fit', color='red')
plt.xlabel('S')
plt.ylabel('S/V')
plt.legend(loc='lower right')
plt.title('Hance fitting of expassoc data')
plt.tight_layout()

Для expassoc получили отрицательную Km, что в природе быть не может. Это связано с тем, скорее всего, что мы видим выброс для 500 uM. Надо его убрать и снова зафитить.

In [79]:
B_init, A_init = np.polyfit(initial_speed['concentration'], initial_speed['concentration'] / initial_speed['speed'], deg=1)
B_exp, A_exp = np.polyfit(der_speed['concentration'][:-1], der_speed['concentration'][:-1] / der_speed['speed'][:-1], deg=1)
print("Hance fitting V/S(S)")
print("Type\tKm\tVm\tk2")
print(f"Init\t{A_init / B_init:.4f}\t{1 / B_init:.4f}\t{1 / B_init / protein_concentration / 1e6:.4f}")
print(f"Exp\t{A_exp / B_exp:.4f}\t{1 / B_exp:.4f}\t{1 / B_exp / protein_concentration / 1e6:.4f}")
Hance fitting V/S(S)
Type	Km	Vm	k2
Init	17.8182	2.8803	312.0297
Exp	12.9668	3.7795	409.4501

Уже данные неотрицательные, но значения сильно отличаются от начальных скоростей. Всё же возьмём их, чтобы показать проблематичность экспоненциальных данных.

In [71]:
plt.figure(figsize=(10, 5))
plt.subplot(1,2,1)
plt.plot(initial_speed['concentration'], initial_speed['concentration'] / initial_speed['speed'], 'o', label='data')
plt.plot(initial_speed['concentration'], A_init + B_init * initial_speed['concentration'], label='fit', color='red')
plt.xlabel('S')
plt.ylabel('S/V')
plt.legend(loc='lower right')
plt.title('Hance fitting of initial speed data')
plt.subplot(1,2,2)
plt.plot(der_speed['concentration'], der_speed['concentration'] / der_speed['speed'], 'o', label='data')
plt.plot(der_speed['concentration'], A_exp + B_exp * der_speed['concentration'], label='fit', color='red')
plt.xlabel('S')
plt.ylabel('S/V')
plt.legend(loc='lower right')
plt.title('Hance fitting of expassoc data')
plt.tight_layout()

По-видимому, проблема всё же в 100 и 200 uM.

3.3. Координаты Иди-Хоффсти.

In [80]:
B_init, A_init = np.polyfit(initial_speed['speed'] / initial_speed['concentration'], initial_speed['speed'], deg=1)
B_exp, A_exp = np.polyfit(der_speed['speed'] / der_speed['concentration'], der_speed['speed'], deg=1)
print("IF fitting V(V/S)")
print("Type\tKm\tVm\tk2")
print(f"Init\t{-B_init:.4f}\t{A_init:.4f}\t{A_init / protein_concentration / 1e6:.4f}")
print(f"Exp\t{-B_exp:.4f}\t{A_exp:.4f}\t{A_exp / protein_concentration / 1e6:.4f}")
IF fitting V(V/S)
Type	Km	Vm	k2
Init	19.1171	2.8886	312.9293
Exp	8.7340	3.3279	360.5205
In [73]:
plt.figure(figsize=(10, 5))
plt.subplot(1,2,1)
plt.plot(initial_speed['speed'] / initial_speed['concentration'], initial_speed['speed'], 'o', label='data')
plt.plot(initial_speed['speed'] / initial_speed['concentration'],
         A_init + B_init * initial_speed['speed'] / initial_speed['concentration'], label='fit', color='red')
plt.xlabel('V / S')
plt.ylabel('V')
plt.legend(loc='lower left')
plt.title('IF fitting of initial speed data')
plt.subplot(1,2,2)
plt.plot(der_speed['speed'] / der_speed['concentration'], der_speed['speed'], 'o', label='data')
plt.plot(der_speed['speed'] / der_speed['concentration'],
         A_exp + B_exp * der_speed['speed'] / der_speed['concentration'], label='fit', color='red')
plt.xlabel('V / S')
plt.ylabel('V')
plt.legend(loc='lower left')
plt.title('IF fitting of expassoc data')
plt.tight_layout()

Данные начальных скоростей выглядят в этих координатах более качественно линейно фитируемыми.

3.4. Координаты Лайнуивера-Берка.

In [82]:
B_init, A_init = np.polyfit(1 / initial_speed['concentration'], 1 / initial_speed['speed'], deg=1)
B_exp, A_exp = np.polyfit(1 / der_speed['concentration'], 1 / der_speed['speed'], deg=1)
print("IF fitting V(V/S)")
print("Type\tKm\tVm\tk2")
print(f"Init\t{B_init / A_init:.4f}\t{1 / A_init:.4f}\t{1 / A_init / protein_concentration / 1e6:.4f}")
print(f"Exp\t{B_exp / A_exp:.4f}\t{1 / A_exp:.4f}\t{1 / A_exp / protein_concentration / 1e6:.4f}")
IF fitting V(V/S)
Type	Km	Vm	k2
Init	19.9282	2.8529	309.0660
Exp	13.6202	3.7112	402.0454
In [75]:
plt.figure(figsize=(10, 5))
plt.subplot(1,2,1)
plt.plot(1 / initial_speed['concentration'], 1 / initial_speed['speed'], 'o', label='data')
plt.plot(1 / initial_speed['concentration'],
         A_init + B_init  / initial_speed['concentration'], label='fit', color='red')
plt.xlabel('1 / S')
plt.ylabel('1 / V')
plt.legend(loc='lower right')
plt.title('LB fitting of initial speed data')
plt.subplot(1,2,2)
plt.plot(1 / der_speed['concentration'], 1 / der_speed['speed'], 'o', label='data')
plt.plot(1 / der_speed['concentration'],
         A_exp + B_exp  / der_speed['concentration'], label='fit', color='red')
plt.xlabel('1 / S')
plt.ylabel('1 / V')
plt.legend(loc='lower right')
plt.title('LB fitting of expassoc data')
plt.tight_layout()

И вновь данные начальных скоростей оказались более аккуратными.

Задание 5. Сравнение кинетических параметров.

In [87]:
kinet_params = pd.read_csv('kinetics_params.tsv', sep='\t', index_col=None, header=[0])
In [88]:
kinet_melt = pd.melt(kinet_params, id_vars=['Coordinates', 'Data source'], value_vars=['Km', 'Vm', 'k2'])
In [89]:
plt.figure(figsize=(15,5))
plt.subplot(1,3,1)
sns.scatterplot(data=kinet_params, x='Coordinates', y='Km', hue='Data source')
plt.title('Km')
plt.subplot(1,3,2)
sns.scatterplot(data=kinet_params, x='Coordinates', y='Vm', hue='Data source')
plt.title('Vm')
plt.subplot(1,3,3)
sns.scatterplot(data=kinet_params, x='Coordinates', y='k2', hue='Data source')
plt.title('k2')
plt.tight_layout()

Видно, что кинетические параметры из данных начальных скоростей согласованы друг с другом, а экспоненты -- нет.

Доп. Фитируем первоначальные данные с гиперболой.

In [90]:
params_list = list()
params_names = ['p1', 'p2']
for conc in abts_concentrations:
    try:
        params, pcov = curve_fit(hyperbl,
                                 data[f'ABTS, {conc} uM'].dropna()['time'],
                                 data[f'ABTS, {conc} uM'].dropna()['consumption.S'],
                                 bounds=(0, np.inf))
    except RuntimeError:
        params = [0 for _ in range(5)]
        print(f"{conc} didn't fit params")
    params_list.append(dict(zip(params_names, params)))
In [91]:
hyper_fit = pd.DataFrame(params_list)
In [92]:
plt.figure(figsize=(10, 25))
for i, conc in enumerate(abts_concentrations):
    plt.subplot(5,2,i+1)
    plt.plot(data[f'ABTS, {conc} uM']['time'], data[f'ABTS, {conc} uM']['consumption.S'], 'o', label='data')
    plt.plot(data[f'ABTS, {conc} uM']['time'],
             hyperbl(data[f'ABTS, {conc} uM']['time'], **hyper_fit.loc[i, :]), color='red', label='fitted line')
    plt.legend()
    plt.title(f'ABTS, {conc} uM')
    plt.xlabel('time, sec')
    plt.ylabel('O2 concentration, uM')
plt.tight_layout()

Зафитировала хуже, чем expassoc.

In [93]:
hyper_speed = pd.DataFrame({'concentration': abts_concentrations, 'speed': hyper_fit['p1'] / hyper_fit['p2'] * 4})
In [94]:
plt.plot(hyper_speed['concentration'], hyper_speed['speed'])
plt.xlabel("ABTS, uM")
plt.ylabel("Speed")
plt.show()

Сравним все 3 метода.

In [95]:
plt.plot(initial_speed['concentration'], initial_speed['speed'], label='Initial')
plt.plot(der_speed['concentration'], der_speed['speed'], color='red', label='Expassoc')
plt.plot(hyper_speed['concentration'], hyper_speed['speed'], color='green', label='Hyperbl')
plt.xlabel("ABTS, uM")
plt.ylabel("Speed")
plt.legend()
plt.show()

Гипербола ещё хуже, чем expassoc, поэтому дальше и не будем считать из неё кинетические параметры.