Яку криву (або модель) я повинен відповідати моїм процентним даним?


15

Я намагаюся створити фігуру, яка показує взаємозв'язок між вірусними копіями та покриттям геному (GCC). Ось так виглядають мої дані:

Вірусне навантаження проти GCC

Спочатку я просто побудував лінійну регресію, але мої керівники сказали мені, що це неправильно, і спробувати сигмоїдальну криву. Тому я зробив це за допомогою geom_smooth:

library(scales)
ggplot(scatter_plot_new, aes(x = Copies_per_uL, y = Genome_cov, colour = Virus)) +
    geom_point() +
    scale_x_continuous(trans = log10_trans(), breaks = trans_breaks("log10", function(x) 10^x), labels = trans_format("log10", math_format(10^.x))) +
        geom_smooth(method = "gam", formula = y ~ s(x), se = FALSE, size = 1) +
    theme_bw() +
    theme(legend.position = 'top', legend.text = element_text(size = 10), legend.title = element_text(size = 12), axis.text = element_text(size = 10), axis.title = element_text(size=12), axis.title.y = element_text(margin = margin (r = 10)), axis.title.x = element_text(margin = margin(t = 10))) +
    labs(x = "Virus copies/µL", y = "GCC (%)") +
    scale_y_continuous(breaks=c(25,50,75,100))

Вірусне навантаження проти GCC - geom_smooth

Однак мої керівники стверджують, що це теж неправильно, оскільки криві дозволяють виглядати так, що GCC може перевищувати 100%, що не може.

Моє запитання: який найкращий спосіб показати зв’язок між копіями вірусів та GCC? Я хочу зрозуміти, що A) низькі копії вірусів = низький рівень GCC і B) після певної кількості вірусу копіюють плато GCC.

Я досліджував багато різних методів - GAM, LOESS, логістичний, кусочно - але я не знаю, як сказати, що є найкращим методом для моїх даних.

EDIT: це дані:

>print(scatter_plot_new)  
Subsample   Virus   Genome_cov  Copies_per_uL
1   S1.1_RRAV   RRAV    100 92500
2   S1.2_RRAV   RRAV    100 95900
3   S1.3_RRAV   RRAV    100 92900
4   S2.1_RRAV   RRAV    100 4049.54
5   S2.2_RRAV   RRAV    96.9935 3809
6   S2.3_RRAV   RRAV    94.5054 3695.06
7   S3.1_RRAV   RRAV    3.7235  86.37
8   S3.2_RRAV   RRAV    11.8186 84.2
9   S3.3_RRAV   RRAV    11.0929 95.2
10  S4.1_RRAV   RRAV    0   2.12
11  S4.2_RRAV   RRAV    5.0799  2.71
12  S4.3_RRAV   RRAV    0   2.39
13  S5.1_RRAV   RRAV    4.9503  0.16
14  S5.2_RRAV   RRAV    0   0.08
15  S5.3_RRAV   RRAV    4.4147  0.08
16  S1.1_UMAV   UMAV    5.7666  1.38
17  S1.2_UMAV   UMAV    26.0379 1.72
18  S1.3_UMAV   UMAV    7.4128  2.52
19  S2.1_UMAV   UMAV    21.172  31.06
20  S2.2_UMAV   UMAV    16.1663 29.87
21  S2.3_UMAV   UMAV    9.121   32.82
22  S3.1_UMAV   UMAV    92.903  627.24
23  S3.2_UMAV   UMAV    83.0314 615.36
24  S3.3_UMAV   UMAV    90.3458 632.67
25  S4.1_UMAV   UMAV    98.6696 11180
26  S4.2_UMAV   UMAV    98.8405 12720
27  S4.3_UMAV   UMAV    98.7939 8680
28  S5.1_UMAV   UMAV    98.6489 318200
29  S5.2_UMAV   UMAV    99.1303 346100
30  S5.3_UMAV   UMAV    98.8767 345100

6
Здається, що найкраще буде логістична регресія, оскільки вона обмежена між 0 і 100%.
mkt -

1
Спробуйте (2) лінійну модель.
користувач158565

3
спробуйте додати method.args=list(family=quasibinomial))аргументи до geom_smooth()свого початкового коду ggplot.
Бен Болкер

