0

I'm trying to fit ELISA plate data to a non-linear mixed effect model, using the nlme function. Previously, I asked about how to make the syntax work for nested groups, though responses made it seem like that was a lost cause. However, I'm still having difficulty producing a model object when just applying a single group.

Sample data can be found here: https://pastebin.com/UzVa04TY

This is my original envisioning of running the NLME function, using separate variables for fixed and random effects (upper- and lower-case letters, respectively). x is the sample dilution factor, while y is ELISA signal density.

# Define the 3-parameter logistic function
logistic3PL <- function(x, A, B, C, a, b, c) {
  (A + a) / (1 + (x / (C + c))^ (B + b))
}

# Define the nonlinear mixed effects model
model <- nlme(y ~ logistic3PL(x, A, B, C, a, b, c),
              data = dataSet,
              fixed = A + B + C ~ 1,
              random = a + b + c ~ 1,
              groups =  ~ Plate,
              start = c(A = 1, B = 1, C = 1))

This produced the following error:

Error in nlme.formula(y ~ logistic3PL(x, A, B, C, a, b, c), data = dataSet,  : 
  Singularity in backsolve at level 0, block 1

A commenter from my prior question suggested that he would actually phrase the 3-parameter function to have both the fixed and random effects denoted by the same set of letters.

# Define the 3-parameter logistic function
logistic3PL <- function(x, A, B, C) {
  A / (1 + (x / C) ^B)
}

# Define the nonlinear mixed effects model
model <- nlme(y ~ logistic3PL(x, A, B, C),
              data = dataSet,
              fixed = A + B + C ~ 1,
              random = A + B + C ~ 1,
              groups =  ~ Plate,
              start = c(A = 1, B = 1, C = 1))

This produced an identical error.

I should note that I also attempted replacing random = A + B + C ~ 1, with ... ~ Plate, in both methods. Each time, the program run would think for about 30 seconds before giving the exact same error, as opposed to giving the error immediately when the line ended with ~ 1.

Happy to answer any questions that may help solve the issue. Thank you in advance for your help!

Ben Bolker
  • 211,554
  • 25
  • 370
  • 453
AndyH
  • 1
  • 1
  • link to previous Q is going to https://stackoverflow.com (rather than to a particular question) – Ben Bolker Jul 30 '23 at 20:43
  • I'm still trying to get this work. Have you been able to get a decent fit with `nls()` to the overall data set, i.e. lumping everything together? – Ben Bolker Jul 31 '23 at 01:06
  • Re: 1st comment - that's strange. When I click the link, it takes me to my first question, "Syntax for 'groups' arguments......" I believe you were the one to suggest the simplified set of fixed/random variables. Perhaps try this? https://stackoverflow.com/questions/76518256/syntax-for-groups-argument-in-rs-nlme-function-multiple-groups Re: 2nd comment - I haven't tried the nls function yet. NLME seemed like the natural (or rather, only) fit for our purposes. NLS might be a decent alternate if it can return estimated coefficients for A, B and C. Thank you for trying this out, in any case – AndyH Jul 31 '23 at 03:07
  • Can you explain why you believe this function is a good model for your data? I've tried plotting it with a few arbitrary parameters but the shape looks nothing like your data. (And yes, the second example is the correct syntax.) You should always start by creating a successful `nlsList` fit and then `update` that fit to an `nlme` fit. – Roland Jul 31 '23 at 05:28
  • Also, the example data is small. Please don't send people to another website to copy it. – Roland Jul 31 '23 at 05:30
  • For USDA's validating of our parallel line assays, our dose-response curves must be fitted to a nonlinear mixed effect model, as directed here: https://www.aphis.usda.gov/animal_health/vet_biologics/publications/STATWI0006.pdf The `nlme` function seemed naturally appropriate. In fitting a model, we need to estimate each parameter (A/B/C) and compare those from one vaccine preparation to another. If a different modeling function like `nls` can fit a nonlinear mixed model and return those coefficients, I'm open to exploring it. Thank you – AndyH Jul 31 '23 at 18:01

1 Answers1

1

I suggest using the parameterization given as Equation 1 in the reference [1].

#plot the data
library(ggplot2)
p <- ggplot(dataSet, aes(x, y)) +
  geom_point(aes(color = factor(Plate))) +
  scale_x_continuous(trans = "log2")
print(p)

#we use the other parametrization from the reference
foo <- function(x, A, B, C) A / (1 + exp((C - log2(x))/B))

#or with gradient attribute as suggested by Ben Bolker in the comments
foo <- deriv(~ A / (1 + exp((C - log2(x))/B)),
         namevec = c("A", "B", "C"),
         function.arg = c("x", "A", "B", "C"))

#plot the function to see if the starting values are sensible 
p + geom_function(fun = foo, args = list(A = 1, B = 1, C = 6))

library(nlme)
#first use nlsList
fit1 <- nlsList(y ~ foo(x, A, B, C) | Plate, data = dataSet, 
                start = list(A = 1, B = 1, C = 6))

#then use that fit as input for nlme
#specify no correlation between random effects with pdDiag
fit2 <- nlme(fit1, random = pdDiag(list(A ~ 1, B ~ 1, C ~ 1)), 
             groups = ~ Plate)
summary(fit2)

#plot predictions
p + lapply(1:3, 
           \(i) geom_function(fun = \(x) predict(fit2, newdata = data.frame(x = x, Plate = i)),
                              aes(color = factor(i))))

plot showing data and predictions from the mixed effects model

Most people would agree that three subjects are not enough to estimate a random effect. You should dramatically increased the number of plates. Then maybe you could even estimate random effects with an unstructured covariance matrix (like you attempted in your question), i.e., including correlations.

[1]: https://www.aphis.usda.gov/animal_health/vet_biologics/publications/STATWI0006.pdf


Data:

dataSet <- structure(list(Plate = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3
), x = c(5, 10, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120, 5, 10, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120, 5, 
         10, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120), y = c(0.0128, 0.0269, 0.0611, 0.1783, 0.4333, 0.6237, 0.846, 0.8952, 0.9145, 
        0.9443, 0.9831, 0.0332, 0.042, 0.0755, 0.172, 0.4082, 0.6905, 0.8783, 0.9687, 1.0235, 1.0798, 1.0744, 0.0139, 0.0307, 0.0592, 
        0.1592, 0.4117, 0.641, 0.8177, 0.8915, 0.9636, 1.0324, 0.9909)), row.names = c(NA, 33L), class = "data.frame")
Roland
  • 127,288
  • 10
  • 191
  • 288
  • 1
    Nice. (1) If you want to fit a model with different A, B, C but a common residual variance term you can use `gnls(..., params = A+ B +C ~ Plate)` (2) it is often useful to define the function with `derivs()` as in https://stackoverflow.com/a/76813813/190277 (this makes autodiff gradients available) – Ben Bolker Aug 02 '23 at 01:16