I have complex matrices and I need to make sure the matrix does not affect the measurement of my analyte. To do that, I need to prepare samples of my analyte in a solvent and of the matrix reinforced with my analyte. Both curves are going to have 5 concentration points, each done 3 times and, for sake of argument, let's assume both have comparable levels of precision. Then, I linearize these two calibration curves and check if they are parallel, which is where my question lies.

How do I prove that both curves are parallel (meaning no matrix effects are present), within a margin of 5%?

I have found the following method:

  1. Get the y-data of the matrix data and apply them to the calibration function of the solvent and get an "apparent concentration" of the matrix.
  2. Perform a linear regression of the concentration values of the solvent with the apparent concentrations of the matrix.

Then, I have an intercept a and a slope b. A value of b different from 1 indicates a concentration dependent bias. I have both standard deviations of the fit, stdev_a, stdev_b, so I have to determine the confidence interval (95%) of b and check if their values are withing these confidence intervals.

I have found two ways to do that in Excel and I am unsure which one is correct. For example, the slope:

  • Do I use the conf = CONFIDENCE.T(0.05, stdev_b, N-4), get b+-conf, and check if 1 is within this interval b+-conf?
  • Do I use t = T.INV(0.95,N-4), multiply that by the stdev_b, get the range b+-t*stdev_b and check if 1 is within this range?

I have used N-4 for the degrees of freedom, since it's two linear regressions, each consuming one degree of freedom, correct? In this case, N-4 == 11.

In summary:

  • Which function do I use to calculate the confidence intervals for this example in Excel, and
  • Is this method of checking parallelism correct?
  • Is there an easier/simpler or more correct way of doing this parallelism check?
  • 5
    $\begingroup$ I'm voting to close this question as off-topic because it's about statistics. $\endgroup$
    – Mithoron
    Dec 26, 2017 at 17:31
  • 9
    $\begingroup$ I think it's multidisciplinary. Analytical chemistry has a lot of statistics in it. If no one answers, I'll ask on the statistics board. I'm afraid the chemistry behind the question might go over their heads and derail the discussion. $\endgroup$
    – K.Cl
    Dec 26, 2017 at 17:41
  • $\begingroup$ If question gets closed you it might be migrated there. $\endgroup$
    – Mithoron
    Dec 26, 2017 at 17:56

3 Answers 3


Define the angular coefficients of the two resulting lines and evaluate the parallelism between them. Perform the statistical evaluation of the data. For the parallelism test between the curves, considering the equations of the lines to be compared as: $y_1 = a_1 + b_1x + \varepsilon$ and $y_2 = a_2 + b_2x + \varepsilon$, the hypothesis for comparison of the lines slopes is given by: $$H_0 : b_1 = b_2$$ $$H_1 : b_1 \neq b_2$$ When the null hypothesis ($H_0$) is true, the regression lines become: $$y_1 = a_1 + b_3x + \varepsilon$$ $$y_2 = a_2 + b_3x + \varepsilon$$ Considering a significance level of $5\ \%$, the null hypothesis will be true when the test returns a P-Value above $0.05$.

More information in the link: http://www.real-statistics.com/regression/hypothesis-testing-significance-regression-line-slope/comparing-slopes-two-independent-samples/

  • $\begingroup$ Welcome to Chemistry.SE! Please note that formulas can be better expressed with \$\ce{}\$ for chemical formulas/equations, \$\mathrm{}\$ for math term/equations, and \$\pu\$ for units. Also, take a minute to look over the help center to better understand our guidelines and question policies. $\endgroup$
    – A.K.
    Aug 12, 2018 at 19:23

Here's some R code to illustrate one way to handle this situation.

First, let's generate some toy data.


# concentrations
x <- c(1, 1, 1, 3, 3, 3, 10, 10, 10, 30, 30, 30)

# toy parameters
M <- 1000
Bclean <- 20
Bdirty <- -20
SD <- 2

# assembling data into dataframe

clean <- M*x + Bclean + rnorm(mean = 0, sd = SD, n = length(x))
dirty <- (MATRIX_EFFECT)*M*x + Bdirty + rnorm(mean = 0, sd = SD, n = length(x))

df <- 
    tibble(x = x, clean = clean, dirty = dirty) %>% 
    pivot_longer(-x, names_to = 'matrix', values_to = 'response') %>%
    arrange(matrix, x)

Here, x is the independent variable, presumably a concentration. clean and dirty are names for two separate response vectors, one for a "clean" sample matrix and one for a "dirty" sample matrix.

