18  Bottle-to-Bottle Variation

This chapter uses bottle-to-bottle and lot-to-lot comparisons to assess material homogeneity and demonstrates how to convert homogeneity results into an uncertainty component. A significance test only indicates whether a difference was observed; it does not by itself indicate practical impact. The final conclusion must also consider repeatability, target values, and prespecified acceptance criteria.

ImportantScope of Use

The F test from bottle_anova() asks whether all group means are identical; it does not ask whether the difference meets a product-homogeneity limit. tukey() locates pairwise differences only when the overall model and its assumptions are appropriate. Homogeneity uncertainty or bottle SD must be estimated from a variance model consistent with the sampling, measurement order, and replicate design, and then compared with a prespecified technical limit; release or rejection must not rely only on a P value.

Workflow: from bottle differences to homogeneity uncertainty

%%{init: {"theme":"base","themeVariables":{"primaryColor":"#EAF2FB","primaryBorderColor":"#2780E3","primaryTextColor":"#212529","lineColor":"#6C757D","secondaryColor":"#EAF7E6","tertiaryColor":"#FFF4E8","fontSize":"13px"},"flowchart":{"nodeSpacing":18,"rankSpacing":24,"padding":6}}}%%

flowchart LR
    A["Check bottle/lot sampling<br/>and replicate design"] --> B["bottle_anova()<br/>Test between-group differences"]
    B --> C["tukey()<br/>Locate differences when needed"]
    B --> D["precision() / variance()<br/>Decompose between- and within-bottle variance"]
    D --> E["uncertainty_homogeneity()<br/>Homogeneity uncertainty"]
    C --> F["Compare with prespecified homogeneity limit"]
    E --> F
    F --> G["Form release evidence"]

    classDef input fill:#EAF2FB,stroke:#2780E3,color:#212529
    classDef process fill:#EAF7E6,stroke:#3FB618,color:#212529
    classDef output fill:#F4EEFB,stroke:#9954BB,color:#212529
    class A input
    class B,C,D,E,F process
    class G output

Bottle-to-bottle variability evaluates differences among bottles (or lots) of the same product. The analysis generally has two steps: use bottle_anova() to test whether bottle or lot means differ statistically, then use variance-component analysis to separate between-bottle and within-bottle variation and assess whether the magnitude meets prespecified precision limits. This chapter demonstrates 15 bottles × 5 measurements and three lots measured with one control material.

The examples are deterministic teaching data. Statistical significance does not mean that a product is unacceptable; decisions must use acceptance limits specified in advance.

Code
library(ivdtools)
library(readr)

18.1 Main Functions

Function Main purpose Key inputs or outputs
bottle_anova() One-way or nested ANOVA value ~ batch or value ~ batch/vial
tukey() Tukey HSD pairwise comparisons and compact letters term specifies the effect
precision() / variance() Decompose between-bottle and within-bottle variance One-way value ~ bottle

18.1.1 ANOVA, Tukey, and Variance Components

Code
bottle_anova(data, formula, conf.level = 0.95)
tukey(x, term = NULL, conf.level = NULL)

precision(data, value ~ bottle)
variance(x, method = c("auto", "anova", "reml"))
uncertainty_homogeneity(data, unit = "bottle", result = "value", ...)

The one-way model is

\[ Y_{ij}=\mu+B_i+\varepsilon_{ij}, \]

with the global null hypothesis that all bottle means are equal. The classical F statistic is

\[ F=\frac{MS_{\mathrm{between}}}{MS_{\mathrm{within}}}. \]

bottle_anova() removes rows with missing or nonfinite responses or grouping values, converts right-hand variables to factors, and fits with aov(). A nested formula such as value ~ lot/vial expands to lot and lot:vial. The object also reports group-mean t intervals, the Bartlett homogeneity test, and a Shapiro–Wilk test on model residuals. These assumption checks do not automatically select a model.

