Computational Provenance & Reproducibility Record

RAISINS - R and AI Solutions for INferential Statistics · Online Statistical Analysis Platform for Agricultural Research

Computational Provenance & Reproducibility Record Line × Tester Analysis · 2.0.0 · DOI 10.5281/zenodo.21392610

Computational Provenance & Reproducibility Record

RAISINS· Line × Tester Analysis Module

This Computational Provenance Record documents the statistical computing environment, software dependencies, computational provenance, and bibliographic references associated with the RAISINS Line × Tester Analysis module. It is intended to support computational reproducibility and software transparency. Detailed statistical methodology, mathematical derivations, and user guidance are provided separately in the official module tutorial.

Any issues or updates required please comment here

Go to the App from here

1 Module Metadata

Parameter Specification
Module Line × Tester Analysis
Module Version 2.0.0
DOI 10.5281/zenodo.21392610
Document Type Computational workflow
Statistical Engine R
R Version 4.5.2
Reproducibility Execution Environment: Posit Connect · GCR · renv-locked

2 Statistical Dependency Manifest

Package Version Repository Core Statistical Functions
agricolae 1.3-7 CRAN LSD.test(), HSD.test()
linetester 2.0.0 RAISINS Native linetester()
stats 4.5.2 Base R aov(), anova(), pf(), pt(), qt(), tapply(), mean()

3 Statistical Function Registry

Analytical Role Primary Function(s)
Line × Tester ANOVA linetester(), built on stats::aov() and stats::anova()
F-tests of the combined table stats::pf()
Combining ability effects (GCA, SCA) linetester(), via stats::tapply() marginal means
Standard errors of effects linetester()
Genetic components & contribution linetester()
Mean comparison agricolae::LSD.test(), agricolae::HSD.test()
Significance of effects & heterosis stats::pt()
Critical difference values stats::qt()

Note : linetester() function created by RAISINS team.

4 Default Methods & Parameters

Analysis Step / Parameter Default Method / Value
Line × Tester
ANOVA

(linetester())
Input structure One column identifying the replication, one for the lines, one for the testers, followed by one column per trait; all traits are analysed in a single run. Parent entries are those rows in which the line or the tester is absent (NA); the remaining complete rows form the crosses. The cross label is formed internally from the line and tester factors
ANOVA with parents aov(Y ~ Replications + Treatments) fitted to the full data set (parents and crosses), giving Replications, Treatments, and Error
Full ANOVA Ten-line table assembled from three fitted models: Replications and Treatments from the model above, Parents from aov(Y ~ Parents) on the parent rows, Crosses from aov(Y ~ Crosses) on the cross rows, Lines / Testers / Lines × Testers from aov(Y ~ Lines * Testers) fitted to the cross rows, Error, and Total. Parents vs. Crosses is obtained by subtraction (Treatments − Parents − Crosses)
F-tests Mean squares are recomputed as SS / df. Lines and Testers are tested against the Lines × Testers mean square; every other source is tested against the Error mean square. P-values from 1 - pf(F, df1, df2) with the denominator degrees of freedom matched accordingly
Significance
& Rounding
Level (α) 0.05 (default) or 0.01
Star codes & rounding ** p ≤ 0.01, * 0.01 < p ≤ 0.05, NS where the p-value is undefined; results rounded to 2 decimal places by default
Mean
Comparison
Method LSD (default) via agricolae::LSD.test(), or TUKEY via agricolae::HSD.test(); both are supplied the Error degrees of freedom and mean square from the full ANOVA
Reporting rule Critical difference (CD) or HSD values are displayed only when the treatment effect is significant at α; the coefficient of variation is taken from the LSD.test() statistics
Combining
Ability
Effects Estimated from the cross means only. All effects are formed from the matrix of cross cell means. GCA of a line = its row mean − the mean of the cell means; GCA of a tester = its column mean − the mean of the cell means; SCA of a cross = cell mean − line row mean − tester column mean + the mean of the cell means. Effects are rounded to 3 decimals inside the engine
Standard errors With Me the Error mean square, r replications, l lines and t testers: SE(gi) = √(Me/(r·t)) for lines and √(Me/(r·l)) for testers; SE(sij) = √(Me/r); the corresponding difference errors are √2 times these values
Significance t = effect / SE, tested against the Error degrees of freedom as p = 2 × pt(-|t|, df); SE(gi), SE(gi − gj), SE(sij) and SE(sij − skl) are appended to the tables
Proportional
Contribution
Components Percentage contribution of Lines, Testers, and Lines × Testers, each as 100 × its sum of squares / the Crosses sum of squares
Genetic
Components
Covariances Half-sib covariance of lines = (MSlines − MSL×T)/(r·t) and of testers = (MStesters − MSL×T)/(r·l); the average half-sib covariance pools both against MSL×T; the average full-sib covariance is formed from the Lines, Testers and Lines × Testers mean squares each less the Error mean square
Additive & dominance variance Var(A) = 4 × Cov(HS, average) / (1 + F) and Var(D) = 2 × (MSL×T − Me) / (r × (1 + F)). Both are computed at F = 0 and F = 1; the module reports the F = 1 estimates, appropriate to inbred parents
Heterosis Mid-parent (relative) 100 × (F1 − MP) / MP, where MP = (P1 + P2) / 2; SE = √(1.5 × Me / r). Throughout the heterosis section Me is the Error mean square of the ANOVA with parents, since the comparison involves parent entries
Better-parent (heterobeltiosis) 100 × (F1 − BP) / BP, where BP = max(P1, P2); SE = √(2 × Me / r)
Standard heterosis 100 × (F1 − SC) / SC, where SC is the user-supplied standard check mean; SE = √(2 × Me / r). All three are tested as t = difference / SE against the Error degrees of freedom, with CD at 5% and 1% from qt(0.975, df) and qt(0.995, df)
Genetic
Parameters
Variances σ²g = (MSt − MSe) / r, with negative estimates set to zero; σ²e = MSe; σ²p = σ²g + σ²e
Coefficients of variation GCV, PCV, and ECV as 100 × √(σ²) / grand mean
Heritability & advance Broad-sense heritability = 100 × σ²g / σ²p; genetic advance = k × √(σ²p) × h², with k = 2.06 at 5% selection intensity; GA as a percentage of the grand mean

