Аппроксимация кривой R (множественная экспонента) с NLS2 и NLS

У меня есть некоторые трудности с получением конкретной кривой, подходящей для R, в то время как она отлично работает в коммерческой программе подбора кривой.

Формула, которой должны соответствовать данные:

y(t) = A * exp(-a*(t)) + B * exp(-b*(t)) - (A+B) * exp(-c*(t))

Поэтому для этого я хочу использовать нелинейную регрессию, встроенную в R. Я занимаюсь этим в течение дня, и просто не могу заставить его работать. Проблема полностью связана с начальными значениями, поэтому я использую NLS2 для поиска начальных значений методом перебора.

y <- c(0,0.01377,0.01400875,0.0119175,0.00759375,0.00512125,0.004175,0.00355375,
0.00308875,0.0028925,0.00266375)
t <- c(0,3,6,12,24,48,72,96,120,144,168)
df <- data.frame(t,y)
plot(t,y);
#Our model:
fo <- y ~ f1*exp(-k1*t)+f2*exp(-k2*t)-(f1+f2)*exp(-k3*t);

#Define the outer boundaries to search for initial values
grd <- data.frame(f1=c(0,1),
              f2=c(0,1),
              k1=c(0,2),
              k2=c(0,2),
              k3=c(0,0.7));

#Do the brute-force
fit <- nls2(fo,
        data=df,
        start = grd,
        algorithm = "brute-force",
        control=list(maxiter=20000))
fit
coef(fit)
final <- nls(fo, data=df, start=as.list(coef(fit)))

Он должен дать следующие значения:

f1  0.013866
f2  0.005364
k1  0.063641
k2  0.004297
k3  0.615125

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

Изменить на основе комментария @Roland:

Метод, который вы предлагаете с приближением k1-3 с линейным подходом, кажется, работает с некоторыми наборами данных, но не со всеми из них. Ниже приведен код, который я использую сейчас на основе ваших данных.

#Oral example:
y <- c(0,0.0045375,0.0066325,0.00511375,0.00395875,0.003265,0.00276,
0.002495,0.00231875);
t <- c(0,12,24,48,72,96,120,144,168);
#IV example:
#y <- c(0,0.01377,0.01400875,0.0119175,0.00759375,0.00512125,0.004175,
#0.00355375,0.00308875,0.0028925,0.00266375)
#t <- c(0,3,6,12,24,48,72,96,120,144,168)
DF <- data.frame(y, t)
fit1 <- nls(y ~ cbind(exp(-k1*t), exp(-k2*t), exp(-k3*t)), algorithm = "plinear", data = DF,
            start = list(k1 = 0.002, k2 = 0.02, k3= 0.2))
k1_predict <-summary(fit1)$coefficients[1,1]
k2_predict <-summary(fit1)$coefficients[2,1]
k3_predict <-summary(fit1)$coefficients[3,1]
fo <- y ~ f1*exp(-k1*t)+f2*exp(-k2*t)-(f1+f2)*exp(-k3*t);
fit2 <- nls(fo, data = DF, 
            start = list(k1 = k1_predict, k2 = k2_predict, k3 = k3_predict, f1 = 0.01, f2 = 0.01))
summary(fit2);
plot(t,y);
curve(predict(fit2, newdata = data.frame(t = x)), 0, 200, add = TRUE, col = "red")

oral_example fit

@ГРАММ. Гротендик. Подобно предложению Роланда, ваше предложение также превосходно в том смысле, что оно способно подогнать одни наборы данных, но затрудняется с другими. Приведенный ниже код основан на вашем вводе и завершается с сингулярной матрицей градиента.

#Oral example:
y <- c(0,0.0045375,0.0066325,0.00511375,0.00395875,0.003265,0.00276,
0.002495,0.00231875);
t <- c(0,12,24,48,72,96,120,144,168);
#IV example:
#y <- c(0,0.01377,0.01400875,0.0119175,0.00759375,0.00512125,0.004175,
#0.00355375,0.00308875,0.0028925,0.00266375)
#t <- c(0,3,6,12,24,48,72,96,120,144,168)
df <- data.frame(y, t)
grd <- data.frame(f1=c(0,1),
              f2=c(0,1),
              k1=c(0,2),
              k2=c(0,2),
              k3=c(0,0.7));
set.seed(123)
fit <- nls2(fo,
        data=df,
        start = grd,
        algorithm = "random",
        control = nls.control(maxiter = 100000))
nls(fo, df, start = coef(fit), alg = "port", lower = 0)
plot(t,y);
curve(predict(nls, newdata = data.frame(t = x)), 0, 200, add = TRUE, col = "red")

person redtails    schedule 11.11.2015    source источник


Ответы (2)


Сначала я бы сделал частично линейную аппроксимацию без ограничений на линейные параметры, чтобы получить хорошие начальные значения для экспоненциальных параметров и некоторое представление о величине линейных параметров:

DF <- data.frame(y, t)
fit1 <- nls(y ~ cbind(exp(-k1*t), exp(-k2*t), exp(-k3*t)), algorithm = "plinear", data = DF,
            start = list(k1 = 0.002, k2 = 0.02, k3= 0.2))