tukey() performs Tukey HSD for the specified effect and returns differences, simultaneous intervals, adjusted P values, and compact letters. In a complex model, verify the default effect and supply term explicitly when necessary. Variance-component analysis estimates

\[ \sigma_{\mathrm{total}}^2=\sigma_B^2+\sigma_e^2, \]

so between-bottle SD is \(\sqrt{\sigma_B^2}\); this is a different scale from the ANOVA P value. Homogeneity conclusions should focus on the prespecified maximum between-bottle variation or its uncertainty contribution.

bottle_anova() returns the ANOVA model and between-group diagnostics; tukey() returns pairwise comparisons; precision()/variance() return variance components; and uncertainty_homogeneity() returns a standard uncertainty component for homogeneity.

18.2 Analysis Examples

18.2.1 Example 1: 15 Bottles × 5 Measurements

18.2.1.1 Read and Check Data

Code
bottle <- read_csv("./data/018-瓶间差/bottle-difference.csv", show_col_types = FALSE)
bottle <- as.data.frame(bottle)
bottle$bottle <- factor(bottle$bottle)
head(bottle, 6)
  bottle replicate  value
1    B01         1  97.52
2    B01         2 100.53
3    B01         3  98.81
4    B01         4  97.55
5    B01         5  97.85
6    B02         1 100.92

18.2.1.2 Step 1: Test for Significant Differences

The one-way model value ~ bottle tests whether the 15 bottle means are equal:

Code
ba <- bottle_anova(bottle, value ~ bottle)
print(ba)

Bottle ANOVA Analysis
  Response:        value
  Group variable:  bottle (15 groups)
  Complete cases:  75 / 75
  Confidence level: 0.95

  Descriptive Statistics:
 ------------------------------------------------------------
  bottle              n      Mean        SD
 ------------------------------------------------------------
  B01                 5   98.4520    1.2739
  B02                 5  101.7140    0.9360
  B03                 5   99.8060    0.8153
  B04                 5  102.7980    0.4992
  B05                 5   98.7520    0.8662
  B06                 5  100.0960    1.4971
  B07                 5  100.7180    0.8466
  B08                 5  100.5000    1.3179
  B09                 5   99.8160    1.0246
  B10                 5  101.6400    0.8034
  B11                 5   99.3300    0.9360
  B12                 5   98.4700    1.0884
  B13                 5   99.7440    1.1004
  B14                 5  100.9040    0.4803
  B15                 5   99.7920    1.4477

  ANOVA Table:
 ------------------------------------------------------------
  Term              Df    Sum Sq    Mean Sq   F-value    p-value  Sig
 ------------------------------------------------------------
  bottle            14  107.7233     7.6945    7.1460     0.0000  ***
  Residuals         60   64.6057     1.0768                       
 ------------------------------------------------------------
  Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

  Assumption Checks:
    Bartlett test: K-squared = 9.9325, df = 14, p-value = 0.7671 (OK)
    Shapiro-Wilk:  W = 0.9828, p-value = 0.4011 (OK)

  Post-hoc: use tukey() for pairwise comparisons

The ANOVA table provides the between-bottle F test; assumptions reports Bartlett’s test of variance homogeneity and the Shapiro–Wilk normality test.

18.2.1.3 Pairwise Comparisons

Code
posthoc <- tukey(ba)
posthoc

