在R中拟合非线性Langmuir等温线

3
我希望在R中为以下数据拟合等温模型。最简单的等温模型是 Langmuir 模型,可以在此处找到 页面底部给出了该模型。我提供的最小化可工作示例 (MWE) 如下,但会产生错误。不知道是否有适用于等温模型的 R 包。
X <- c(10, 30, 50, 70, 100, 125)
Y <- c(155, 250, 270, 330, 320, 323)
Data <- data.frame(X, Y)
LangIMfm2 <- nls(formula = Y ~ Q*b*X/(1+b*X),  data = Data, start = list(Q = 1, b = 0.5), algorith = "port")

Error in nls(formula = Y ~ Q * b * X/(1 + b * X), data = Data, start = list(Q = 1,  : 
  Convergence failure: singular convergence (7)

编辑

一些非线性模型可以转化为线性模型。我的理解是,非线性模型的估计值与其线性模型形式之间可能存在一对一的关系,但它们相应的标准误差并不相关。这个说法是否正确?将非线性模型转化为线性模型拟合时有什么注意事项吗?


只需尝试不同的起始参数,例如 start = list(Q = 300, b = 1) - Marat Talipov
感谢@MaratTalipov的有用评论。您知道是否有任何R软件包可拟合等温线模型吗? - MYaseen208
3个回答

3
我不知道是否有这样的软件包,但个人认为您并不需要一个,因为该问题可以使用基本的R语言解决。
nls对于起始参数很敏感,所以你应该从一个好的猜测开始。你可以很容易地评估Q,因为它对应于等温线在x-->Inf时的渐近极限,因此从Q=323(这是您的样本数据集中最后一个Y值)开始是合理的。
接下来,您可以执行plot(Data)并添加一条等温线,该等温线对应于您的起始参数Q和b,然后调整b以得出一个合理的猜测。
下面的图显示了您的数据集(点)和一个探针等温线,其中Q = 323,b = 0.5,由with(Data,lines(X,323*0.5*X/(1+0.5*X),col='red'))生成(红色线)。对我来说,这似乎是一个合理的起始猜测,我尝试使用nls。
LangIMfm2 <- nls(formula = Y ~ Q*b*X/(1+b*X),  data = Data, start = list(Q = 300, b = 1), algorith = "port")
# Nonlinear regression model
#   model: Y ~ Q * b * X/(1 + b * X)
#    data: Data
#        Q        b 
# 366.2778   0.0721 
#  residual sum-of-squares: 920.6
# 
# Algorithm "port", convergence message: relative convergence (4)

并绘制预测线以确保nls找到了正确的解决方案:

lines(Data$X,predict(LangIMfm2),col='green')

话虽如此,我建议采用更有效的策略,通过在倒数坐标中重新编写等温线方程来线性化模型:

enter image description here

z <- 1/Data
plot(Y~X,z)
abline(lm(Y~X,z))
M <- lm(Y~X,z)

Q <- 1/coef(M)[1]
# 363.2488 

b <- coef(M)[1]/coef(M)[2]
# 0.0741759 

如您所见,这两种方法本质上产生的结果相同,但线性模型更为健壮,不需要起始参数(并且据我记得,在实验物理化学中,这是等温线分析的标准方式)。

感谢@Marat的回答。我认为Lanmuir等温线模型的线性版本和非线性版本有很大的不同,特别是在其估计值的标准误差方面。 - MYaseen208
一些非线性模型可以转化为线性模型。我的理解是,非线性模型的估计值和其线性模型形式可能存在一对一的关系,但它们对应的标准误差并不相关。这个说法正确吗?将非线性模型转化为线性模型拟合是否存在任何陷阱?感谢您的帮助。 - MYaseen208

0

您可以在R的nlme包中使用SSmicmen自启动函数(参见Ritz和Streibig,2008年,《R中的非线性回归》),该函数从Michaelis-Menten(MM)方程的线性化形式的拟合中计算初始参数。幸运的是,MM方程具有可适用于Langmuir方程的形式,S = Smax * x /(KL + x)。我发现nlshelper和tidyverse包对于建模和将nls命令的结果导出到表格和图形特别有用,尤其是在建模样本组时。以下是我用于建模单个吸附数据集的代码:

library(tidyverse)
library(nlme)
library(nlshelper)

lang.fit <- nls(Y ~ SSmicmen(X,Smax,InvKL), data=Data)
fit.summary <- tidy(lang.fit)
fit.coefs <- coef(lang.fit)

为了简化,这里将Langmuir亲和常数建模为1/KL。应用此代码,我得到与@Marat上面给出的相同的参数估计。
下面的简单代码允许整理数据以创建一个ggplot对象,其中包含原始点和拟合线(即,geom_point表示原始X和Y数据,geom_line表示原始X加上YHat)。
FitY <- tibble(predict(lang.fit))
YHat <- FitY[,1]
Data2 <- cbind(Data, YHat)

如果您想对多组数据进行建模(例如,基于“Sample_name”列),那么lang.fit变量将通过以下方式计算,这次使用nlsList命令:

lang.fit <- nlsList(Y ~ SSmicmen(X,Smax,InvKL) | Sample_name, data=Data)

0
问题在于起始值。我们展示了两种方法以及一种替代方案,即使使用问题中的起始值也会收敛。
1)plinear 右侧对 Q*b 是线性的,因此最好将 b 吸收到 Q 中,然后我们就有一个参数是线性进入的,因此更容易解决。而且使用 plinear 算法不需要用于线性参数的起始值,因此只需指定 b 的起始值即可。 使用 plinear 时,nls 公式的右侧应指定为乘以线性参数的向量。 运行 nls 并给出下面的 fm0 结果将给出名为 b 和 .lin 的系数,其中 Q = .lin/b。
我们已经从 fm0 中得到了答案,但是如果我们想要以 b 和 Q 而不是 b 和 .lin 的术语来进行干净的运行,则可以像所示地运行问题中的原始公式,并使用 fm0 返回的系数隐含的起始值。
fm0 <- nls(Y ~ X/(1+b*X), Data, start = list(b = 0.5), alg = "plinear")

st <- with(as.list(coef(fm0)), list(b = b, Q = .lin/b))
fm <- nls(Y ~ Q*b*X/(1+b*X), Data, start = st)
fm

提供

Nonlinear regression model
  model: Y ~ Q * b * X/(1 + b * X)
   data: Data
       b        Q 
  0.0721 366.2778 
 residual sum-of-squares: 920.6

Number of iterations to convergence: 0 
Achieved convergence tolerance: 9.611e-07

我们可以展示结果。点是数据,红线是拟合曲线。
plot(Data)
lines(fitted(fm) ~ X, Data, col = "red")

(图表后继续)屏幕截图

2) 平均值 或者,对于 Q,使用 mean(Data$Y) 作为起始值似乎效果很好。