4
PS Я б закликав вас не придушувати стандартні помилки за допомогою se=FALSE. Завжди приємно показувати людям, наскільки насправді велика невизначеність ...
Бен Болкер

2
У вас не вистачає точок даних у перехідному регіоні, щоб заявити будь-яким органам, що існує плавна крива. Я міг би так само легко підігнати функцію Heaviside до точок, які ви нам показуєте.
Карл Віттофт

Відповіді:


6

Ще один спосіб зробити це - використовувати байєсівську формулювання, для початку це може бути трохи важким, але це, як правило, набагато простіше висловити специфіку вашої проблеми, а також отримати кращі уявлення про те, де "невизначеність" є

Стен - пробовідбірник Монте-Карло з відносно простим у використанні програмним інтерфейсом, бібліотеки доступні для R та інших, але я тут використовую Python

ми використовуємо сигмоїд, як і всі інші: він має біохімічні мотивації, а також математично дуже зручний для роботи. приємною параметризацією для цього завдання є:

import numpy as np

def sigfn(x, alpha, beta):
    return 1 / (1 + np.exp(-(x - alpha) * beta))

де alphaвизначається середня точка сигмоподібної кривої (тобто там, де вона перетинає 50%) і betaвизначає нахил, значення ближче до нуля більш плоскі

щоб показати, як це виглядає, ми можемо використати ваші дані та скласти це за допомогою:

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

df = pd.read_table('raw_data.txt', delim_whitespace=True)
df.columns = ['subsample', 'virus', 'coverage', 'copies']
df.coverage /= 100

x = np.logspace(-1, 6, 201)
plt.semilogx(x, sigfn(np.log(x), 5.5, 3), label='sigfn', color='C2')

sns.scatterplot(df.copies, df.coverage, hue=df.virus, edgecolor='none')

де raw_data.txtмістяться дані, які ви надали, і я перетворив висвітлення на щось більш корисне. коефіцієнти 5,5 і 3 виглядають непогано і дають змову дуже схоже на інші відповіді:

дані сюжету та вручну

щоб "підходити" до цієї функції за допомогою Стен, нам потрібно визначити нашу модель, використовуючи її власну мову, яка є сумішшю між R і C ++. проста модель буде щось на зразок:

data {
    int<lower=1> N;  // number of rows
    vector[N] log_copies;
    vector<lower=0,upper=1>[N] coverage;
}
parameters {
    real alpha;
    real beta;
    real<lower=0> sigma;
}
model {
    vector[N] mu;
    mu = 1 ./ (1 + exp(-(log_copies - alpha) * beta));

    sigma ~ cauchy(0, 0.1);
    alpha ~ normal(0, 5);
    beta ~ normal(0, 5);

    coverage ~ normal(mu, sigma);
}

який, сподіваємось, читає добре у нас є dataблок, який визначає дані, які ми очікуємо при вибірці моделі, parametersвизначає речі, які вибираються, і modelвизначає функцію ймовірності. Ви говорите Стен "скласти" модель, яка займає деякий час, а потім ви зможете спробувати з неї деякі дані. наприклад:

import pystan

model = pystan.StanModel(model_code=code)
model.sampling(data=dict(
    N=len(df),
    log_copies=np.log(df.copies),
    coverage=df.coverage,
), iter=10000, chains=4, thin=10)

import arviz
arviz.plot_trace(fit)

arviz полегшує діагностичні діаграми, а друк відповідності дає вам хороший підсумок параметрів стилю R:

4 chains, each with iter=10000; warmup=5000; thin=10; 
post-warmup draws per chain=500, total post-warmup draws=2000.

        mean se_mean     sd   2.5%    25%    50%    75%  97.5%  n_eff   Rhat
alpha   5.51  6.0e-3   0.26   4.96   5.36   5.49   5.64   6.12   1849    1.0
beta    2.89    0.04   1.71   1.55   1.98   2.32   2.95   8.08   1698    1.0
sigma   0.08  2.7e-4   0.01   0.06   0.07   0.08   0.09    0.1   1790    1.0
lp__   57.12    0.04   1.76   52.9   56.1  57.58  58.51  59.19   1647    1.0