Tukey HSD Post-hoc Test
  Response:        value
  Complete cases:  75 / 75
  Confidence level: 0.95

  Term: bottle 
 ------------------------------------------------------------
  Comparison                Diff       lwr       upr   p-value  Sig
 ------------------------------------------------------------
  B02-B01                 3.2620    0.9412    5.5828    0.0005  ***
  B03-B01                 1.3540   -0.9668    3.6748    0.7526  
  B04-B01                 4.3460    2.0252    6.6668    0.0000  ***
  B05-B01                 0.3000   -2.0208    2.6208    1.0000  
  B06-B01                 1.6440   -0.6768    3.9648    0.4526  
  B07-B01                 2.2660   -0.0548    4.5868    0.0624  
  B08-B01                 2.0480   -0.2728    4.3688    0.1408  
  B09-B01                 1.3640   -0.9568    3.6848    0.7431  
  B10-B01                 3.1880    0.8672    5.5088    0.0008  ***
  B11-B01                 0.8780   -1.4428    3.1988    0.9896  
  B12-B01                 0.0180   -2.3028    2.3388    1.0000  
  B13-B01                 1.2920   -1.0288    3.6128    0.8078  
  B14-B01                 2.4520    0.1312    4.7728    0.0287  *
  B15-B01                 1.3400   -0.9808    3.6608    0.7656  
  B03-B02                -1.9080   -4.2288    0.4128    0.2229  
  B04-B02                 1.0840   -1.2368    3.4048    0.9391  
  B05-B02                -2.9620   -5.2828   -0.6412    0.0026  **
  B06-B02                -1.6180   -3.9388    0.7028    0.4794  
  B07-B02                -0.9960   -3.3168    1.3248    0.9686  
  B08-B02                -1.2140   -3.5348    1.1068    0.8674  
  B09-B02                -1.8980   -4.2188    0.4228    0.2298  
  B10-B02                -0.0740   -2.3948    2.2468    1.0000  
  B11-B02                -2.3840   -4.7048   -0.0632    0.0384  *
  B12-B02                -3.2440   -5.5648   -0.9232    0.0006  ***
  B13-B02                -1.9700   -4.2908    0.3508    0.1830  
  B14-B02                -0.8100   -3.1308    1.5108    0.9952  
  B15-B02                -1.9220   -4.2428    0.3988    0.2134  
  B04-B03                 2.9920    0.6712    5.3128    0.0022  **
  B05-B03                -1.0540   -3.3748    1.2668    0.9507  
  B06-B03                 0.2900   -2.0308    2.6108    1.0000  
  B07-B03                 0.9120   -1.4088    3.2328    0.9852  
  B08-B03                 0.6940   -1.6268    3.0148    0.9990  
  B09-B03                 0.0100   -2.3108    2.3308    1.0000  
  B10-B03                 1.8340   -0.4868    4.1548    0.2778  
  B11-B03                -0.4760   -2.7968    1.8448    1.0000  
  B12-B03                -1.3360   -3.6568    0.9848    0.7693  
  B13-B03                -0.0620   -2.3828    2.2588    1.0000  
  B14-B03                 1.0980   -1.2228    3.4188    0.9330  
  B15-B03                -0.0140   -2.3348    2.3068    1.0000  
  B05-B04                -4.0460   -6.3668   -1.7252    0.0000  ***
  B06-B04                -2.7020   -5.0228   -0.3812    0.0092  **
  B07-B04                -2.0800   -4.4008    0.2408    0.1258  
  B08-B04                -2.2980   -4.6188    0.0228    0.0549  
  B09-B04                -2.9820   -5.3028   -0.6612    0.0023  **
  B10-B04                -1.1580   -3.4788    1.1628    0.9026  
  B11-B04                -3.4680   -5.7888   -1.1472    0.0002  ***
  B12-B04                -4.3280   -6.6488   -2.0072    0.0000  ***
  B13-B04                -3.0540   -5.3748   -0.7332    0.0016  **
  B14-B04                -1.8940   -4.2148    0.4268    0.2326  
  B15-B04                -3.0060   -5.3268   -0.6852    0.0020  **
  B06-B05                 1.3440   -0.9768    3.6648    0.7619  
  B07-B05                 1.9660   -0.3548    4.2868    0.1854  
  B08-B05                 1.7480   -0.5728    4.0688    0.3515  
  B09-B05                 1.0640   -1.2568    3.3848    0.9470  
  B10-B05                 2.8880    0.5672    5.2088    0.0037  **
  B11-B05                 0.5780   -1.7428    2.8988    0.9999  
  B12-B05                -0.2820   -2.6028    2.0388    1.0000  
  B13-B05                 0.9920   -1.3288    3.3128    0.9696  
  B14-B05                 2.1520   -0.1688    4.4728    0.0968  
  B15-B05                 1.0400   -1.2808    3.3608    0.9556  
  B07-B06                 0.6220   -1.6988    2.9428    0.9997  
  B08-B06                 0.4040   -1.9168    2.7248    1.0000  
  B09-B06                -0.2800   -2.6008    2.0408    1.0000  
  B10-B06                 1.5440   -0.7768    3.8648    0.5576  
  B11-B06                -0.7660   -3.0868    1.5548    0.9972  
  B12-B06                -1.6260   -3.9468    0.6948    0.4711  
  B13-B06                -0.3520   -2.6728    1.9688    1.0000  
  B14-B06                 0.8080   -1.5128    3.1288    0.9953  
  B15-B06                -0.3040   -2.6248    2.0168    1.0000  
  B08-B07                -0.2180   -2.5388    2.1028    1.0000  
  B09-B07                -0.9020   -3.2228    1.4188    0.9866  
  B10-B07                 0.9220   -1.3988    3.2428    0.9837  
  B11-B07                -1.3880   -3.7088    0.9328    0.7199  
  B12-B07                -2.2480   -4.5688    0.0728    0.0670  
  B13-B07                -0.9740   -3.2948    1.3468    0.9739  
  B14-B07                 0.1860   -2.1348    2.5068    1.0000  
  B15-B07                -0.9260   -3.2468    1.3948    0.9831  
  B09-B08                -0.6840   -3.0048    1.6368    0.9992  
  B10-B08                 1.1400   -1.1808    3.4608    0.9125  
  B11-B08                -1.1700   -3.4908    1.1508    0.8956  
  B12-B08                -2.0300   -4.3508    0.2908    0.1498  
  B13-B08                -0.7560   -3.0768    1.5648    0.9976  
  B14-B08                 0.4040   -1.9168    2.7248    1.0000  
  B15-B08                -0.7080   -3.0288    1.6128    0.9988  
  B10-B09                 1.8240   -0.4968    4.1448    0.2859  
  B11-B09                -0.4860   -2.8068    1.8348    1.0000  
  B12-B09                -1.3460   -3.6668    0.9748    0.7601  
  B13-B09                -0.0720   -2.3928    2.2488    1.0000  
  B14-B09                 1.0880   -1.2328    3.4088    0.9374  
  B15-B09                -0.0240   -2.3448    2.2968    1.0000  
  B11-B10                -2.3100   -4.6308    0.0108    0.0523  
  B12-B10                -3.1700   -5.4908   -0.8492    0.0009  ***
  B13-B10                -1.8960   -4.2168    0.4248    0.2312  
  B14-B10                -0.7360   -3.0568    1.5848    0.9982  
  B15-B10                -1.8480   -4.1688    0.4728    0.2668  
  B12-B11                -0.8600   -3.1808    1.4608    0.9914  
  B13-B11                 0.4140   -1.9068    2.7348    1.0000  
  B14-B11                 1.5740   -0.7468    3.8948    0.5257  
  B15-B11                 0.4620   -1.8588    2.7828    1.0000  
  B13-B12                 1.2740   -1.0468    3.5948    0.8226  
  B14-B12                 2.4340    0.1132    4.7548    0.0311  *
  B15-B12                 1.3220   -0.9988    3.6428    0.7819  
  B14-B13                 1.1600   -1.1608    3.4808    0.9015  
  B15-B13                 0.0480   -2.2728    2.3688    1.0000  
  B15-B14                -1.1120   -3.4328    1.2088    0.9266  
 ------------------------------------------------------------
  Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

  Compact Letter Display:
 ------------------------------------------------------------
    B04: a
    B02: a
    B10: a
    B14: a
    B07: ab
    B08: ab
    B06: b
    B09: b
    B03: b
    B15: b
    B13: b
    B11: b
    B05: b
    B12: b
    B01: b