nls(Y ~ Q*b*X/(1+b*X), Data, start = list(b = 0.5, Q = mean(Data$Y)))

提供:

Nonlinear regression model
  model: Y ~ Q * b * X/(1 + b * X)
   data: Data
       b        Q 
  0.0721 366.2779 
 residual sum-of-squares: 920.6

Number of iterations to convergence: 6 
Achieved convergence tolerance: 5.818e-06

问题已经有了一个合理的起始值 b,我们使用了它,但如果需要,可以将 Y 设置为 Q*b,以便它们相互抵销,将 X 设为 mean(Data$X),并解决 b,得到 b = 1 - 1/mean(Data$X) 作为一种可能的起始值。虽然没有显示,但使用这个起始值对于 b 和以 mean(Data$Y) 作为 Q 的起始值也导致了收敛。

3) optim 如果我们使用 optim,即使在问题中使用了初始值,算法也会收敛。我们形成残差平方和,并将其最小化:

rss <- function(p) {
  Q <- p[1]
  b <- p[2]
  with(Data, sum((Y - b*Q*X/(1+b*X))^2))
}
optim(c(1, 0.5), rss)

提供:

$par
[1] 366.27028219   0.07213613

$value
[1] 920.62

$counts
function gradient 
     249       NA 

$convergence
[1] 0

$message
NULL

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接