велике стандартне відхилення betaговорить про те, що дані дійсно не надають багато інформації про цей параметр. також деякі відповіді, що дають 10+ значущих цифр у їхніх моделях, дещо завищують речі

оскільки деякі відповіді зазначали, що кожному вірусу, можливо, потрібні власні параметри, я розширив модель, щоб дозволити alphaта betaзмінити залежно від "Вірус". все це стає трохи химерно, але два віруси майже напевно мають різні alphaзначення (тобто вам потрібно більше копій / мкл RRAV для одного покриття), і сюжет, що показує це:

графік даних та зразки МС

дані такі ж, як і раніше, але я намалював криву для 40 зразків задньої частини. UMAVвидається відносно добре визначеним, хоча RRAVможе дотримуватися того ж схилу і потребує більшого числа копій або мати більш крутий нахил та подібний кількість копій. більша частина задньої маси потребує більшої кількості копій, але ця невизначеність може пояснити деякі відмінності в інших відповідях, що знаходять різні речі

Я в основному використовується відповівши цю вправу , щоб поліпшити свої знання Стана, і я поставив Jupyter зошит це тут в разі , якщо хто зацікавлений / хоче повторити це.


14

(Відредаговано з урахуванням коментарів нижче. Дякуємо @BenBolker & @WeiwenNg за корисну інформацію.)

Встановити дробову логістичну регресію до даних. Він добре підходить до процентних даних, які обмежені між 0 і 100% і теоретично обґрунтовані у багатьох областях біології.

Зауважте, що вам, можливо, доведеться розділити всі значення на 100, щоб відповідати цьому, оскільки програми часто очікують, що дані будуть знаходитись у межах від 0 до 1. І як рекомендує Бен Болкер, для вирішення можливих проблем, викликаних суворими припущеннями біноміального розподілу щодо дисперсії, використовуйте квазібіноміальний розподіл натомість.

Я зробив деякі припущення на основі вашого коду, наприклад, що є два віруси, які вас цікавлять, і вони можуть показувати різні шаблони (тобто може бути взаємодія між типом вірусу та кількістю копій).

По-перше, модель підходить:

dat <- read.csv('Book1.csv')
dat$logcopies <- log10(dat$Copies_per_uL)
dat$Genome_cov_norm <- dat$Genome_cov/100

fit <- glm(Genome_cov_norm ~ logcopies * Virus, data = dat, family = quasibinomial())
summary(fit)


Call:
glm(formula = Genome_cov_norm ~ logcopies * Virus, family = quasibinomial(), 
    data = dat)

Deviance Residuals: 
     Min        1Q    Median        3Q       Max  
-0.55073  -0.13362   0.07825   0.20362   0.70086  

Coefficients:
                    Estimate Std. Error t value Pr(>|t|)  
(Intercept)          -5.9702     2.8857  -2.069   0.0486 *
logcopies             2.3262     1.0961   2.122   0.0435 *
VirusUMAV             2.6147     3.3049   0.791   0.4360  
logcopies:VirusUMAV  -0.6028     1.3173  -0.458   0.6510  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for quasibinomial family taken to be 0.6934319)

    Null deviance: 30.4473  on 29  degrees of freedom
Residual deviance:  2.7033  on 26  degrees of freedom

Якщо ви довіряєте p-значенням, вихід не говорить про те, що два віруси суттєво відрізняються. Це на відміну від наведених нижче результатів @ NickCox, хоча ми використовували різні методи. Я не був би дуже впевнений в будь-якому випадку з 30 точками даних.

По-друге, сюжет:

Не важко кодувати спосіб візуалізації виводу самостійно, але, здається, є пакет ggPredict, який зробить більшу частину роботи за вас (не можу поручитися за це, я сам цього не пробував). Код буде виглядати приблизно так:

library(ggiraphExtra)
ggPredict(fit) + theme_bw(base_size = 20) + geom_line(size = 2) 

Оновлення: я більше не рекомендую код або функцію ggPredict загалом. Спробувавши це, я виявив, що нанесені точки не точно відображають вхідні дані, а замість цього змінюються з якихось химерних причин (деякі з накреслених точок були вище 1 і нижче 0). Тому я рекомендую кодувати його самостійно, хоча це більше роботи.


