[英]Exponential curve fitting in R
注意:此答案已完全从原始答案重写,它在几个方面存在缺陷(感谢评论者突出显示这些)。 我希望这个新答案是正确的。
您需要一个模型来拟合数据。 在不知道模型的全部细节的情况下,假设这是一个指数增长模型,可以写为: y = a * e r*t
其中y是您的测量变量, t是测量它的时间, a是t = 0时的 y 值, r是增长常数。 我们要估计a和r 。
这是一个非线性问题,因为我们要估计指数r 。 但是,在这种情况下,我们可以使用一些代数,通过对两边取对数并求解(记住对数规则)将其转换为线性方程,结果为: log(y) = log(a) + r * t
我们可以用一个例子来形象化,通过从我们的模型中生成一条曲线,假设a和r 的一些值:
t <- 1:100 # these are your time points
a <- 10 # assume the size at t = 0 is 10
r <- 0.1 # assume a growth constant
y <- a*exp(r*t) # generate some y observations from our exponential model
# visualise
par(mfrow = c(1, 2))
plot(t, y) # on the original scale
plot(t, log(y)) # taking the log(y)
因此,对于这种情况,我们可以探索两种可能性:
nls()
函数)lm()
函数)选择哪个选项(还有更多选项)取决于我们认为(或假设)数据背后的数据生成过程。
让我们用一些模拟来说明,其中包括添加的噪声(从正态分布采样),以模拟真实数据。 请查看此StackExchange 帖子以了解此模拟背后的推理(由Alejo Bernardin 的评论指出)。
set.seed(12) # for reproducible results
# errors constant across time - additive
y_add <- a*exp(r*t) + rnorm(length(t), sd = 5000) # or: rnorm(length(t), mean = a*exp(r*t), sd = 5000)
# errors grow as y grows - multiplicative (constant on the log-scale)
y_mult <- a*exp(r*t + rnorm(length(t), sd = 1)) # or: rlnorm(length(t), mean = log(a) + r*t, sd = 1)
# visualise
par(mfrow = c(1, 2))
plot(t, y_add, main = "additive error")
lines(t, a*exp(t*r), col = "red")
plot(t, y_mult, main = "multiplicative error")
lines(t, a*exp(t*r), col = "red")
对于加性模型,我们可以使用nls()
,因为误差在t 上是恒定的。 使用nls()
我们需要为优化算法指定一些起始值(尝试“猜测”这些是什么,因为nls()
经常难以收敛于解决方案)。
add_nls <- nls(y_add ~ a*exp(r*t),
start = list(a = 0.5, r = 0.2))
coef(add_nls)
# a r
# 11.30876845 0.09867135
使用coef()
函数,我们可以获得两个参数的估计值。 这给了我们可以的估计,接近我们模拟的(a = 10 和 r = 0.1)。
通过绘制模型的残差,您可以看到误差方差在数据范围内是合理恒定的:
plot(t, resid(add_nls))
abline(h = 0, lty = 2)
对于乘法误差情况(我们的y_mult
模拟值),我们应该对对数转换数据使用lm()
,因为误差在该尺度上是恒定的。
mult_lm <- lm(log(y_mult) ~ t)
coef(mult_lm)
# (Intercept) t
# 2.39448488 0.09837215
为了解释这个输出,再次记住我们的线性模型是log(y) = log(a) + r*t ,它等价于形式为Y = β0 + β1 * X的线性模型,其中β0是我们的截距和β1我们的斜率。
因此,在这个输出中(Intercept)
相当于我们模型的log(a)并且t
是时间变量的系数,所以相当于我们的r 。 有意义地解释(Intercept)
,我们可以采取其指数( exp(2.39448488)
给我们〜10.96,这是相当接近我们的模拟值。
值得注意的是,如果我们使用nls
函数拟合误差为乘法的数据会发生什么:
mult_nls <- nls(y_mult ~ a*exp(r*t), start = list(a = 0.5, r = 0.2))
coef(mult_nls)
# a r
# 281.06913343 0.06955642
现在我们高估了 a并低估了r ( Mario Reutter在他的评论中强调了这一点)。 我们可以想象使用错误的方法来拟合我们的模型的后果:
# get the model's coefficients
lm_coef <- coef(mult_lm)
nls_coef <- coef(mult_nls)
# make the plot
plot(t, y_mult)
lines(t, a*exp(r*t), col = "brown", lwd = 5)
lines(t, exp(lm_coef[1])*exp(lm_coef[2]*t), col = "dodgerblue", lwd = 2)
lines(t, nls_coef[1]*exp(nls_coef[2]*t), col = "orange2", lwd = 2)
legend("topleft", col = c("brown", "dodgerblue", "orange2"),
legend = c("known model", "nls fit", "lm fit"), lwd = 3)
我们可以看到lm()
对对数转换数据的拟合比nls()
对原始数据的拟合要好得多。
您可以再次绘制此模型的残差,以查看方差在数据范围内不是恒定的(我们也可以在上图中看到这一点,其中数据的分布随着t值的增加而增加):
plot(t, resid(mult_nls))
abline(h = 0, lty = 2)
不幸的是,取对数并拟合线性模型并不是最佳的。 原因是当应用指数函数返回原始模型时,大 y 值的误差比小 y 值的误差权重得多。 这是一个例子:
f <- function(x){exp(0.3*x+5)}
squaredError <- function(a,b,x,y) {sum((exp(a*x+b)-f(x))^2)}
x <- 0:12
y <- f(x) * ( 1 + sample(-300:300,length(x),replace=TRUE)/10000 )
x
y
#--------------------------------------------------------------------
M <- lm(log(y)~x)
a <- unlist(M[1])[2]
b <- unlist(M[1])[1]
print(c(a,b))
squaredError(a,b,x,y)
approxPartAbl_a <- (squaredError(a+1e-8,b,x,y) - squaredError(a,b,x,y))/1e-8
for ( i in 0:10 )
{
eps <- -i*sign(approxPartAbl_a)*1e-5
print(c(eps,squaredError(a+eps,b,x,y)))
}
结果:
> f <- function(x){exp(0.3*x+5)}
> squaredError <- function(a,b,x,y) {sum((exp(a*x+b)-f(x))^2)}
> x <- 0:12
> y <- f(x) * ( 1 + sample(-300:300,length(x),replace=TRUE)/10000 )
> x
[1] 0 1 2 3 4 5 6 7 8 9 10 11 12
> y
[1] 151.2182 203.4020 278.3769 366.8992 503.5895 682.4353 880.1597 1186.5158 1630.9129 2238.1607 3035.8076 4094.6925 5559.3036
> #--------------------------------------------------------------------
>
> M <- lm(log(y)~x)
> a <- unlist(M[1])[2]
> b <- unlist(M[1])[1]
> print(c(a,b))
coefficients.x coefficients.(Intercept)
0.2995808 5.0135529
> squaredError(a,b,x,y)
[1] 5409.752
> approxPartAbl_a <- (squaredError(a+1e-8,b,x,y) - squaredError(a,b,x,y))/1e-8
> for ( i in 0:10 )
+ {
+ eps <- -i*sign(approxPartAbl_a)*1e-5
+ print(c(eps,squaredError(a+eps,b,x,y)))
+ }
[1] 0.000 5409.752
[1] -0.00001 5282.91927
[1] -0.00002 5157.68422
[1] -0.00003 5034.04589
[1] -0.00004 4912.00375
[1] -0.00005 4791.55728
[1] -0.00006 4672.70592
[1] -0.00007 4555.44917
[1] -0.00008 4439.78647
[1] -0.00009 4325.71730
[1] -0.0001 4213.2411
>
也许可以尝试一些数值方法,即梯度搜索,以找到平方误差函数的最小值。
当然,这不是一个可爱的答案。 请不要惩罚我。
如果它真的是指数的,您可以尝试取变量的对数并对其拟合线性模型。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.