Какая кривая (или модель) должна соответствовать моим процентным данным?

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? Я хочу прояснить, что А) низкий уровень копий вируса = низкий GCC, и что B) после того, как определенное количество вируса копирует плато GCC.

Я исследовал множество различных методов - GAM, LOESS, логистику, кусочно - но я не знаю, как определить, какой метод лучше всего подходит для моих данных.

РЕДАКТИРОВАТЬ: это данные:

>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
teaelleceecee
источник
6
Похоже, что логистическая регрессия была бы лучшей, так как она ограничена от 0 до 100%.
mkt - Восстановить Монику
1
Попробуйте (2) кусочную (линейную) модель.
user158565
3
попробуйте добавить method.args=list(family=quasibinomial))аргументы geom_smooth()в исходный код ggplot.
Бен Болкер
4
PS Я бы посоветовал вам не подавлять стандартные ошибки с se=FALSE. Всегда приятно показать людям, насколько велика эта неопределенность ...
Бен Болкер,
2
У вас недостаточно точек данных в области перехода, чтобы с уверенностью утверждать, что существует плавная кривая. Я мог бы так же легко приспособить функцию Хевисайда к точкам, которые вы нам показываете.
Карл Виттофт

Ответы:

6

Еще один способ сделать это - использовать байесовскую формулировку. Начать с нее может быть немного тяжело, но это значительно упрощает формулировку специфики вашей проблемы, а также позволяет лучше понять, где находится «неопределенность». является

Stan - это сэмплер Монте-Карло с относительно простым в использовании программным интерфейсом, библиотеки доступны для 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 выглядят хорошо и дают график очень похожий на другие ответы:

данные графика и ручная подгонка

чтобы «подогнать» эту функцию с помощью Stan, нам нужно определить нашу модель, используя ее собственный язык, который представляет собой смесь между 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. И, как рекомендует Бен Болкер, для решения возможных проблем, вызванных строгими предположениями биномиального распределения относительно дисперсии, используйте квазибиномиальное распределение вместо.

Я сделал несколько предположений, основанных на вашем коде, например, что вас интересуют 2 вируса, и они могут демонстрировать разные паттерны (то есть может быть взаимодействие между типом вируса и количеством копий).

Во-первых, модель подойдет:

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). Поэтому я рекомендую кодировать это самостоятельно, хотя это больше работы.

mkt - восстановить монику
источник
7
Я поддерживаю этот ответ, но я бы хотел пояснить: я бы назвал это дробной логистической регрессией. Я думаю, что этот термин будет более широко признанным. Когда большинство людей слышат «логистическую регрессию», я уверен, что они думают о зависимой переменной 0/1. Один хороший ответ Stackexchange, касающийся этой номенклатуры, находится здесь: stats.stackexchange.com/questions/216122/…
Ng
2
@teaelleceecee Вы, очевидно, должны сначала разделить покрытие на 100.
Ник Кокс
4
используйте, family=quasibinomial()чтобы избежать предупреждения (и основных проблем со слишком строгими предположениями об отклонениях). Воспользуйтесь советом @ mkt по другой проблеме.
Бен Болкер
2
Это может сработать, но я хотел бы предупредить людей, что перед настройкой функции у вас должна быть предпосылка, что ваши данные на самом деле должны следовать этой функции. В противном случае вы выбираете случайную стрельбу, когда выбираете подходящую функцию, и результаты могут вас обмануть.
Карл Виттофт
6
@CarlWitthoft Мы слышим проповедь, но грешники вне службы. Какие предварительные предпосылки побудили вас предложить функцию Хевисайда в других комментариях? Биология здесь не похожа на переход на остром пороге. Насколько я понимаю, факт исследования здесь заключается в том, что формальная теория слабее данных. Я согласен: если люди думают, что ступенчатая функция имеет смысл, они должны соответствовать ей.
Ник Кокс
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 (логистика с четырьмя параметрами), как ограниченная, так и неограниченная, с уравнением по К. А. Гольштейну, М. Гриффину, Дж. Хонгу, П. Д. Сэмпсону, «Статистический метод определения и сравнения пределов обнаружения биоанализов», Anal , Химреагент 87 (2015) 9795-9801. Уравнение 4PL показано на обоих рисунках, а значения параметров следующие: a = нижняя асимптота, b = коэффициент наклона, c = точка перегиба и d = верхняя асимптота.

На рисунке 1 a равно 0%, а d равно 100%:

Рис. 1 ограниченный и D

Рисунок 2 не имеет ограничений на 4 параметра в уравнении 4PL:

Рис. 2 Без ограничений

Это было весело, я не претендую на то, что знаю что-то биологическое, и будет интересно посмотреть, как все это устроится!

Эд V
источник
Спасибо, это действительно полезно. Просто интересно, вы делали это в MATLAB с помощью функции fit?
Teaelleceecee
1
Я использовал Igor Pro с пользовательской функцией, показанной на рисунках. Я использую Igor Pro и его предшественника (Igor) с 1988 года, но многие другие программы могут выполнять подгонку кривой, например Origin Pro и очень недорогой Kaleidagraph. И кажется, что у вас есть R и (возможно?) Доступ к Matlab, о котором я ничего не знаю, кроме того, что они чрезвычайно способны. Удачи вам в этом, и я надеюсь, что вы получите хорошие новости в следующий раз, когда будете обсуждать вопросы с руководителями! Также спасибо за размещение данных!
Эд V
2

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

участок

РЕДАКТИРОВАТЬ: Теперь, когда вопрос был отредактирован для включения фактических данных, вот график с использованием вышеуказанного уравнения с 3 параметрами и начальных оценок параметров.

Plot2

Джеймс Филлипс
источник
В извлечении данных произошла ошибка: у вас есть куча отрицательных процентных значений. Кроме того, ваши максимальные значения составляют около 90% вместо 100%, как в исходном графике. По какой-то причине вы можете сместить все примерно на 10%.
mkt - Восстановить Монику
Мех - это полуавтоматически извлеченные данные, требуются исходные данные. Этого обычно достаточно для поиска по уравнениям, и, конечно, не для окончательных результатов - вот почему я сказал использовать мои значения параметров extract-o-fit в качестве начальных оценок параметров исходных данных.
Джеймс Филлипс
Обратите внимание, что, поскольку фактические данные теперь добавлены в сообщение, я обновил этот ответ, используя обновленные данные.
Джеймс Филлипс
Просто повторюсь: применение, например, функции Хевисайда, может привести к аналогичным значениям ошибок.
Карл Виттофт
1
@JamesPhillips Я постараюсь сделать это (Heaviside -> errorbars или эквивалентный)
Карл Виттофт
2

Так как мне пришлось открыть свой большой рот о Хевисайде, вот результаты. Я установил точку перехода на log10 (viruscopies) = 2.5. Затем я вычислил стандартные отклонения двух половин набора данных, то есть Хевисайд предполагает, что данные с обеих сторон имеют все производные = 0.

Стандартное отклонение от
правой стороны = 4,76 стандартное отклонение от правой стороны = 7,72

Так как оказывается, что в каждой партии 15 выборок, среднее значение стандартного отклонения составляет 6,24.

Если предположить, что «RMSE», приведенная в других ответах, является «среднеквадратичной ошибкой», то функция Хевисайда, по-видимому, выполняет, по крайней мере, даже лучше, чем большинство «Z-кривой» (заимствовано из номенклатуры фотографического ответа). Вот.

редактировать

Бесполезный график, но запрошенный в комментариях:

Кривая Хевисайда

Карл Виттофт
источник
Woukd, пожалуйста, опубликуйте модель и график рассеяния аналогично тому, что было сделано в других ответах? Мне очень любопытно увидеть эти результаты и сравнить. Пожалуйста, также добавьте значения RMSE и R-квадрат для сравнения. Лично я никогда не использовал функцию Хевисайда и нахожу это очень интересным.
Джеймс Филлипс
R2
Я имел в виду составить сюжет, похожий на те, что были сделаны в других ответах, с целью прямого сравнения с этими ответами.
Джеймс Филлипс
2
@JamesPhillips у тебя осталось два желания. Выбирай мудро :-)
Карл Виттофт
Спасибо за сюжет. Я наблюдаю, что на всех графиках в других ответах построенное уравнение соответствует кривой форме данных в правом верхнем углу, а вы - нет, как и природа функции Хевисайда. Это визуально противоречит вашему утверждению о том, что функция Хевисайда подойдет так же, как и уравнения, приведенные в других ответах - поэтому я ранее запрашивал значения RMSE и R-квадрат, я подозревал, что функция Хевисайда не будет иметь форму данных в этом регионе и может привести к худшим значениям для этих статистических данных.
Джеймс Филлипс