7
Я схвалюю цю відповідь, але я хотів би зробити уточнення: я б назвав цю часткову логістичну регресію. Я думаю, що цей термін був би більш визнаним. Коли більшість людей чують "логістичну регресію", я думаю, що вони думають про змінну, що залежить від 0/1. Один хороший відповідь Stackexchange, що стосується цієї номенклатури, є тут: stats.stackexchange.com/questions/216122/…
Ng

2
@teaelleceecee Ви, очевидно, повинні розділити покриття спочатку на 100.
Нік Кокс

4
використовувати family=quasibinomial()для уникнення попередження (та основних проблем із занадто суворими припущеннями про відхилення). Скористайтеся порадою @ mkt щодо іншої проблеми.
Бен Болкер

2
Це може спрацювати, але я хотів би попередити людей, що перед встановленням функції ви повинні мати приміщення, згідно з яким ваші дані насправді повинні відповідати цій функції. В іншому випадку ви дуже багато стріляєтеся навмання, коли вибираєте функцію підгонки, і вас можуть обдурити результати.
Карл Віттофт

6
@CarlWitthoft Ми чуємо проповідь, але є грішниками поза службою. Яка попередня передумова спонукала вас запропонувати функцію Heaviside в інших коментарях? Біологія тут не нагадує перехід із різким порогом. Фактом дослідження тут, як я розумію, є те, що формальна теорія слабша за дані. Я погоджуюся: якщо люди думають, що функція кроку має сенс, вони повинні відповідати.
Нік Кокс

11

Це не інша відповідь від @mkt, але графіки, зокрема, не впишуться в коментар. Я спершу підганяю логістичну криву в Stata (після реєстрації прогноктора) до всіх даних і отримую цей графік

введіть тут опис зображення

Рівняння є

100 invlogit(-4.192654 + 1.880951 log10( Copies))

Тепер я підганяю криві окремо для кожного вірусу за найпростішим сценарієм вірусу, що визначає змінну індикатора. Ось для запису є сценарій Stata:

clear 
input id str9 Subsample   str4 Virus   Genome_cov  Copies_per_uL
1   S1.1_RRAV   RRAV    100 92500
2   S1.2_RRAV   RRAV    100 95900
3   S1.3_RRAV   RRAV    100 92900
4   S2.1_RRAV   RRAV    100 4049.54
5   S2.2_RRAV   RRAV    96.9935 3809
6   S2.3_RRAV   RRAV    94.5054 3695.06
7   S3.1_RRAV   RRAV    3.7235  86.37
8   S3.2_RRAV   RRAV    11.8186 84.2
9   S3.3_RRAV   RRAV    11.0929 95.2
10  S4.1_RRAV   RRAV    0   2.12
11  S4.2_RRAV   RRAV    5.0799  2.71
12  S4.3_RRAV   RRAV    0   2.39
13  S5.1_RRAV   RRAV    4.9503  0.16
14  S5.2_RRAV   RRAV    0   0.08
15  S5.3_RRAV   RRAV    4.4147  0.08
16  S1.1_UMAV   UMAV    5.7666  1.38
17  S1.2_UMAV   UMAV    26.0379 1.72
18  S1.3_UMAV   UMAV    7.4128  2.52
19  S2.1_UMAV   UMAV    21.172  31.06
20  S2.2_UMAV   UMAV    16.1663 29.87
21  S2.3_UMAV   UMAV    9.121   32.82
22  S3.1_UMAV   UMAV    92.903  627.24
23  S3.2_UMAV   UMAV    83.0314 615.36
24  S3.3_UMAV   UMAV    90.3458 632.67
25  S4.1_UMAV   UMAV    98.6696 11180
26  S4.2_UMAV   UMAV    98.8405 12720
27  S4.3_UMAV   UMAV    98.7939 8680
28  S5.1_UMAV   UMAV    98.6489 318200
29  S5.2_UMAV   UMAV    99.1303 346100
30  S5.3_UMAV   UMAV    98.8767 345100
end 

gen log10Copies = log10(Copies)
gen Genome_cov_pr = Genome_cov / 100
encode Virus, gen(virus)
set seed 2803 
fracreg logit Genome_cov_pr log10Copies i.virus, vce(bootstrap, reps(10000)) 