5 R Code for Key Analytical Steps

The linetester function is provided below for reproducibility, followed by a single worked example on a 5 line × 3 tester dataset with parents.

For any issues in the function please comment here

5.1 The linetester() function

linetester <- function(replications, lines, testers, y) {

  replications <- as.factor(replications)
  lines        <- as.factor(lines)
  testers      <- as.factor(testers)

  datos <- data.frame(
    Replications = replications,
    Lines        = lines,
    Testers      = testers,
    Y            = y
  )

  n_r <- nlevels(datos$Replications)
  n_l <- nlevels(datos$Lines)
  n_t <- nlevels(datos$Testers)

  # ---- crosses (complete rows) and parents (line or tester absent) ----
  datos2 <- na.omit(datos)                                        # crosses
  datos3 <- subset(datos, is.na(datos$Lines) | is.na(datos$Testers))  # parents

  # ---- ANOVA with parents: Replications, Treatments, Error ----
  Treatments <- as.factor(paste(datos$Lines, datos$Testers))
  modelo1 <- aov(Y ~ Replications + Treatments, data = datos)
  matriz1 <- as.matrix(anova(modelo1))

  # ---- Lines, Testers, Lines x Testers: fitted on the CROSSES only ----
  modelo4 <- aov(Y ~ Lines * Testers, data = datos2)
  matriz4 <- as.matrix(anova(modelo4))

  # ---- Parents ----
  Parents <- as.factor(paste(datos3$Lines, datos3$Testers))
  modelo2 <- aov(Y ~ Parents, data = datos3)
  matriz2 <- as.matrix(anova(modelo2))

  # ---- Crosses ----
  Crosses <- as.factor(paste(datos2$Lines, datos2$Testers))
  modelo3 <- aov(Y ~ Crosses, data = datos2)
  matriz3 <- as.matrix(anova(modelo3))

  # ---- cell means of the crosses ----
  mm_cross <- tapply(datos2$Y, datos2[, c("Lines", "Testers")], mean, na.rm = TRUE)
  gm_cell  <- mean(mm_cross, na.rm = TRUE)

  # ---- SCA: cell - line marginal - tester marginal + grand ----
  SCA <- round(
    mm_cross -
      outer(rowMeans(mm_cross, na.rm = TRUE),
            colMeans(mm_cross, na.rm = TRUE), "+") +
      gm_cell,
    3
  )

  # ---- GCA of lines and testers, from the cell means ----
  GCA.lines   <- round(rowMeans(mm_cross, na.rm = TRUE) - gm_cell, 3)
  GCA.testers <- round(colMeans(mm_cross, na.rm = TRUE) - gm_cell, 3)

  # ---- assemble the ten-line table ----
  # Parents vs. Crosses by subtraction: Treatments - Parents - Crosses.
  # Only df and SS are meaningful here; columns 3-5 are recomputed below.
  matriz5 <- matriz1[2, ] - matriz2[1, ] - matriz3[1, ]

  matriz <- rbind(
    matriz1[1:2, ],   # Replications, Treatments
    matriz2[1, ],     # Parents
    matriz5,          # Parents vs. Crosses
    matriz3[1, ],     # Crosses
    matriz4[1:3, ],   # Lines, Testers, Lines x Testers
    matriz1[3, ]      # Error
  )
  matriz <- rbind(matriz, c(sum(matriz1[, 1]), sum(matriz1[, 2]), NA, NA, NA))

  rownames(matriz) <- c(
    "Replications", "Treatments", "Parents", "Parents vs. Crosses",
    "Crosses", "Lines", "Testers", "Lines x Testers", "Error", "Total"
  )

  # ---- F-tests: Lines (6) and Testers (7) against Lines x Testers (8);
  #      every other source against Error (9) ----
  for (i in 1:9) {
    matriz[i, 3] <- matriz[i, 2] / matriz[i, 1]
    matriz[i, 4] <- matriz[i, 3] / matriz[9, 3]
    matriz[i, 5] <- 1 - pf(matriz[i, 4], matriz[i, 1], matriz[9, 1])

    if (i == 6 || i == 7) {
      matriz[i, 4] <- matriz[i, 3] / matriz[8, 3]
      matriz[i, 5] <- 1 - pf(matriz[i, 4], matriz[i, 1], matriz[8, 1])
    }
  }

  # ---- standard errors, with Me the Error mean square of the full table ----
  Me <- matriz[9, 3]
  SE <- list(
    gca_line     = sqrt(Me / (n_r * n_t)),
    gca_tester   = sqrt(Me / (n_r * n_l)),
    sca          = sqrt(Me / n_r),
    gi_gj_line   = sqrt(2 * Me / (n_r * n_t)),
    gi_gj_tester = sqrt(2 * Me / (n_r * n_l)),
    sij_skl      = sqrt(2 * Me / n_r)
  )

  # ---- genetic components ----
  MS_l  <- matriz[6, 3]
  MS_t  <- matriz[7, 3]
  MS_lt <- matriz[8, 3]

  cov1 <- (MS_l - MS_lt) / (n_r * n_t)                      # Cov HS, lines
  cov2 <- (MS_t - MS_lt) / (n_r * n_l)                      # Cov HS, testers
  cov3 <- (((n_l - 1) * MS_l + (n_t - 1) * MS_t) / (n_l + n_t - 2) - MS_lt) /
          (n_r * (2 * n_l * n_t - n_l - n_t))               # Cov HS, average
  cov4 <- ((MS_l - Me) + (MS_t - Me) + (MS_lt - Me)) / (3 * n_r)  # Cov FS, average

  Fcoef <- 0
  varA0 <- cov3 * (4 / (1 + Fcoef))
  varD0 <- ((MS_lt - Me) / n_r) * (2 / (1 + Fcoef))

  Fcoef <- 1
  varA1 <- cov3 * (4 / (1 + Fcoef))
  varD1 <- ((MS_lt - Me) / n_r) * (2 / (1 + Fcoef))

  Genetic <- list(
    Cov_HS_line   = cov1,
    Cov_HS_tester = cov2,
    Cov_HS_avg    = cov3,
    Cov_FS_avg    = cov4,
    VarA_F0 = varA0, VarA_F1 = varA1,
    VarD_F0 = varD0, VarD_F1 = varD1
  )

  # ---- proportional contribution to the Crosses sum of squares ----
  Contributions <- list(
    lines   = matriz[6, 2] * 100 / matriz[5, 2],
    testers = matriz[7, 2] * 100 / matriz[5, 2],
    lxt     = matriz[8, 2] * 100 / matriz[5, 2]
  )

  return(list(
    ANOVA_with_parents = matriz1,
    ANOVA_LxT          = matriz4,
    ANOVA_full         = matriz,
    GCA_lines          = GCA.lines,
    GCA_testers        = GCA.testers,
    SCA                = SCA,
    SE                 = SE,
    Genetic            = Genetic,
    Contribution       = Contributions
  ))
}

