hồi quy phi tuyến tính với hiệu ứng ngẫu nhiên và lsoda
Tôi đang đối mặt với một vấn đề mà tôi không quản lý để giải quyết. Tôi muốn sử dụng nlme
hoặc nlmODE
thực hiện một hồi quy phi tuyến tính với hiệu ứng ngẫu nhiên bằng cách sử dụng làm mô hình nghiệm của một phương trình vi phân bậc hai với các hệ số cố định (một bộ dao động giảm xóc).
Tôi quản lý để sử dụng nlme
với các mô hình đơn giản, nhưng có vẻ như việc sử dụng deSolve
để tạo ra nghiệm của phương trình vi phân gây ra một vấn đề. Dưới đây là một ví dụ và những vấn đề tôi gặp phải.
Dữ liệu và chức năng
Đây là hàm tạo nghiệm của phương trình vi phân bằng cách sử dụng deSolve
:
library(deSolve)
ODE2_nls <- function(t, y, parms) {
S1 <- y[1]
dS1 <- y[2]
dS2 <- dS1
dS1 <- - parms["esp2omega"]*dS1 - parms["omega2"]*S1 + parms["omega2"]*parms["yeq"]
res <- c(dS2,dS1)
list(res)}
solution_analy_ODE2 = function(omega2,esp2omega,time,y0,v0,yeq){
parms <- c(esp2omega = esp2omega,
omega2 = omega2,
yeq = yeq)
xstart = c(S1 = y0, dS1 = v0)
out <- lsoda(xstart, time, ODE2_nls, parms)
return(out[,2])
}
Tôi có thể tạo ra một giải pháp cho một khoảng thời gian nhất định và hệ số tắt dần, ví dụ ở đây là khoảng thời gian 20 và giảm xóc nhẹ là 0,2:
# small example:
time <- 1:100
period <- 20 # period of oscillation
amort_factor <- 0.2
omega <- 2*pi/period # agular frequency
oscil <- solution_analy_ODE2(omega^2,amort_factor*2*omega,time,1,0,0)
plot(time,oscil)
Bây giờ tôi tạo một bảng gồm 10 cá nhân với giai đoạn bắt đầu ngẫu nhiên (tức là vị trí và vận tốc xuất phát khác nhau). Mục tiêu là thực hiện một hồi quy phi tuyến tính với tác động ngẫu nhiên đến các giá trị bắt đầu
library(data.table)
# generate panel
Npoint <- 100 # number of time poitns
Nindiv <- 10 # number of individuals
period <- 20 # period of oscillation
amort_factor <- 0.2
omega <- 2*pi/period # agular frequency
# random phase
phase <- sample(seq(0,2*pi,0.01),Nindiv)
# simu data:
data_simu <- data.table(time = rep(1:Npoint,Nindiv), ID = rep(1:Nindiv,each = Npoint))
# signal generation
data_simu[,signal := solution_analy_ODE2(omega2 = omega^2,
esp2omega = 2*0.2*omega,
time = time,
y0 = sin(phase[.GRP]),
v0 = omega*cos(phase[.GRP]),
yeq = 0)+
rnorm(.N,0,0.02),by = ID]
Nếu chúng ta xem xét, chúng ta có một tập dữ liệu thích hợp:
library(ggplot2)
ggplot(data_simu,aes(time,signal,color = ID))+
geom_line()+
facet_wrap(~ID)
Vấn đề
Sử dụng nlme
Sử dụng nlme
với cú pháp tương tự làm việc trên các ví dụ đơn giản hơn (các hàm phi tuyến tính không sử dụng deSolve), tôi đã thử:
fit <- nlme(model = signal ~ solution_analy_ODE2(esp2omega,omega2,time,y0,v0,yeq),
data = data_simu,
fixed = esp2omega + omega2 + y0 + v0 + yeq ~ 1,
random = y0 ~ 1 ,
groups = ~ ID,
start = c(esp2omega = 0.08,
omega2 = 0.04,
yeq = 0,
y0 = 1,
v0 = 0))
Tôi có được:
Lỗi trong checkFunc (Func2, times, y, rho): Số lượng các dẫn xuất được trả về bởi func () (2) phải bằng độ dài của vectơ điều kiện ban đầu (2000)
Quá trình truy tìm:
12. stop(paste("The number of derivatives returned by func() (", length(tmp[[1]]), ") must equal the length of the initial conditions vector (", length(y), ")", sep = ""))
11. checkFunc(Func2, times, y, rho)
10. lsoda(xstart, time, ODE2_nls, parms)
9. solution_analy_ODE2(omega2, esp2omega, time, y0, v0, yeq)
.
.
Có vẻ như tôi nlme
đang cố gắng chuyển một vectơ của điều kiện bắt đầu sang solution_analy_ODE2
và gây ra lỗi trong checkFunc
từ lasoda
.
Tôi đã thử sử dụng nlsList
:
test <- nlsList(model = signal ~ solution_analy_ODE2(omega2,esp2omega,time,y0,v0,yeq) | ID,
data = data_simu,
start = list(esp2omega = 0.08, omega2 = 0.04,yeq = 0,
y0 = 1,v0 = 0),
control = list(maxiter=150, warnOnly=T,minFactor = 1e-10),
na.action = na.fail, pool = TRUE)
head(test)
Call:
Model: signal ~ solution_analy_ODE2(omega2, esp2omega, time, y0, v0, yeq) | ID
Data: data_simu
Coefficients:
esp2omega omega2 yeq y0 v0
1 0.1190764 0.09696076 0.0007577956 -0.1049423 0.30234654
2 0.1238936 0.09827158 -0.0003463023 0.9837386 0.04773775
3 0.1280399 0.09853310 -0.0004908579 0.6051663 0.25216134
4 0.1254053 0.09917855 0.0001922963 -0.5484005 -0.25972829
5 0.1249473 0.09884761 0.0017730823 0.7041049 0.22066652
6 0.1275408 0.09966155 -0.0017522320 0.8349450 0.17596648
Chúng ta có thể thấy rằng sự phù hợp phi tuyến tính hoạt động tốt trên các tín hiệu riêng lẻ. Bây giờ, nếu tôi muốn thực hiện hồi quy tập dữ liệu với các hiệu ứng ngẫu nhiên, thì cú pháp phải là:
fit <- nlme(test,
random = y0 ~ 1 ,
groups = ~ ID,
start = c(esp2omega = 0.08,
omega2 = 0.04,
yeq = 0,
y0 = 1,
v0 = 0))
Nhưng tôi nhận được cùng một thông báo lỗi.
Sau đó, tôi đã thử sử dụng nlmODE
, theo bình luận của Bne Bolker về một câu hỏi tương tự mà tôi đã hỏi cách đây vài năm
sử dụng nlmODE
library(nlmeODE)
datas_grouped <- groupedData( signal ~ time | ID, data = data_simu,
labels = list (x = "time", y = "signal"),
units = list(x ="arbitrary", y = "arbitrary"))
modelODE <- list( DiffEq = list(dS2dt = ~ S1,
dS1dt = ~ -esp2omega*S1 - omega2*S2 + omega2*yeq),
ObsEq = list(yc = ~ S2),
States = c("S1","S2"),
Parms = c("esp2omega","omega2","yeq","ID"),
Init = c(y0 = 0,v0 = 0))
resnlmeode = nlmeODE(modelODE, datas_grouped)
assign("resnlmeode", resnlmeode, envir = .GlobalEnv)
#Fitting with nlme the resulting function
model <- nlme(signal ~ resnlmeode(esp2omega,omega2,yeq,time,ID),
data = datas_grouped,
fixed = esp2omega + omega2 + yeq + y0 + v0 ~ 1,
random = y0 + v0 ~1,
start = c(esp2omega = 0.08,
omega2 = 0.04,
yeq = 0,
y0 = 0,
v0 = 0)) #
Tôi gặp lỗi:
Lỗi trong resnlmeode (esp2omega, omega2, yeq, time, ID): không tìm thấy đối tượng 'yhat'
Ở đây tôi không hiểu lỗi từ đâu, cũng như cách giải quyết.
Câu hỏi
- Bạn có thể tạo lại vấn đề ?
- Có ai có ý tưởng để giải quyết vấn đề này, sử dụng một trong hai
nlme
hoặcnlmODE
? - Nếu không, có giải pháp nào bằng cách sử dụng gói khác không? Tôi đã thấy
nlmixr
(https://cran.r-project.org/web/packages/nlmixr/index.html), nhưng tôi không biết, quá trình cài đặt rất phức tạp và gần đây nó đã bị xóa khỏi CRAN
Chỉnh sửa
@tpetzoldt đã đề xuất một cách hay để gỡ lỗi nlme
hành vi và nó khiến tôi rất ngạc nhiên. Đây là một ví dụ làm việc với một hàm phi tuyến tính, trong đó tôi tạo một tập hợp 5 cá thể với tham số ngẫu nhiên khác nhau giữa các cá thể:
reg_fun = function(time,b,A,y0){
cat("time : ",length(time)," b :",length(b)," A : ",length(A)," y0: ",length(y0),"\n")
out <- A*exp(-b*time)+(y0-1)
cat("out : ",length(out),"\n")
tmp <- cbind(b,A,y0,time,out)
cat(apply(tmp,1,function(x) paste(paste(x,collapse = " "),"\n")),"\n")
return(out)
}
time <- 0:10*10
ramdom_y0 <- sample(seq(0,1,0.01),10)
Nid <- 5
data_simu <-
data.table(time = rep(time,Nid),
ID = rep(LETTERS[1:Nid],each = length(time)) )[,signal := reg_fun(time,0.02,2,ramdom_y0[.GRP]) + rnorm(.N,0,0.1),by = ID]
Những con mèo trong hàm đưa ra ở đây:
time : 11 b : 1 A : 1 y0: 1
out : 11
0.02 2 0.64 0 1.64
0.02 2 0.64 10 1.27746150615596
0.02 2 0.64 20 0.980640092071279
0.02 2 0.64 30 0.737623272188053
0.02 2 0.64 40 0.538657928234443
0.02 2 0.64 50 0.375758882342885
0.02 2 0.64 60 0.242388423824404
0.02 2 0.64 70 0.133193927883213
0.02 2 0.64 80 0.0437930359893108
0.02 2 0.64 90 -0.0294022235568269
0.02 2 0.64 100 -0.0893294335267746
.
.
.
Bây giờ tôi làm với nlme
:
nlme(model = signal ~ reg_fun(time,b,A,y0),
data = data_simu,
fixed = b + A + y0 ~ 1,
random = y0 ~ 1 ,
groups = ~ ID,
start = c(b = 0.03, A = 1,y0 = 0))
Tôi có:
time : 55 b : 55 A : 55 y0: 55
out : 55
0.03 1 0 0 0
0.03 1 0 10 -0.259181779318282
0.03 1 0 20 -0.451188363905974
0.03 1 0 30 -0.593430340259401
0.03 1 0 40 -0.698805788087798
0.03 1 0 50 -0.77686983985157
0.03 1 0 60 -0.834701111778413
0.03 1 0 70 -0.877543571747018
0.03 1 0 80 -0.909282046710588
0.03 1 0 90 -0.93279448726025
0.03 1 0 100 -0.950212931632136
0.03 1 0 0 0
0.03 1 0 10 -0.259181779318282
0.03 1 0 20 -0.451188363905974
0.03 1 0 30 -0.593430340259401
0.03 1 0 40 -0.698805788087798
0.03 1 0 50 -0.77686983985157
0.03 1 0 60 -0.834701111778413
0.03 1 0 70 -0.877543571747018
0.03 1 0 80 -0.909282046710588
0.03 1 0 90 -0.93279448726025
0.03 1 0 100 -0.950212931632136
0.03 1 0 0 0
0.03 1 0 10 -0.259181779318282
0.03 1 0 20 -0.451188363905974
0.03 1 0 30 -0.593430340259401
0.03 1 0 40 -0.698805788087798
0.03 1 0 50 -0.77686983985157
0.03 1 0 60 -0.834701111778413
0.03 1 0 70 -0.877543571747018
0.03 1 0 80 -0.909282046710588
0.03 1 0 90 -0.93279448726025
0.03 1 0 100 -0.950212931632136
0.03 1 0 0 0
0.03 1 0 10 -0.259181779318282
0.03 1 0 20 -0.451188363905974
0.03 1 0 30 -0.593430340259401
0.03 1 0 40 -0.698805788087798
0.03 1 0 50 -0.77686983985157
0.03 1 0 60 -0.834701111778413
0.03 1 0 70 -0.877543571747018
0.03 1 0 80 -0.909282046710588
0.03 1 0 90 -0.93279448726025
0.03 1 0 100 -0.950212931632136
0.03 1 0 0 0
0.03 1 0 10 -0.259181779318282
0.03 1 0 20 -0.451188363905974
0.03 1 0 30 -0.593430340259401
0.03 1 0 40 -0.698805788087798
0.03 1 0 50 -0.77686983985157
0.03 1 0 60 -0.834701111778413
0.03 1 0 70 -0.877543571747018
0.03 1 0 80 -0.909282046710588
0.03 1 0 90 -0.93279448726025
0.03 1 0 100 -0.950212931632136
time : 55 b : 55 A : 55 y0: 55
out : 55
0.03 1 0 0 0
0.03 1 0 10 -0.259181779318282
0.03 1 0 20 -0.451188363905974
0.03 1 0 30 -0.593430340259401
0.03 1 0 40 -0.698805788087798
0.03 1 0 50 -0.77686983985157
0.03 1 0 60 -0.834701111778413
0.03 1 0 70 -0.877543571747018
0.03 1 0 80 -0.909282046710588
0.03 1 0 90 -0.93279448726025
0.03 1 0 100 -0.950212931632136
0.03 1 0 0 0
0.03 1 0 10 -0.259181779318282
0.03 1 0 20 -0.451188363905974
0.03 1 0 30 -0.593430340259401
0.03 1 0 40 -0.698805788087798
0.03 1 0 50 -0.77686983985157
0.03 1 0 60 -0.834701111778413
0.03 1 0 70 -0.877543571747018
0.03 1 0 80 -0.909282046710588
0.03 1 0 90 -0.93279448726025
0.03 1 0 100 -0.950212931632136
0.03 1 0 0 0
0.03 1 0 10 -0.259181779318282
0.03 1 0 20 -0.451188363905974
0.03 1 0 30 -0.593430340259401
0.03 1 0 40 -0.698805788087798
0.03 1 0 50 -0.77686983985157
0.03 1 0 60 -0.834701111778413
0.03 1 0 70 -0.877543571747018
0.03 1 0 80 -0.909282046710588
0.03 1 0 90 -0.93279448726025
0.03 1 0 100 -0.950212931632136
...
Vì vậy, nlme
liên kết 5 thời gian (số lượng riêng lẻ) vectơ thời gian và chuyển nó cho hàm, với các tham số được lặp lại cùng một số thời gian. Mà tất nhiên là không tương thích với cách thức lsoda
và chức năng của tôi hoạt động.
Trả lời
Có vẻ như mô hình ode được gọi với một đối số sai, vì vậy nó nhận được một vectơ có 2000 biến trạng thái thay vì 2. Hãy thử làm như sau để xem vấn đề:
ODE2_nls <- function(t, y, parms) {
cat(length(y),"\n") # <----
S1 <- y[1]
dS1 <- y[2]
dS2 <- dS1
dS1 <- - parms["esp2omega"]*dS1 - parms["omega2"]*S1 + parms["omega2"]*parms["yeq"]
res <- c(dS2,dS1)
list(res)
}
Chỉnh sửa : Tôi nghĩ rằng hàm phân tích đã hoạt động, vì nó được vector hóa, vì vậy bạn có thể cố gắng vector hóa hàm ode, bằng cách lặp qua mô hình ode hoặc (tốt hơn) trong nội bộ sử dụng vectơ làm biến trạng thái. Vì ode
việc giải nhanh các hệ thống với vài 100k phương trình, 2000 nên khả thi.
Tôi đoán rằng cả hai, trạng thái và tham số từ nlme
đều được truyền dưới dạng vectơ. Biến trạng thái của mô hình ode khi đó là một vector "dài", các tham số có thể được thực hiện dưới dạng danh sách.
Đây là một ví dụ (đã chỉnh sửa, bây giờ với các tham số là danh sách):
ODE2_nls <- function(t, y, parms) {
#cat(length(y),"\n")
#cat(length(parms$omega2)) ndx <- seq(1, 2*N-1, 2) S1 <- y[ndx] dS1 <- y[ndx + 1] dS2 <- dS1 dS1 <- - parms$esp2omega * dS1 - parms$omega2 * S1 + parms$omega2 * parms$yeq
res <- c(dS2, dS1)
list(res)
}
solution_analy_ODE2 = function(omega2, esp2omega, time, y0, v0, yeq){
parms <- list(esp2omega = esp2omega, omega2 = omega2, yeq = yeq)
xstart = c(S1 = y0, dS1 = v0)
out <- ode(xstart, time, ODE2_nls, parms, atol=1e-4, rtol=1e-4, method="ode45")
return(out[,2])
}
Sau đó thiết lập (hoặc tính toán) số lượng phương trình, ví dụ: N <- 1
resp. N <-1000
trước các cuộc gọi.
Mô hình chạy theo cách này, trước khi chạy trong các vấn đề số, nhưng đó là một câu chuyện khác ...
Sau đó, bạn có thể cố gắng sử dụng một bộ giải ode khác (ví dụ vode
), đặt atol
và đặt rtol
các giá trị thấp hơn, tinh chỉnh nmle
các tham số tối ưu hóa, sử dụng các ràng buộc hộp ... v.v., như thường lệ trong tối ưu hóa phi tuyến.
Tôi đã tìm thấy một nlme
hành vi hack giải pháp : như được hiển thị trong bản chỉnh sửa của tôi, vấn đề xuất phát từ thực tế là nlme
chuyển một vectơ NindividualxNpoints đến hàm phi tuyến, giả sử rằng hàm liên kết với mỗi thời điểm một giá trị. Nhưng lsoda
đừng làm điều đó, vì nó tích hợp một phương trình theo thời gian (tức là nó cần tất cả thời gian cho đến một thời điểm nhất định để tạo ra một giá trị).
Giải pháp của tôi bao gồm việc phân tách các tham số nlme
chuyển đến hàm của tôi, thực hiện phép tính và tạo lại một vectơ:
detect_id <- function(vec){
tmp <- c(0,diff(vec))
out <- tmp
out <- NA
out[tmp < 0] <- 1:sum(tmp < 0)
out <- na.locf(out,na.rm = F)
rleid(out)
}
detect_id
phân rã vectơ thời gian thành bộ nhận dạng vectơ thời gian duy nhất:
detect_id(rep(1:10,3))
[1] 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3
Và sau đó, hàm thực hiện vòng lặp tích hợp số trên từng cá nhân và liên kết các vectơ kết quả lại với nhau:
solution_analy_ODE2_modif = function(omega2,esp2omega,time,y0,v0,yeq){
tmp <- detect_id(time)
out <- lapply(unique(tmp),function(i){
idxs <- which(tmp == i)
parms <- c(esp2omega = esp2omega[idxs][1],
omega2 = omega2[idxs][1],
yeq = yeq[idxs][1])
xstart = c(S1 = y0[idxs][1], dS1 = v0[idxs][1])
out_tmp <- lsoda(xstart, time[idxs], ODE2_nls, parms)
out_tmp[,2]
}) %>% unlist()
return(out)
}
Nó tôi thực hiện một bài kiểm tra, trong đó tôi chuyển một vectơ tương tự như những gì được nlme
chuyển tới hàm:
omega2vec <- rep(0.1,30)
eps2omegavec <- rep(0.1,30)
timevec <- rep(1:10,3)
y0vec <- rep(1,30)
v0vec <- rep(0,30)
yeqvec = rep(0,30)
solution_analy_ODE2_modif(omega2 = omega2vec,
esp2omega = eps2omegavec,
time = timevec,
y0 = y0vec,
v0 = v0vec,
yeq = yeqvec)
[1] 1.0000000 0.9520263 0.8187691 0.6209244 0.3833110 0.1321355 -0.1076071 -0.3143798
[9] -0.4718058 -0.5697255 1.0000000 0.9520263 0.8187691 0.6209244 0.3833110 0.1321355
[17] -0.1076071 -0.3143798 -0.4718058 -0.5697255 1.0000000 0.9520263 0.8187691 0.6209244
[25] 0.3833110 0.1321355 -0.1076071 -0.3143798 -0.4718058 -0.5697255
Nó hoạt động. Nó sẽ không hoạt động với phương thức @tpetzoldt, vì vectơ thời gian chuyển từ 10 đến 0, điều này sẽ gây ra sự cố tích hợp. Ở đây tôi thực sự cần phải hack cách nlnme
hoạt động. Hiện nay :
fit <- nlme(model = signal ~ solution_analy_ODE2_modif (esp2omega,omega2,time,y0,v0,yeq),
data = data_simu,
fixed = esp2omega + omega2 + y0 + v0 + yeq ~ 1,
random = y0 ~ 1 ,
groups = ~ ID,
start = c(esp2omega = 0.5,
omega2 = 0.5,
yeq = 0,
y0 = 1,
v0 = 1))
hoạt động như một sự quyến rũ
summary(fit)
Nonlinear mixed-effects model fit by maximum likelihood
Model: signal ~ solution_analy_ODE2_modif(omega2, esp2omega, time, y0, v0, yeq)
Data: data_simu
AIC BIC logLik
-597.4215 -567.7366 307.7107
Random effects:
Formula: list(y0 ~ 1, v0 ~ 1)
Level: ID
Structure: General positive-definite, Log-Cholesky parametrization
StdDev Corr
y0 0.61713329 y0
v0 0.67815548 -0.269
Residual 0.03859165
Fixed effects: esp2omega + omega2 + y0 + v0 + yeq ~ 1
Value Std.Error DF t-value p-value
esp2omega 0.4113068 0.00866821 186 47.45002 0.0000
omega2 1.0916444 0.00923958 186 118.14876 0.0000
y0 0.3848382 0.19788896 186 1.94472 0.0533
v0 0.1892775 0.21762610 186 0.86974 0.3856
yeq 0.0000146 0.00283328 186 0.00515 0.9959
Correlation:
esp2mg omega2 y0 v0
omega2 0.224
y0 0.011 -0.008
v0 0.005 0.030 -0.269
yeq -0.091 -0.046 0.009 -0.009
Standardized Within-Group Residuals:
Min Q1 Med Q3 Max
-3.2692477 -0.6122453 0.1149902 0.6460419 3.2890201
Number of Observations: 200
Number of Groups: 10