Tukey HSD returns all pairwise comparisons and compact letter display. Bottles with different letters have significantly different means. With many bottles, some significant differences are expected; the key question is whether the magnitude is acceptable.

18.2.1.4 Step 2: Does the Magnitude Meet the Requirement?

Use precision(value ~ bottle) to split total variation into between-bottle and within-bottle components:

Code
bp <- precision(bottle, value ~ bottle)
bp <- variance(bp)
Variance components
 sample component     VC %Total     SD  CV[%] Method
----------------------------------------------------
    all    bottle 1.3236   55.1 1.1505 1.1485  anova
    all     error 1.0768   44.9 1.0377 1.0359  anova 

Between-bottle SD is approximately 1.15 (CV approximately 1.15%) and within-bottle SD is approximately 1.04 (CV approximately 1.04%). If the protocol specifies a total precision limit of 2% CV, the between-bottle difference is within the illustrative limit. Note: this limit is an example; a formal study must use the limit specified by the protocol or standard and compare both total CV (including between- and within-bottle components) and the individual components.

Code
bp <- ci(bp)
Confidence intervals (SD)
 sample component estimate  lower  upper
----------------------------------------
    all     total   1.5493 1.2449 2.0519
    all    bottle   1.1505 0.4254 1.5704
    all     error   1.0377 0.8807 1.2633 

