Weight-Length Relationships

DVR with More than Two Groups

Dummy variable regression (DVR) was introduced in Chapter 7 of Introductory Fisheries Analyses with R in the context of determining if the slope and y-intercept parameters of weight-length relationship regressions differed between fish captured in two different years. That analysis may be extended to more than two groups, though more dummy variables are required and special methods are needed to determine which pairs of groups (if any) differ. This supplement demonstrates how to extend the DVR to more than two groups.

Required Packages for this Supplement

Functions used in this supplement require the packages shown below.

> library(FSA)
> library(dplyr)
> library(car)

Data Used in this Supplement

Weights (g) and total lengths (mm) from Ruffe (Gymnocephalus cernuus) captured in the St. Louis River Harbor (Minnesota and Wisconsin) were used in Chapter 7 of IFAR and will also be used in this supplement. These data are from Ogle and Winfield (2009) and are in RuffeSLRH.csv ( download). To eliminate within-season variability, only Ruffe captured in July are used here. Additionally, a factored version of year was created, the common logarithms of weight and length were created, and the fishIDmonth, and day variables were removed to save space in the output.

> ruf <- read.csv("RuffeSLRH.csv") %>%
    filterD(month==7) %>%
    mutate(fYear=factor(year),logW=log10(wt),logL=log10(tl)) %>%
> headtail(ruf)
     year  tl   wt fYear      logW     logL
1    1988  78  6.0  1988 0.7781513 1.892095
2    1988  81  7.0  1988 0.8450980 1.908485
3    1988  82  7.0  1988 0.8450980 1.913814
1936 2004 137 28.0  2004 1.4471580 2.136721
1937 2004 143 31.4  2004 1.4969296 2.155336
1938 2004 174 82.4  2004 1.9159272 2.240549

For the first example below, only fish from 1990, 1995, and 2000 were examined. In the second example below, fish from 1992 to 1995 were examined.

> ruf1 <- filterD(ruf,year %in% c(1990,1995,2000))
> ruf2 <- filterD(ruf,year %in% 1992:1995)


Full Model

The number of dummy variables required to represent kk groups is k1k−1. Thus, in Chapter 7 of IFAR, only one dummy variable was required to represent the two groups. In this example, three groups (the years) are being examined and, thus, two dummy variables are needed. For example, lm() will ultimately treat the “1990” group as the reference group and create two dummy variables as follows