5.2 Worked Example: Input Data

Five lines (L1-L5) crossed with three testers (T6-T8) in four replications, together with the fifteen cross rows, the five line parents (tester absent) and the three tester parents (line absent). Parent entries are marked by NA.

dataset <- data.frame(
  line = c(rep(c("L1","L2","L3","L4","L5"), each = 12),
           rep(c("L1","L2","L3","L4","L5"), each = 4),
           rep(NA, 12)),
  tester = c(rep(rep(c("T6","T7","T8"), each = 4), 5),
             rep(NA, 20),
             rep(c("T6","T7","T8"), each = 4)),
  Replication = rep(1:4, 23),
  Yield = c(
    74.4, 70.86, 60.94, 68, 91.82, 99.18, 118.88, 120.68, 48.08, 62.1, 58.54, 41.84,
    59.06, 65.62, 81.62, 86.76, 84.16, 109.74, 102.14, 94.52, 96.92, 91.44, 79.86, 74.38,
    109.86, 98.16, 93.26, 102.26, 117.2, 100.28, 116.16, 112.52, 109.68, 116.48, 123.92, 120.86,
    103.14, 109.66, 90.98, 119.4, 53.4, 60.86, 74.46, 69.08, 53.86, 48.3, 40.64, 44.62,
    98.46, 73.1, 89.18, 75.86, 81.36, 72.82, 89.82, 83.74, 86.62, 94.18, 90.32, 108.16,
    104.86, 84.32, 76.92, 76.48,
    88.02, 106.54, 89.82, 108.68,
    77.94, 71.34, 77.52, 69.48,
    80.82, 106.52, 83.28, 95.92,
    59.96, 52.48, 52.98, 50.98,
    96.44, 98.82, 99.14, 107.16,
    91.44, 99.66, 83.28, 89.46,
    91.78, 84.82, 69.92, 81.48
  ),
  stringsAsFactors = FALSE
)