summary(fit1)
#Formula: y ~ cbind(exp(-k1 * t), exp(-k2 * t), exp(-k3 * t))
#
#Parameters:
#        Estimate Std. Error t value Pr(>|t|)    
#k1     0.0043458  0.0010397   4.180 0.008657 ** 
#k2     0.0639379  0.0087141   7.337 0.000738 ***
#k3     0.6077646  0.0632586   9.608 0.000207 ***
#.lin1  0.0053968  0.0006637   8.132 0.000457 ***
#.lin2  0.0139231  0.0008694  16.014 1.73e-05 ***
#.lin3 -0.0193145  0.0010631 -18.168 9.29e-06 ***

Затем вы можете подобрать свою реальную модель:

fit2 <- nls(fo, data = DF, 
            start = list(k1 = 0.06, k2 = 0.004, k3 = 0.6, f1 = 0.01, f2 = 0.01))
summary(fit2)  
#Formula: y ~ f1 * exp(-k1 * t) + f2 * exp(-k2 * t) - (f1 + f2) * exp(-k3 * t)
#
#Parameters:
#    Estimate Std. Error t value Pr(>|t|)    
#k1 0.0639344  0.0079538   8.038 0.000198 ***
#k2 0.0043456  0.0009492   4.578 0.003778 ** 
#k3 0.6078929  0.0575616  10.561 4.24e-05 ***
#f1 0.0139226  0.0007934  17.548 2.20e-06 ***
#f2 0.0053967  0.0006059   8.907 0.000112 ***         

curve(predict(fit2, newdata = data.frame(t = x)), 0, 200, add = TRUE, col = "red")

итоговый график

Обратите внимание, что эту модель можно легко перенастроить, переключив экспоненциальные члены (то есть порядок начальных значений kn), что может привести к разным оценкам для f1 и f2, но в основном к одинаковому соответствию.

person Roland    schedule 11.11.2015
comment
Я внес поправку в начальный пост на основе вашего вклада. Ваше предложение, похоже, частично решает проблему, хотя в некоторых наборах данных мне все еще трудно найти подходящее сочетание с R. Если вы можете взглянуть на него в другой раз, это действительно может помочь моему исследованию. - person redtails; 11.11.2015
comment
У вас есть (i) сложная пятипараметрическая модель и (ii) в критическом временном диапазоне (около пика) очень мало точек данных для поддержки этой модели. Я считаю, что небольшие отклонения (например, из-за неопределенности измерения) второй точки данных сильно влияют на подгонку и могут привести к проблемам сходимости. - person Roland; 11.11.2015
comment
Все в порядке. спасибо вам любезно. Ваш вклад помог мне лучше смоделировать мои IV / устные данные :) - person redtails; 13.11.2015

С таким количеством параметров я бы использовал алгоритм = "random", а не "brute". Если мы это сделаем, то следующий результат даст результат, близкий к рассматриваемому (с точностью до перестановки аргументов из-за симметрии параметров модели):

set.seed(123)
fit <- nls2(fo,
        data=df,
        start = grd,
        algorithm = "random",
        control = nls.control(maxiter = 20000))
nls(fo, df, start = coef(fit), alg = "port", lower = 0)

давая:

Nonlinear regression model
  model: y ~ f1 * exp(-k1 * t) + f2 * exp(-k2 * t) - (f1 + f2) * exp(-k3 * t)
   data: df
      f1       f2       k1       k2       k3 
0.005397 0.013923 0.004346 0.063934 0.607893 
 residual sum-of-squares: 2.862e-07

Algorithm "port", convergence message: relative convergence (4)

ДОБАВЛЕНО

Вариантом вышеизложенного является использование nlsLM в пакете minpack.lm вместо nls и использование сплайнов для получения большего количества точек в наборе данных. Вместо строки nls попробуйте следующее. Это по-прежнему дает сближение:

library(minpack.lm)
t_s <- with(df, min(t):max(t))
df_s <- setNames(data.frame(spline(df$t, df$y, xout = t_s)), c("t", "y"))
nlsLM(fo, df_s, start = coef(fit), lower = rep(0,5), control = nls.control(maxiter = 1024))

и то же самое в устном примере:

set.seed(123)
y <- c(0,0.0045375,0.0066325,0.00511375,0.00395875,0.003265,0.00276,
0.002495,0.00231875);
t <- c(0,12,24,48,72,96,120,144,168)
DF <- data.frame(y, t)
grd <- data.frame(f1=c(0,1), f2=c(0,1), k1=c(0,2), k2=c(0,2), k3=c(0,0.7))
fit <- nls2(fo,
        data=DF,
        start = grd,
        algorithm = "random",
        control = nls.control(maxiter = 20000))

library(minpack.lm)
t_s <- with(DF, min(t):max(t))
df_s <- setNames(data.frame(spline(DF$t, DF$y, xout = t_s)), c("t", "y"))
nlsLM(fo, df_s, start = coef(fit), lower = rep(0,5), control = nls.control(maxiter = 1024))
person G. Grothendieck    schedule 11.11.2015
comment
Я внес поправку в исходный вопрос, также основываясь на вашем мнении. Ваше предложение перебора со случайным семенем, похоже, очень эффективно работает с некоторыми наборами данных, хотя с другими наборами данных я изо всех сил пытаюсь найти подходящий вариант. Если у вас есть дополнительные советы, это действительно поможет мне - person redtails; 11.11.2015
comment
спасибо большое, ваш вклад был полезен для моделирования моих данных :) - person redtails; 13.11.2015
comment
Вы должны отметить тот, который вам нравится, а не использовать комментарии для благодарности. - person G. Grothendieck; 13.11.2015