(This way of creating the toy data assumes that you make authentic standards at the same concentrations when you make the calibration curves; but this isn't required for the statistical modeling that follows; the model will work if the two calibration curves both have totally different concentrations.)

I've assumed a value for M, B, SD, and MATRIX_EFFECT in this code, but to see how they would affect results I'd vary them and re-run the below code. The meaning of those parameters is:

  • M is the slope or response coefficient for the "clean" sample matrix.
  • Bclean is the intercept (or offset) for the "clean" sample matrix.
  • Bdirty is the intercept for the "dirty" matrix.
  • SD is the standard deviation (in response units) of both the clean and dirty responses. Here, I've assumed an unrealistically low value, which is helpful for being able to understand the meaning of the parameters returned by the fit.
  • MATRIX_EFFECT is a fraction by which M is modified in the "dirty" matrix relative to the clean matrix.

The result of this code is a "dataframe" that looks like this:

x   matrix  response
<dbl>   <chr>   <dbl>
1   clean   352.590857
1   clean   34.753328
1   clean   365.959853
3   clean   554.485864
3   clean   382.928287
3   clean   -7.990008
10  clean   814.286593
10  clean   941.055911
10  clean   998.846565
30  clean   3480.930678
30  clean   3152.718692
30  clean   2840.198150
1   dirty   -123.531402
1   dirty   48.107685
1   dirty   46.156976
3   dirty   235.697833
3   dirty   368.444690
3   dirty   139.615775
10  dirty   1147.136660
10  dirty   812.492316
10  dirty   1015.146423
30  dirty   3255.479129
30  dirty   3206.667272
30  dirty   3340.837902

We can model this data as follows:

df %>% 
    lm(data=., formula = response ~ x*matrix) %>% 

This is models the response as a linear (lm()) function of a bunch of parameters, with parameters implicitly defined by the formula. response ~ x*matrix means, since x is a continuous variable and matrix in this case is a factor variable, to regress response on concentration interacting with matrix. The results are:

enter image description here

Each row gives results, including standard errors, point estimates, and p-values that the parameter is non-zero, for a single parameter. One subtlety is that the algorithm has implicitly assumed one (of in this case two) levels of our factor variable matrix to be the "baseline", and only parameters for the other non-baseline level (in this case "dirty") are referred to directly. So the results mean:

  • (Intercept) is the model estimate of Bclean. The estimate of 20.28 has an uncertainty of $\pm$ 0.740. Gratifyingly, the confidence interval overlaps the "true" Bclean value we picked for this toy data above.
  • x is the model estimate of M
  • matrixdirty is the model estimate of Bclean - Bdirty
  • x:matrixdirty is the model estimate of M*(1-MATRIX_EFFECT) and is the parameter that you are really interested in. Dividing by M, we see the regression says it is -49.96. Dividing by M, in this case estimated as 1000.02839, gives an estimate for the matrix effect of 0.95004. Thus, for these (made up) numbers, the model would say your dirty matrix has a calibration slope that is 4.996 percent of the clean matrix. Of course, you also have error estimates here. The std.error of 0.0658 is also in response units; after dividing by M, we can see the model thinks it's matrix effect estimate is good to the third decimal place.

These amazing results are because I've picked an SD of 2 for the simulated data, but the response varies between 100 and 30,000. So we're assuming relative error of 2% down to 0.006%. If you pick more realistic values for SD, the std.error of all of the parameters will be much larger.


I think that this is one of those in-between questions. Before migrating, let's define the chemistry better.

(1) Roughly what sort of precision are you expecting? What sort of instrumental precision, what sort of wet chemistry precision, and what sort of sampling precision?

Trying to figure if one error source dominates, or if all three contribute significant error.

(2) You said "Both curves are going to have 5 concentration points, each done 3 times..." I assume you made three different solutions of the sample, then spiked 4 different aliquots each with 4 different amounts of the analyte. Thus you end up with 5 solutions for some sort of instrumental analysis. Correct?

So the 15 solutions analyzed depend on three weighings of sample. Also I assume that you're using 4 different volumes. In other words you the spike an aliquot with a one ml pipette, and the second spike is with a two ml pipette, not two shots from the one ml pipette, and so on...

So I assume that you assume that the spiking is exact and you're not worried about the spiking errors.

A point here is that you seem to accept that the intercepts of the lines will be different. So is there something in the sample matrix that causes the blank value to vary? So although you have 5 points on the line, you seem to only have three different matrix solutions.

(3) When you say you "linearize these two calibration curves", I assume you mean that you used linear least squares to compute slope and intercept of each.

(4) Exactly what do you mean by 5% error.

I assume that you mean a 5% chance of a type I error in slope. But I don't think that is what you want to test. I think you spiked the sample solution with 4 different concentrations of the analyte. So what you really want to know is the difference between the sample values at the end of the line, not variation in the slope itself.

The difference between the non-spiked values will be a lot more sensitive than test on variabilities. You have very small statistics and the variabilities are going to be huge.

The gist here is also considering a type II error and/or using multiple determinations to drive the error to 5%.

  • $\begingroup$ (1) I am doing HPLC at concentrations ranges of 80-120%and errors of 2%. (2) Correct; Correct; The matrix itself has some of the analyte, at a concentration C. I have, then, these concentrations standart dilution : , 80%, 90%, 100% ,110%,120%;matrix:( standart+ feedstook) : 80% 90%, 100%, 110% 120%, with the same dilution of standart. The intercepts will be the same. (3) Yes, that's what I mean. But the slope is important, not the intercept, in this case. $\endgroup$
    – K.Cl
    Dec 26, 2017 at 20:30
  • $\begingroup$ (4)I need to know specifically the slope, because that's in a norm. It states, in full: "The line parallelism is indicative of a lack of interference by the constituents of the matrix and its determination must be realized by an adequate statistical evaluation. The significance level adopted must be 5% in the hypothesis test." So from my basic understanding of statistics, it's a 5% of a type I error in the slope. $\endgroup$
    – K.Cl
    Dec 26, 2017 at 20:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.