trait_name <- "Yield"

A     <- factor(dataset$line)         # lines
B     <- factor(dataset$tester)       # testers
Block <- factor(dataset$Replication)  # replications

r <- nlevels(Block)   # 4 replications
l <- nlevels(A)       # 5 lines
n_t <- nlevels(B)     # 3 testers

5.3 Worked Example: ANOVA, Combining Ability, Genetic Components

A single call returns the ANOVA tables, the GCA and SCA effects, their standard errors, the proportional contributions and the genetic components.

out <- linetester(replications = Block,
                  lines        = A,
                  testers      = B,
                  y            = dataset[[trait_name]])

parent_result <- as.data.frame(out$ANOVA_with_parents)  # Replications, Treatments, Error
full_result   <- as.data.frame(out$ANOVA_full)          # 9 sources + Total

out$GCA_lines       # GCA effects of the five lines
out$GCA_testers     # GCA effects of the three testers
out$SCA             # SCA effects, 5 x 3 matrix of crosses
out$SE              # gca_line, gca_tester, sca, gi_gj_line, gi_gj_tester, sij_skl
out$Contribution    # lines, testers, lxt (% of the Crosses sum of squares)
out$Genetic         # Cov_HS_line/tester/avg, Cov_FS_avg, VarA_F0/F1, VarD_F0/F1
                    # the module reports the F = 1 values

# Error term driving the mean comparisons and the tests of effects
MSE <- as.numeric(full_result[9, 3])
DFE <- as.numeric(full_result[9, 1])

5.4 Significance of GCA and SCA Effects

The effects and their standard errors come straight from out; only the t statistics, p values and star codes are formed here.

alpha <- 0.05

# --- GCA of lines ---
t_gca_l <- as.numeric(out$GCA_lines) / out$SE$gca_line

# --- GCA of testers ---
t_gca_t <- as.numeric(out$GCA_testers) / out$SE$gca_tester

# --- SCA of crosses ---
t_sca <- as.numeric(out$SCA) / out$SE$sca

5.5 Heterosis, Heterobeltiosis and Standard Heterosis

The cross mean F1 and the two parent means P1 (line) and P2 (tester) are read off the data; the error term is the one from the ANOVA with parents, since the comparison involves parent entries.

datos <- data.frame(Replications = Block,
                    Lines        = A,
                    Testers      = B,
                    Y            = dataset[[trait_name]])

# error mean square and df from the ANOVA WITH PARENTS
Me_par   <- as.numeric(parent_result[3, 3])
df_par   <- as.numeric(parent_result[3, 1])