Confidence intervals (%CV)
 sample component estimate  lower  upper
----------------------------------------
    all     total   1.5467 1.2428 2.0485
    all    bottle   1.1485 0.4246 1.5678
    all     error   1.0359 0.8792 1.2612 

18.2.2 Example 2: Three Reagent Lots Measuring One Control

The three reagent lots jointly form one between-lot model and therefore must not be split by lot. If a raw long table also contains multiple products or analytes, first use split_by_factors(data, variable="product") to split independent evaluation projects; retain all lots within each subset and call bottle_anova() or precision() separately.

18.2.2.1 Read Data

Three reagent lots (L1, L2, L3) each measure the same control 15 times:

Code
lot_data <- read_csv("./data/018-瓶间差/reagent-lot.csv", show_col_types = FALSE)
lot_data <- as.data.frame(lot_data)
lot_data$lot <- factor(lot_data$lot)

18.2.2.2 Test Lot-to-Lot Differences

Code
ba_lot <- bottle_anova(lot_data, value ~ lot)
print(ba_lot)

Bottle ANOVA Analysis
  Response:        value
  Group variable:  lot (3 groups)
  Complete cases:  45 / 45
  Confidence level: 0.95

  Descriptive Statistics:
 ------------------------------------------------------------
  lot                 n      Mean        SD
 ------------------------------------------------------------
  L1                 15  100.8173    1.1626
  L2                 15  101.2760    1.4271
  L3                 15  100.5673    0.7829

  ANOVA Table:
 ------------------------------------------------------------
  Term              Df    Sum Sq    Mean Sq   F-value    p-value  Sig
 ------------------------------------------------------------
  lot                2    3.8754     1.9377    1.4528     0.2454   
  Residuals         42   56.0183     1.3338                       
 ------------------------------------------------------------
  Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

  Assumption Checks:
    Bartlett test: K-squared = 4.6245, df = 2, p-value = 0.0990 (OK)
    Shapiro-Wilk:  W = 0.9817, p-value = 0.6870 (OK)

  Post-hoc: use tukey() for pairwise comparisons
Code
tukey(ba_lot)

Tukey HSD Post-hoc Test
  Response:        value
  Complete cases:  45 / 45
  Confidence level: 0.95

  Term: lot 
 ------------------------------------------------------------
  Comparison                Diff       lwr       upr   p-value  Sig
 ------------------------------------------------------------
  L2-L1                   0.4587   -0.5659    1.4832    0.5269  
  L3-L1                  -0.2500   -1.2745    0.7745    0.8247  
  L3-L2                  -0.7087   -1.7332    0.3159    0.2245  
 ------------------------------------------------------------
  Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

  Compact Letter Display:
 ------------------------------------------------------------
    L2: a
    L1: a
    L3: a