twoway function invlogit(-5.055519 + 1.961538 * x), lc(orange) ra(log10Copies)      ///
|| function invlogit(-5.055519 + 1.233273 + 1.961538 * x), ra(log10Copies) lc(blue) ///
|| scatter Genome_cov_pr log10Copies if Virus == "RRAV", mc(orange) ms(Oh)          ///
|| scatter Genome_cov_pr log10Copies if Virus == "UMAV", mc(blue) ms(+)             ///
legend(order(4 "UMAV" 3 "RRAV") pos(11) col(1) ring(0))                             ///
xla(-1 "0.1" 0 "1" 1 "10" 2 "100" 3 "10{sup:3}" 4 "10{sup:4}" 5 "10{sup:5}")        ///
yla(0 .25 "25" .5 "50" .75 "75" 1 "100", ang(h))                                    ///
ytitle(Genome coverage (%)) xtitle(Genome copies / {&mu}L) scheme(s1color) 

Це важко підштовхує крихітний набір даних, але значення P для вірусу виглядає сприятливим для суміщення двох кривих спільно.

Fractional logistic regression                  Number of obs     =         30
                                                Replications      =     10,000
                                                Wald chi2(2)      =      48.14
                                                Prob > chi2       =     0.0000
Log pseudolikelihood = -6.9603063               Pseudo R2         =     0.6646

-------------------------------------------------------------------------------
              |   Observed   Bootstrap                         Normal-based
Genome_cov_pr |      Coef.   Std. Err.      z    P>|z|     [95% Conf. Interval]
--------------+----------------------------------------------------------------
  log10Copies |   1.961538   .2893965     6.78   0.000     1.394331    2.528745
              |
        virus |
        UMAV  |   1.233273   .5557609     2.22   0.026     .1440018    2.322544
        _cons |  -5.055519   .8971009    -5.64   0.000    -6.813805   -3.297234
-------------------------------------------------------------------------------

введіть тут опис зображення


3

Спробуйте сигмоподібну функцію. Існує багато формулювань такої форми, включаючи логістичну криву. Гіперболічний дотичний - ще один популярний вибір.

Враховуючи сюжети, я також не можу виключити функцію простого кроку. Боюся, ви не зможете розмежовувати між ступеневою функцією та будь-якою кількістю сигмоїдних специфікацій. Ви не маєте жодних спостережень, коли ваш відсоток знаходиться в діапазоні 50%, тому проста формулювання кроків може бути самим стильним вибором, який виконує не гірше, ніж складніші моделі


σ(x)=12(1+tanhx2)

2
@JG "сигмоїд" - це загальний термін для S-кривої, наскільки я переживаю, але ви праві вказати на зв'язок між двома специфікаціями
Аксакал

2

Ось 4PL (4-логічний логістичний) підхід, як обмежений, так і необмежений, з рівнянням за К.А. Голштейна, М.Гріффіна, Дж. Гонга, П.Д. Сампсона, «Статистичний метод визначення та порівняння меж виявлення біологічних аналізів», Anal . Хім. 87 (2015) 9795-9801. Рівняння 4PL показано на обох малюнках, а значення параметрів такі: a = нижня асимптота, b = коефіцієнт нахилу, c = точка перегину та d = верхня асимптота.

Фігура 1 обмежує a дорівнює 0%, а d дорівнює 100%:

Рис. 1. Обмежений а & д

Фігура 2 не обмежує 4 параметри рівняння 4PL:

Рис. 2 Без обмежень

Це було весело, я не прикидаюсь знати щось біологічне, і мені буде цікаво подивитися, як це все розгортається!


Дякую, це справді корисно. Цікаво, що ти робив це в MATLAB з функцією fit?
teaelleceecee

1
Я використовував Ігор Про з визначеною користувачем функцією користувача, показаною на малюнках. Я використовую Ігоря Про та його попередника (Ігоря) з 1988 року, але безліч інших програм може зробити підгонку кривої, наприклад, Origin Pro та дуже недорогий Kaleidagraph. І, здається, у вас є R та (можливо?) Доступ до Matlab, про який я нічого не знаю, крім того, що вони надзвичайно здатні. Найкращий успіх у цьому, і я сподіваюся, що ви отримаєте хороші новини наступного разу, коли ви обговорите речі з керівниками! Також дякую за публікацію даних!
Ред V