# cross means, line parent means, tester parent means
cross_mean <- tapply(datos$Y[!is.na(datos$Lines) & !is.na(datos$Testers)],
                     paste(datos$Lines, datos$Testers)[
                       !is.na(datos$Lines) & !is.na(datos$Testers)],
                     mean, na.rm = TRUE)

line_par <- subset(datos, !is.na(Lines) & is.na(Testers))
P1_mean  <- tapply(line_par$Y, droplevels(line_par$Lines), mean, na.rm = TRUE)

test_par <- subset(datos, is.na(Lines) & !is.na(Testers))
P2_mean  <- tapply(test_par$Y, droplevels(test_par$Testers), mean, na.rm = TRUE)

# one row per cross
grid <- expand.grid(line   = levels(A),
                    tester = levels(B),
                    stringsAsFactors = FALSE)
grid$cross <- paste(grid$line, grid$tester)

F1 <- cross_mean[grid$cross]
P1 <- P1_mean[grid$line]
P2 <- P2_mean[grid$tester]

MP <- (P1 + P2) / 2                  # mid-parent
BP <- pmax(P1, P2)                   # better parent

# percentage heterosis
mid_parent   <- ((F1 - MP) / MP) * 100
heterobelt   <- ((F1 - BP) / BP) * 100

# standard errors
SE_H1 <- sqrt((1.5 * Me_par) / r)    # mid-parent
SE_H2 <- sqrt((2   * Me_par) / r)    # better parent
SE_SH <- sqrt((2   * Me_par) / r)    # standard check

# t test, p value and critical differences (shown for heterobeltiosis)
t_val <- (F1 - BP) / SE_H2
p_val <- 2 * pt(-abs(t_val), df = df_par)
CD5   <- SE_H2 * qt(0.975, df = df_par)
CD1   <- SE_H2 * qt(0.995, df = df_par)

5.6 Genetic Parameters

MSt <- as.numeric(full_result[2, 3])   # Treatments mean square
MSe <- as.numeric(full_result[9, 3])   # Error mean square

grand_mean <- mean(dataset[[trait_name]], na.rm = TRUE)

sigma2_g <- max((MSt - MSe) / r, 0)    # negative estimates set to zero
sigma2_e <- MSe
sigma2_p <- sigma2_g + sigma2_e

GCV <- (sqrt(sigma2_g) / grand_mean) * 100
PCV <- (sqrt(sigma2_p) / grand_mean) * 100
ECV <- (sqrt(sigma2_e) / grand_mean) * 100

h2_bs <- ifelse(sigma2_p > 0, (sigma2_g / sigma2_p) * 100, 0)  # broad sense (%)

k   <- 2.06                                # 5% selection intensity
GA  <- k * sqrt(sigma2_p) * (h2_bs / 100)  # genetic advance
GAM <- (GA / grand_mean) * 100             # GA as % of mean

Explore the entire Line × Tester Analysis module in preview mode using our demo datasets. To submit suggestions or report a workflow issue, please use the discussion section below, or visit the official RAISINS website.

6 RAISINS Native Statistical Framework

RAISINS uses R for all its statistical computations. Every package used to generate major results is listed and demonstrated with examples, so results can be reproduced independently. These results are then organized and formatted on the RAISINS website along with visualisation to make them easier to use and interpret. RAISINS also has its own custom-built statistical tools for managing workflows, validating results, and generating reports. Details of these are not fully covered here, they’re shared with outside researchers only on request, and are subject to licensing terms.

7 Package References

R Core Team. (2025). R: A language and environment for statistical computing. R Foundation for Statistical Computing, Vienna, Austria. https://www.R-project.org/

de Mendiburu, F. (2023). agricolae: Statistical Procedures for Agricultural Research (R package version 1.3-7). https://doi.org/10.32614/CRAN.package.agricolae

Kempthorne, O. (1957). An Introduction to Genetic Statistics. John Wiley & Sons, New York.

Singh, R. K., & Chaudhary, B. D. (1985). Biometrical Methods in Quantitative Genetic Analysis. Kalyani Publishers, New Delhi.

Allaire, J. J., Xie, Y., Dervieux, C., McPherson, J., Luraschi, J., Ushey, K., Atkins, A., Wickham, H., Cheng, J., Chang, W., & Iannone, R. (2025). rmarkdown: Dynamic Documents for R (R package version 2.30). https://github.com/rstudio/rmarkdown

Feedback & Discussion