Compact letters show which lots have significantly different means and the direction of the differences. Even a statistically significant lot-mean difference must be evaluated against a prespecified acceptable bias, such as the target range for a control; a P value alone is insufficient.

18.2.3 Standard Case: YY/T 1709-2020 Calibrator Homogeneity

Appendix A samples 14 bottles of a CRP calibrator from one lot and measures each three times, using one-way ANOVA to separate between- and within-bottle variation.

Code
yyt1709_homogeneity <- read_csv(
  "./data/018-瓶间差/std-yyt1709-homogeneity.csv",
  show_col_types = FALSE
)
yyt1709_homogeneity$bottle <- factor(yyt1709_homogeneity$bottle)

yyt1709_fit <- ivdtools::bottle_anova(
  yyt1709_homogeneity, result ~ bottle, conf.level = 0.95
)
yyt1709_fit

Bottle ANOVA Analysis
  Response:        result
  Group variable:  bottle (14 groups)
  Complete cases:  42 / 42
  Confidence level: 0.95

  Descriptive Statistics:
 ------------------------------------------------------------
  bottle              n      Mean        SD
 ------------------------------------------------------------
  1                   3    7.1433    0.1415
  2                   3    7.2333    0.1301
  3                   3    7.0433    0.1210
  4                   3    7.1333    0.1106
  5                   3    7.0233    0.0723
  6                   3    7.1133    0.0987
  7                   3    7.2533    0.1069
  8                   3    7.2033    0.0462
  9                   3    7.2500    0.0872
  10                  3    7.1767    0.1115
  11                  3    7.0767    0.1234
  12                  3    7.1433    0.1115
  13                  3    7.1633    0.1563
  14                  3    7.0800    0.1153

  ANOVA Table:
 ------------------------------------------------------------
  Term              Df    Sum Sq    Mean Sq   F-value    p-value  Sig
 ------------------------------------------------------------
  bottle            13    0.2114     0.0163    1.2807     0.2809   
  Residuals         28    0.3556     0.0127                       
 ------------------------------------------------------------
  Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

  Assumption Checks:
    Bartlett test: K-squared = 3.1688, df = 13, p-value = 0.9973 (OK)
    Shapiro-Wilk:  W = 0.9532, p-value = 0.0839 (OK)

  Post-hoc: use tukey() for pairwise comparisons
Code
yyt1709_u <- ivdtools::uncertainty_homogeneity(
  yyt1709_homogeneity, unit = "bottle", result = "result"
)
yyt1709_u

Between-unit homogeneity uncertainty
  Units                             : 14
  Results                           : 42
  Mean                              : 7.1454762
  Effective degrees of freedom      : 3
  Average replicates                : 3
  Within-unit mean square           : 0.0127
  Between-unit mean square          : 0.016264652
  Within-unit degrees of freedom    : 28
  Between-unit degrees of freedom   : 13
  Repeatability SD                  : 0.11269428
  Between-unit SD                   : 0.034470529
  Hidden inhomogeneity              : 0.033636378
  Between-unit uncertainty          : 0.034470529
  Relative repeatability            : 0.015771416
  Relative between-unit             : 0.0048241052
  Relative hidden inhomogeneity     : 0.0047073669
  Relative between-unit uncertainty : 0.0048241052
YY/T 1709 Appendix A: comparison with the standard
Standard position Statistic Standard result ivdtools result
YY/T 1709 Appendix A SS_between 0.211440 0.211440
YY/T 1709 Appendix A SS_within 0.355600 0.355600
YY/T 1709 Appendix A MS_between 0.016265 0.016265
YY/T 1709 Appendix A MS_within 0.012700 0.012700
YY/T 1709 Appendix A F 1.280681 1.280681
YY/T 1709 Appendix A u_bb 0.034492 0.034471