fYear1995={1, if captured in 1995 0, if NOT captured in 1995 fYear1995={1, if captured in 1995 0, if NOT captured in 1995 
fYear2000={1, if captured in 2000 0, if NOT captured in 2000 fYear2000={1, if captured in 2000 0, if NOT captured in 2000 


These dummy variables are each combined with the log10(L)log10(L) covariate to construct the following ultimate full model




Substitution of appropriate values for the dummy variables into Equation 1 shows how Equation 1 simultaneously represents the weight-length relationship regressions for all three years (Table 1). From these submodels, it is apparent that αα is the y-intercept for the reference (e.g., 1990) group, ββ is the slope for the reference group, δiδi is the difference in y-intercepts between the iith and reference groups, and γiγi is the difference in slopes between the iith and reference groups. By extension, the interaction variables measure differences in slopes and the dummy variables measure differences in y-intercepts.


Table 1: The submodels by capture year represented by the full model.

Year fYear1995 fYear2000 Submodel (log10(Wi)=log10(Wi)=)
1990 0 0 log10(α)+βlog10(Li)log10(α)+βlog10(Li)
1995 1 0 (log10(α)+δ1)+(β+γ1)log10(Li)(log10(α)+δ1)+(β+γ1)∗log10(Li)
2000 0 1 (log10(α)+δ2)+(β+γ2)log10(Li)(log10(α)+δ2)+(β+γ2)∗log10(Li)


The model in Equation 1 is fit with lm() using a formula of the form y~x*factor exactly as described in Chapter 7 of IFAR (again noting that lm() will create the appropriate dummy and interaction variables).

> fit1 <- lm(logW~logL*fYear,data=ruf1)

The linearity and homoscedasticity assumptions (Figure 1-Left) and normality assumption (Figure 1-Right) are largely met with this model.

> residPlot(fit1,legend=FALSE)

Figure 1: Modified residual plot (Left) and histogram of residuals (Right) from fitting a dummy variable regression to the log-transformed weights and lengths of Ruffe captured in 1990, 1995, and 2000.

An ANOVA table is constructed (using Anova() from car) and interpreted (sequentially starting at the bottom of the table) as described in Chapter 7 of IFAR.

> Anova(fit1)
Anova Table (Type II tests)

Response: logW
           Sum Sq  Df   F value    Pr(>F)
logL       43.529   1 22278.435 < 2.2e-16
fYear       0.451   2   115.429 < 2.2e-16
logL:fYear  0.052   2    13.395 2.159e-06
Residuals   0.971 497                    

From these results it is apparent that the interaction term is a significant predictor in the model. In relation to Equation 1 this suggests that at least one of γ1γ1 or γ2γ2 is significantly different than zero, which implies that the slope of the relationship for fish captured in 1995, 2000, or both differs significantly from the slope for fish captured in 1990. Additionally, it is possible that the slopes for fish captured in 1995 and 2000 also differ, but this cannot be assessed with this model.

The ANOVA table for the fit of the full model is useful for determining if there is some difference in the regression model parameters among groups, but it cannot specifically identify where those differences occur. Specific differences are identified in the next section.


Pinpointing Specific Differences Among Slopes

No simple method exists (such as Tukey’s Honestly Significant Difference (HSD) method) for comparing slopes among all pairs of groups while controlling for an increase in experimentwise error due to multiple comparisons. One strategy to perform these tests, though, is to fit the DVR as in the previous section and extract all of the p-values for comparisons of slopes to the reference level. Another model is then fit where the reference level has been changed and all p-values for comparisons of slopes to this level are extracted. This process is repeated until p-values for all pairwise comparisons have been extracted. The set of p-values from these comparisons is then submitted to procedures, such as one of the sequential Bonferroni or false discovery rate procedures, that are designed to adjust a set of p-values for multiple comparisons.

Controlling the experimentwise error rate for multiple comparisons is easily accomplished in R with p.adjust(). However, the sequential resetting of the reference level, fitting a new model, and extracting the p-values from all slope comparisons is tedious, especially for many groups. Fortunately, compSlopes() from FSA automates the tedious computation of the set of p-values and use of p.adjust() to control the experimentwise error rate.

The compSlopes() function requires a DVR model fit from lm() as its first argument. The method to control for multiple comparisons is set with method= and may be any of the methods returned by p.adjust.methods() (see ?p.adjust).

> compSlopes(fit1)
Multiple Slope Comparisons (using the 'holm' adjustment)
  comparison     diff  95% LCI  95% UCI p.unadj   p.adj
1  1995-1990 -0.17186 -0.27049 -0.07324 0.00067 0.00134
2  2000-1990 -0.22852 -0.31738 -0.13965 0.00000 0.00000
3  2000-1995 -0.05665 -0.15236  0.03905 0.24538 0.24538

Slope Information (using the 'holm' adjustment)
  level  slopes 95% LCI 95% UCI p.unadj p.adj
3  2000 2.79665 2.73611 2.85719       0     0
2  1995 2.85330 2.77918 2.92743       0     0
1  1990 3.02516 2.96011 3.09022       0     0

Two sets of results are returned by compSlopes(). The top set of results shows the difference in slopes (diff), unadjusted confidence interval for the difference in slopes (95% LCI and 95% UCI), and unadjusted (p.unadj) and adjusted (p.adj) p-values for testing that the difference in slopes is not zero. These results show that the slope for 1990 is significantly greater than the slopes for 1995 and 2000, and that the slopes for 1995 and 2000 do not differ significantly.

The bottom set of results shows the slope (slope), unadjusted confidence interval for the slope (95% LCI and 95% UCI), and unadjusted (p.unadj) and adjusted (p.adj) p-values for testing that the slope is not equal to zero. In this case, this output is primarily for completeness, as these hypothesis are not generally of interest with weight-length relationship regressions.


A Summary Plot

A plot that shows the transformed weight-length data with best-fit lines for each year superimposed (Figure 2) is constructed with the code below. This code follows that found in the IFAR book with the exception that col2rgbt() from FSA is used to add transparency to each color in a vector of colors.

> ## Base plot
> clrs1 <- c("black","red","blue")
> clrs2 <- col2rgbt(clrs1,1/4)
> plot(logW~logL,data=ruf1,pch=19,col=clrs2[fYear],
       xlab="log(Total Length)",ylab="log(Weight)")
> ## Fitted lines
> ( cfs <- coef(fit1) )
   (Intercept)           logL      fYear1995      fYear2000 logL:fYear1995 
    -4.9144676      3.0251636      0.2817809      0.3942964     -0.1718633 
> minx <- min(ruf1$logL)
> maxx <- max(ruf1$logL)
> curve(cfs[1]+cfs[2]*x,from=minx,to=maxx,col=clrs1[1],lwd=2,add=TRUE)
> curve((cfs[1]+cfs[3])+(cfs[2]+cfs[5])*x,from=minx,to=maxx,col=clrs1[2],lwd=2,add=TRUE)
> curve((cfs[1]+cfs[4])+(cfs[2]+cfs[6])*x,from=minx,to=maxx,col=clrs1[3],lwd=2,add=TRUE)
> ## Add legend
> legend("topleft",levels(ruf1$fYear),pch=19,col=clrs1)

Figure 2: Log-transformed weight versus log-transformed length of Ruffe separated by capture year.


Pinpointing Specific Differences Among Intercepts

When a difference in slopes exists, as in the previous example, it generally does not make sense to compare intercepts. However, if the slopes do not differ, then testing for differences in intercepts becomes important because, with parallel lines (i.e., same slopes), a difference in intercepts implies that the mean value of the response variable differs at every value of the explanatory variable.

The example below fits a DVR of the weight-length relationship regressions for the years from 1992 to 1995 (data.frame constructed above). In this example, the interaction term is not a significant predictor which indicates that none of the slopes differ. However, the term related to the factor variable is significant, which implies that at least one of the δiδi is different from zero. Thus, the y-intercept for at least of 1993, 1994, or 1995 differs from the y-intercept for 1992 (the reference level).

> fit2 <- lm(logW~logL*fYear,data=ruf2)
> Anova(fit2)
Anova Table (Type II tests)

Response: logW
           Sum Sq  Df    F value Pr(>F)
logL       75.798   1 45594.0523 <2e-16
fYear       0.326   3    65.4373 <2e-16
logL:fYear  0.008   3     1.6373 0.1793
Residuals   1.235 743                  

The same problem of incomplete comparisons and increasing experimentwise error occurs here with the y-intercepts as occurred with the slopes above. The solution, however, is different here because the y-intercept is a point estimate, whereas the slope estimates a rate of change. In this case, Tukey’s HSD method can be used to determine which pairs of y-intercepts differ. However, the observed values need to be adjusted to a single values of the covariate.

The adjusted value for an individual is computed by adding the individual’s residual from the full model to the predicted value of the response variable (from the full model gieven the individuals group) at a common covariate value. Visually, this adjustment may be thought of as sliding each point along the regression line (for the individual’s group) to a common value (for all groups) along the x-axis.

The compIntercepts() function from FSA performs these adjustments and applies Tukey’s HSD procedure to the adjusted values. The compIntercepts() function requires the fitted DVR as the first argument. By default, the response variables are are adjusted to the mean value of the covariate unless a value is given to common.cov=. For example, the actual y-intercepts will be used if common.cov=0. Also note that a warning is given if the DVR model contains an interaction term, though compIntercepts() will adjust for this by fitting a new model without the nonsignificant interaction term.

> compIntercepts(fit2)
Warning: Removed an interaction term from 'mdl' (i.e., assumed
 parallel lines) to test intercepts.
Tukey HSD on means adjusted assuming parallel lines
  comparison          diff     95% LCI      95% UCI     p.adj
1  1993-1992 -0.0560847608 -0.06723042 -0.044939103 0.0000000
2  1994-1992 -0.0609843518 -0.07392816 -0.048040541 0.0000000
3  1995-1992 -0.0616068456 -0.07461176 -0.048601927 0.0000000
4  1994-1993 -0.0048995910 -0.01528465  0.005485465 0.6176269
5  1995-1993 -0.0055220848 -0.01598321  0.004939036 0.5254957
6  1995-1994 -0.0006224938 -0.01298177  0.011736778 0.9992208

Mean logW when logL=2.012621
    1992     1993     1994     1995 
1.171924 1.115839 1.110939 1.110317 

Two sets of results are returned by compIntercepts(). The top set of results shows the difference in y-intercepts (diff), unadjusted confidence interval for the difference in y-intercepts (95% LCI and 95% UCI), and adjusted (p.adj) p-values for testing that the difference in y-intercepts is not zero. These results show that the y-intercept for 1992 is significantly greater than the y-intercepts for all other years, which did not differ significantly.

The bottom set of results shows the mean value of the response variable at a common value of the covariate for each group. For example, the mean log10(W)log10(W) for when log10(L)log10(L)=2.012621 for fish captured in 1992 is 1.171924.

> ## Base plot
> clrs1 <- c("black","red","blue","green")
> clrs2 <- col2rgbt(clrs1,1/4)
> plot(logW~logL,data=ruf2,pch=19,col=clrs2[fYear],
       xlab="log(Total Length)",ylab="log(Weight)")
> ## Fitted lines
> ( cfs <- coef(fit2) )
   (Intercept)           logL      fYear1993      fYear1994      fYear1995 
  -4.553035394    2.846025564   -0.197272960   -0.189460113   -0.079651305 
logL:fYear1993 logL:fYear1994 logL:fYear1995 
   0.068720265    0.062584797    0.007274738 
> minx <- min(ruf2$logL)
> maxx <- max(ruf2$logL)
> curve(cfs[1]+cfs[2]*x,from=minx,to=maxx,col=clrs1[1],lwd=2,add=TRUE)
> curve((cfs[1]+cfs[3])+(cfs[2]+cfs[6])*x,from=minx,to=maxx,col=clrs1[2],lwd=2,add=TRUE)
> curve((cfs[1]+cfs[4])+(cfs[2]+cfs[7])*x,from=minx,to=maxx,col=clrs1[3],lwd=2,add=TRUE)
> curve((cfs[1]+cfs[5])+(cfs[2]+cfs[8])*x,from=minx,to=maxx,col=clrs1[4],lwd=2,add=TRUE)
> ## Add legend
> legend("topleft",levels(ruf2$fYear),pch=19,col=clrs1)

Figure 3: Log-transformed weight versus log-transformed length of Ruffe separated by capture year.


Reproducibility Information

  • Compiled Date: Thu Nov 05 2015
  • Compiled Time: 8:54:13 PM
  • R Version: R version 3.2.2 (2015-08-14)
  • System: Windows, i386-w64-mingw32/i386 (32-bit)
  • Base Packages: base, datasets, graphics, grDevices, methods, stats, utils
  • Required Packages: FSA, dplyr, car, captioner, knitr and their dependencies (assertthat, DBI, digest, evaluate, formatR, gdata, gplots, graphics, grDevices, highr, Hmisc, lazyeval, magrittr, markdown, MASS, methods, mgcv, nnet, pbkrtest, plotrix, plyr, quantreg, R6, Rcpp, sciplot, stats, stringr, tools, utils, yaml)
  • Other Packages: captioner_2.2.3, car_2.1-0, dplyr_0.4.3, extrafont_0.17, FSA_0.8.4, knitr_1.11, rmarkdown_0.8.1
  • Loaded-Only Packages: assertthat_0.1, DBI_0.3.1, digest_0.6.8, evaluate_0.8, extrafontdb_1.0, formatR_1.2.1, gdata_2.17.0, grid_3.2.2, gtools_3.5.0, htmltools_0.2.6, lattice_0.20-33, lazyeval_0.1.10, lme4_1.1-10, magrittr_1.5, MASS_7.3-44, Matrix_1.2-2, MatrixModels_0.4-1, mgcv_1.8-8, minqa_1.2.4, nlme_3.1-122, nloptr_1.0.4, nnet_7.3-11, parallel_3.2.2, pbkrtest_0.4-2, plyr_1.8.3, quantreg_5.19, R6_2.1.1, Rcpp_0.12.1, Rttf2pt1_1.3.3, SparseM_1.7, splines_3.2.2, stringi_1.0-1, stringr_1.0.0, tools_3.2.2, yaml_2.1.13
  • Links: Script / RMarkdown



Ogle, D. H., and I. J. Winfield. 2009. Ruffe length-weight relationships with a proposed standard weight equation. North American Journal of Fisheries Management 29:850–858.