Helmut
★★★
avatar
Homepage
Vienna, Austria,
2012-10-09 21:30
(4210 d 08:19 ago)

Posting: # 9361
Views: 5,613
 

 Analytical error and its consequences [Bioanalytics]

Dear all,

since I didn’t want to go too much off-topic in conversations I had with Franz and Detlew (see this post and followings) I prepared an example about analytical error. Calibration at six levels (replicates) compliant with the GL (CV ≤20% at the LLOQ, ≤15% above; back-calculated inaccuracy ≤±20/15%), linear model, weighting 1/σ².
  x   y       SDy    CV%
  1  0.032  0.0064  20.0
  2  0.062  0.0091  14.7
  4  0.128  0.0173  13.5
  8  0.280  0.0392  14.0
 16  0.520  0.0640  12.3
 32  1.020  0.1224  12.0

I got:
ayx -0.013642 ±0.0019957
byx  0.032667 ±0.000738
R2   0.099797

Not that bad. But what about variability? How reliable are concentrations obtained from the calibration? Imagine samples in the mid-range and close to the LLOQ. We have two measurements (y) each. Can we distinguish between estimated concentrations (x)?
                 y       x       95% CI
mid-range       0.40   12.3   11.7  – 13.0
                0.45   13.8   13.1  – 14.6
close to LLOQ   0.040   1.27   1.13 –  1.40
                0.045   1.42   1.29 –  1.55

Note the asymmetric confidence interval of estimated concentrations, which is caused by the hyperbolic CI of the regression.

[image]

We can reliably distinguish between two concentrations in the mid-range, but not close to the LLOQ since their CIs overlap. That’s not rocket since, but basic stuff.*
When we talk about excluding data points in the estimation of λz we should keep this issue in mind. We must not forget that PK data are not isolated pieces of information, but highly correlated. We should apply scientific judgment (an euphemism for common sense) on which data are included – and which not.


  • Miller JC, Miller JN. Statistics and Chemometrics for Analytical Chemistry. Upper Saddle River; 6th ed 2010: Prentice Hall.

Dif-tor heh smusma 🖖🏼 Довге життя Україна! [image]
Helmut Schütz
[image]

The quality of responses received is directly proportional to the quality of the question asked. 🚮
Science Quotes
drcampos
★    

Brazil,
2012-10-11 20:46
(4208 d 09:02 ago)

@ Helmut
Posting: # 9398
Views: 4,531
 

 Analytical error and its consequences

Dear Helmut,

It is a great pleasure to write to you again. I apologize for disturbing you with my basic query but I tried to find out the same values and I couldn't. Maybe I did something wrong.

                 y       x       95% CI

mid-range       0.40   12.3   11.7  – 13.0

                0.45   13.8   13.1  – 14.6

close to LLOQ   0.040   1.27   1.13 –  1.40

                0.045   1.42   1.29 –  1.55


Could you explain how did you calculate these data?

Best regards,

Daniel
Helmut
★★★
avatar
Homepage
Vienna, Austria,
2012-10-11 22:26
(4208 d 07:22 ago)

@ drcampos
Posting: # 9399
Views: 4,745
 

 CI of concentrations (quick & dirty R)

Dear Daniel!

❝ […] my basic query


Well, most bioanalysts simply ignore this issue. No chromatography data system I know calculates the confidence interval of back-calculated concentrations.

❝ but I tried to find out the same values and I couldn't. Maybe I did something wrong.


Let’s see (coded in [image] for simplicity):
x   <- c(1, 2, 4, 8, 16, 32)
y   <- c(0.032, 0.062, 0.128, 0.280, 0.520, 1.020)
S.y <- c(0.0064, 0.0091, 0.0173, 0.0392, 0.0640, 0.1224)
LR  <- lm(y ~ x, weights = 1/S.y^2)
summary(LR)


Should give:
Call:
lm(formula = y ~ x, weights = 1/S.y^2)
Weighted Residuals:
       1        2        3        4        5        6
 0.10895 -0.21644 -0.07534  0.51095 -0.02040 -0.19588
Coefficients:
              Estimate Std. Error t value Pr(>|t|)
(Intercept) -0.0013642  0.0019957  -0.684    0.532
x            0.0326669  0.0007375  44.291 1.55e-06 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.3018 on 4 degrees of freedom
Multiple R-squared: 0.998,      Adjusted R-squared: 0.9975
F-statistic:  1962 on 1 and 4 DF,  p-value: 1.554e-06


Now let’s set up a data frame containing the x-values where we want to predict the confidence interval of y. I’ll ask R for 100 values in order to get a ‘smooth’ curve later on.
newdata <- data.frame(x=seq(min(x), max(x), length.out=100))

… and calculate the 95% CI at the 100 locations (new) based on the fitted model (LR).
newdata <- data.frame(x = seq(min(x), max(x), length.out = 100))
pred    <- predict(LR, newdata, interval = "confidence", level = 0.95)


Now we can plot this stuff:
plot(x, y, xlim = c(0, 32), ylim = c(0, 1.1), pch = 19, cex = 1.35,
     col = "blue", las = 1)
grid()
arrows(x0 = x, y0 = y-S.y, x1 = x, y1 = y+S.y, length = 0.025,
       angle = 90, code = 3, col = "blue")
abline(LR, lwd = 2, col = "blue")
lines(newdata$x, pred[, 2], col = "red")
lines(newdata$x, pred[, 3], col = "red")


I’m a little bit too short in time in order to set up the bisection method to extract the back-calculated CI of estimated concentrations.* The bisection method would fiddle around with the prediction range in such a way that the CI equals the observed y. We would start from a given response (y) and try to find the two intersections with the CI (to the ‘left’ with the upper CL and to the ‘right’ with the lower CL). Once we have these two values we can calculate the CI of the concentration as usual. Hint: Type pred in R. You see three columns – denoted fit, lwr, and upr.