2

Я витягнув дані з вашого розсіювача, і мій пошук рівнянь виявив 3-параметричне рівняння логістичного типу як хороший кандидат: "y = a / (1,0 + b * exp (-1,0 * c * x))", де " x "- це база журналу 10 для вашої ділянки. Встановлені параметри були a = 9.0005947126706630E + 01, b = 1.2831794858584102E + 07, і c = 6.6483431489473155E + 00 для моїх витягнутих даних, відповідність (log 10 x) вихідних даних повинна дати подібні результати, якщо ви повторно підходите вихідні дані, використовуючи мої значення в якості початкових оцінок параметрів. Мої значення параметрів дають R-квадрат = 0,983 і RMSE = 5,625 на видобутих даних.

сюжет

EDIT: Тепер, коли питання було відредаговано, щоб включити фактичні дані, ось графік з використанням вищевказаного 3-параметричного рівняння та початкових оцінок параметрів.

сюжет2


Здається, виникла помилка у вилученні даних: у вас є маса негативних відсоткових значень. Крім того, ваші максимальні значення становлять приблизно 90% замість 100%, як у вихідному графіку. Можливо, у вас чомусь буде компенсовано приблизно на 10%.
mkt -

Мех - це вилучені дані вручну, потрібні оригінальні дані. Зазвичай цього достатньо для пошуку рівнянь, і, звичайно, не для кінцевих результатів - саме тому я сказав використовувати свої параметри extra-o-fit як вихідні оцінки параметрів для вихідних даних.
Джеймс Філліпс

Зверніть увагу, оскільки фактичні дані тепер додані до публікації, я оновив цю відповідь, використовуючи оновлені дані.
Джеймс Філліпс

Ще раз наголошую: застосування, наприклад, функції Heaviside, може отримати подібні значення помилок.
Карл Віттофт

1
@JamesPhillips Я спробую це зробити (Heaviside -> смуги помилок або еквівалент)
Carl Witthoft

2

Оскільки мені довелося відкрити великий рот щодо Heaviside, ось результати. Я встановив точку переходу до log10 (вірускопії) = 2,5. Тоді я обчислював стандартні відхилення двох половин набору даних - тобто Heaviside припускає, що дані з будь-якої сторони мають всі похідні = 0.

RH сторона std dev = 4,76
LH сторона std dev = 7,72

Оскільки виявляється, що в кожній партії є 15 зразків, загальний показник std - це середнє значення, або 6,24.

Якщо припустити, що "RMSE", цитований в інших відповідях, - це "помилка RMS" загалом, функція Heaviside, здавалося б, працює як мінімум так само добре, як і, якщо не краще, ніж більша частина "кривої Z" (запозичена з номенклатури фотовідповідей) тут.

редагувати

Графік марний, але запитується в коментарях:

Крива на високій стороні підходить


Ви б, будь ласка, опублікували модель і розпорошили аналогічно тому, що було зроблено в інших відповідях? Мені найцікавіше бачити ці результати і порівнювати. Для порівняння також додайте значення RMSE та R-квадрата. Я особисто ніколи не використовував функцію Heaviside і вважаю це дуже цікавим.
Джеймс Філліпс

R2

Мій сенс полягав у тому, щоб зробити сюжет подібним до тих, що зроблені в інших відповідях, з метою прямого порівняння з цими відповідями.
Джеймс Філліпс

2
@JamesPhillips у вас залишилось два побажання. Вибирайте розумно :-)
Карл Віттофт

Дякую за сюжет. Я зауважую, що на всіх графіках в інших відповідях графічне рівняння слідує за вигнутою формою даних у верхньому правому куті - вашої немає, як це характер природи Heaviside. Це візуально здається, що суперечить вашому твердженню, що функція Heaviside буде виконувати так само, як і рівняння, розміщені в інших відповідях - саме тому я раніше вимагав значення RMSE і R-квадрата, я підозрював, що функція Heaviside не буде відповідати формі даних у цьому регіоні та може дати гірші значення для відповідних статистичних даних.
Джеймс Філліпс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.