A quick estimate of the upper value close to the LLOQ in my example (y=0.045):
lo2   <- data.frame(x = seq(1.2, 1.6, by = 0.01))
pred2 <- as.data.frame(predict(LR, lo2, interval = "confidence", level = 0.95))
loc   <- c(which.min(abs(pred2$upr - 0.045)),
           which.min(abs(pred2$fit - 0.045)),
           which.min(abs(pred2$lwr - 0.045)))


By calling print(pred2[loc, ], digits = 5) we get:
        fit      lwr      upr
10 0.040776 0.036501 0.045051
23 0.045023 0.040804 0.049241
36 0.049269 0.045091 0.053448


The 10th and 36th rows of pred2 are pretty close to the intersection we want (at 0.045). Therefore:
print(lo2[range(loc), ])
[1] 1.29 1.55


The 10th and 36th values of lo2 are 1.29 and 1.55 – a first approximation of the CI.

Homework: :-D
For the CI of x=1.42 (from y=0.045) I get by the exact method* in Gnumeric 1.288422087 – 1.547226655. What do you get?


  • See also the end of this post. The intersection of the CI with a given y-value can be solved analytically – but only for a linear model (irrespective of the weighting scheme). The formula is quite lengthy and it took me a lot of paper/brain to come to an end. Since quadratic calibration is also common in analytics, I would only set up a numeric method anyway.

“After-the-dinner-quickshot” for the CI of x with a response of 0.045 (requires the fitted model LR from above):
prec  <- 1e-11                      # target precision
y.obs <- 0.045                      # response
x.est <- (y.obs - coef(LR)[[1]])/coef(LR)[[2]]
for (j in 1:2) {                    # 1: upper CL, 2: lower CL
  if (j == 1) {
    lower <- min(x); upper <- x.est # 1: from LLOQ to x
  } else {
    lower <- x.est; upper <- max(x) # 2: from x to ULOQ
  }
  x.range <- data.frame(x = c(lower, upper))
  CL.est <- predict(LR, x.range, interval="confidence", level=0.95)
  if (j == 1) {
    f.lo <- CL.est[1, 3]; f.hi <- CL.est[2, 3]
  } else {
    f.lo <- CL.est[1, 2]; f.hi <- CL.est[2, 2]
  }
  delta <- upper - lower            # interval
  while (abs(delta) > prec) {       # continue until precision reached
    x.new <- data.frame(x = (lower+upper)/2) # bisection
    if (j == 1) {
      f.new <- predict(LR, x.new, interval="confidence", level=0.95)[1, 3]
    } else {
      f.new <- predict(LR, x.new, interval="confidence", level=0.95)[1, 2]
    }
    if (abs(f.new - y.obs) <= prec) break # precision reached
    if (f.new > y.obs) upper <- x.new     # move limit downwards
    if (f.new < y.obs) lower <- x.new     # move limit upwards
    delta <- upper - lower                # new interval
  }
  ifelse (j == 1, CL.lo <- as.numeric(x.new), CL.hi <- as.numeric(x.new))
}
cat(paste0("response (y): ", y.obs,
           "\nconcent. (x): ", signif(x.est, 5),
           " (95% CI: ", signif(CL.lo, 5), " \u2013 ", signif(CL.hi, 5),
           ")\n"))


Which gives on my machine:
response (y): 0.045
concent. (x): 1.4193 (95% CI: 1.2884 – 1.5472)]


Or alternatively:
x.range <- data.frame(x = c(CL.lo, x.est, CL.hi))
CL.est  <- predict(LR, x.range, interval="confidence", level=0.95)
result  <- cbind(x.range, CL.est)              # add x-values
result  <- result[,names(result)[c(1,4,2,3)]]  # nicer order
print(result, row.names=FALSE)                 # Q.E.D.


Which gives:
      x      upr      fit      lwr
 1.2884 0.045000 0.040725 0.036449
 1.4193 0.049219 0.045000 0.040781
 1.5472 0.053358 0.049179 0.045000


If you still have the plot open:
lines(x=c(-pretty(x)[2], rep(x.est, 2)),
      y=c(rep(y.obs, 2), -pretty(y)[2]), col="blue")
lines(x=rep(CL.lo, 2),
      y=c(coef(LR)[[1]]+coef(LR)[[2]]*CL.lo, -pretty(x)[2]),
      col="red")
lines(x=rep(CL.hi, 2),
      y=c(coef(LR)[[1]]+coef(LR)[[2]]*CL.hi, -pretty(x)[2]),
      col="red")


[image]

Dif-tor heh smusma 🖖🏼 Довге життя Україна! [image]
Helmut Schütz
[image]

The quality of responses received is directly proportional to the quality of the question asked. 🚮
Science Quotes
drcampos
★    

Brazil,
2012-10-16 06:45
(4203 d 23:03 ago)

@ Helmut
Posting: # 9422
Views: 4,343
 

 CI of concentrations (quick & dirty R)

Helmut,

Thank you for your prompt reply and explanation.

Regards, Daniel
UA Flag
Activity
 Admin contact
22,988 posts in 4,825 threads, 1,653 registered users;
80 visitors (0 registered, 80 guests [including 5 identified bots]).
Forum time: 05:49 CEST (Europe/Vienna)

The whole purpose of education is
to turn mirrors into windows.    Sydney J. Harris

The Bioequivalence and Bioavailability Forum is hosted by
BEBAC Ing. Helmut Schütz
HTML5