Author: Written by: Anıl Axel Tellbüscher

# Calculating Growth

#### Written by: Anıl Axel Tellbüscher

#### Last updated: 2025-12-31 (rebuilt 2026-01-21)

This short vignette provides coding examples on how to use the functions provided by the `aquacultuR` package to calculate growth metrics commonly used in the field of aquaculture.

    library(aquacultuR)
    library(magrittr)
    library(dplyr)
    library(tidyr)
    library(lubridate)

    oldopts <- options()
    options(digits = 3)

# Data

## Source and Adjustments

The data we will assess in order to demonstrate the calculation of growth and feed conversion metrics originates from one out of two experiments during with Atlantic salmon (Salmo salar) has been exposed to different levels of dissolved oxygen saturation. The original data was published by Liland et al. (2024). The original Excel workbook and can be found together with additional information in the dedicated publication. The data has been tidied by converting the double-row into single-row column names. More information on tidy data can be found for instance in Wickham (2014). In addition, proximate and amino acid compositions have been converted from percentages to mass fractions in gram per gram.

## Overview

To calculate growth and the zootechnical metrics related to it, we generally need data related to the

1. weight (or length) over time and
2. water temperature over time.

The weight data is recorded in the `samplings` dataset.

    head(samplings)
    #> # A tibble: 6 × 14
    #>   date       tank  replicate sample_type fish_weight fork_length liver_weight
    #>   <date>     <fct> <fct>     <fct>             <dbl>       <dbl>        <dbl>
    #> 1 2023-03-16 T1    1         bulk                295          NA           NA
    #> 2 2023-03-16 T2    1         bulk                315          NA           NA
    #> 3 2023-03-16 T3    1         bulk                328          NA           NA
    #> 4 2023-03-16 T6    1         bulk                332          NA           NA
    #> 5 2023-03-16 T7    1         bulk                310          NA           NA
    #> 6 2023-03-16 T8    1         bulk                309          NA           NA
    #> # ℹ 7 more variables: hsi <dbl>, heart_weight <dbl>, csi <dbl>,
    #> #   gonad_weight <dbl>, gsi <dbl>, viscera_weight <dbl>, vsi <dbl>

Water parameters, meanwhile, are recorded in the `water_params` dataset.

    head(water_params)
    #> # A tibble: 6 × 6
    #>   date       tank   temp salinity do_perc do_conc
    #>   <date>     <chr> <dbl>    <dbl>   <dbl>   <dbl>
    #> 1 2023-03-16 T1     12.1     22.6    60.4    5.62
    #> 2 2023-03-16 T2     12.3     22.6    60.2    5.58
    #> 3 2023-03-16 T3     12.2     22.6    56.3    5.23
    #> 4 2023-03-16 T6     12.1     22      63.1    5.88
    #> 5 2023-03-16 T7     12.2     22      63.5    5.91
    #> 6 2023-03-16 T8     12.1     22      65.4    6.1

Both datasets share the `date` and `tank` column and can be merged by them.

## Preprocessing

To calculate some metrics related to fish growth, we are interested in the fish weight at the beginning and the end of the experiment for each tank, which is exactly the data that the dataset provides. Therefore, we do not need to filter the dataset. However, we will add an additional column that specifies the timepoint in relation to the date and will make our life easier later on and calculate the mean and standard deviation per tank.

    df <- samplings %>%
      mutate(timepoint = case_when(
        date == ymd("2023-03-16") ~ "beginning",
        date == ymd("2023-04-14") ~ "end",
        .default = NA
      )) %>%
      group_by(tank, timepoint, sample_type) %>%
      summarise(mean_weight = mean(fish_weight),
                sd_weight = sd(fish_weight)) %>%
      print()
    #> `summarise()` has grouped output by 'tank', 'timepoint'. You can override using
    #> the `.groups` argument.
    #> # A tibble: 18 × 5
    #> # Groups:   tank, timepoint [18]
    #>    tank  timepoint sample_type mean_weight sd_weight
    #>    <fct> <chr>     <fct>             <dbl>     <dbl>
    #>  1 T1    beginning bulk               295       NA
    #>  2 T1    end       individual         385.     127.
    #>  3 T10   beginning bulk               313       NA
    #>  4 T10   end       individual         385.     133.
    #>  5 T11   beginning bulk               305       NA
    #>  6 T11   end       individual         369.     122.
    #>  7 T2    beginning bulk               315       NA
    #>  8 T2    end       individual         394.     113.
    #>  9 T3    beginning bulk               328       NA
    #> 10 T3    end       individual         370.      95.5
    #> 11 T6    beginning bulk               332       NA
    #> 12 T6    end       individual         398.     133.
    #> 13 T7    beginning bulk               310       NA
    #> 14 T7    end       individual         402.     121.
    #> 15 T8    beginning bulk               309       NA
    #> 16 T8    end       individual         417.     132.
    #> 17 T9    beginning bulk               305       NA
    #> 18 T9    end       individual         378.     131.

The initial weighing was done in bulk per tank, which is why there was no standard deviation calculated for the respective time points. Now we convert the data into wide format and calculate the duration of the experiment.

    df <- df %>%
      select(-sample_type, -starts_with("sd")) %>%
      pivot_wider(
        names_from = timepoint,
        values_from = c(mean_weight),
        names_vary = "slowest"
      ) %>%
      mutate(duration = as.numeric(max(samplings$date) - min(samplings$date))) %>%
      print()
    #> # A tibble: 9 × 4
    #> # Groups:   tank [9]
    #>   tank  beginning   end duration
    #>   <fct>     <dbl> <dbl>    <dbl>
    #> 1 T1          295  385.       29
    #> 2 T10         313  385.       29
    #> 3 T11         305  369.       29
    #> 4 T2          315  394.       29
    #> 5 T3          328  370.       29
    #> 6 T6          332  398.       29
    #> 7 T7          310  402.       29
    #> 8 T8          309  417.       29
    #> 9 T9          305  378.       29

# Absolute Growth, Absolute and Specific Growth Rate

After data preparation, we can now calculate some growth-related metrics, including the Absolute Growth (AG) with the `ag()` function, the Relative Growth (RG) with the `rg()` function, and the time-related Absolute Growth Rate (AGR) using the `agr()` function. A special case is the Relative Growth Rate (RGR) that is calculated with the `rgr()` function. Note that the denominator of the Relative Growth Rate is calculated by default by applying the geometric mean, but the arithmetic mean can also be used. Lastly, we also calculate the Specific Growth Rate (SGR) with the `sgr()` function.

    df %>%
      group_by(tank) %>%
      mutate(
        absolute_growth = ag(ibw = beginning, fbw = end),
        relative_growth = rg(ibw = beginning, fbw = end),
        absolute_growth_rate = agr(ibw = beginning, fbw = end, duration),
        specific_growth_rate = sgr(ibw = beginning, fbw = end, duration)
      )
    #> # A tibble: 9 × 8
    #> # Groups:   tank [9]
    #>   tank  beginning   end duration absolute_growth relative_growth
    #>   <fct>     <dbl> <dbl>    <dbl>           <dbl>           <dbl>
    #> 1 T1          295  385.       29            90.4           0.307
    #> 2 T10         313  385.       29            71.5           0.229
    #> 3 T11         305  369.       29            63.8           0.209
    #> 4 T2          315  394.       29            78.9           0.251
    #> 5 T3          328  370.       29            41.6           0.127
    #> 6 T6          332  398.       29            65.6           0.198
    #> 7 T7          310  402.       29            92.1           0.297
    #> 8 T8          309  417.       29           108.            0.351
    #> 9 T9          305  378.       29            73.1           0.240
    #> # ℹ 2 more variables: absolute_growth_rate <dbl>, specific_growth_rate <dbl>

    df %>%
      group_by(tank) %>%
      mutate(
        geometric_bodyweight = gbw(ibw = beginning, fbw = end),
        relative_growth_rate_geom = rgr(
          ibw = beginning,
          fbw = end,
          duration,
          mean_fun = "geometric"
        ),
        relative_growth_rate_arith = rgr(
          ibw = beginning,
          fbw = end,
          duration,
          mean_fun = "arithmetic"
        )
      )
    #> # A tibble: 9 × 7
    #> # Groups:   tank [9]
    #>   tank  beginning   end duration geometric_bodyweight relative_growth_rate_geom
    #>   <fct>     <dbl> <dbl>    <dbl>                <dbl>                     <dbl>
    #> 1 T1          295  385.       29                 337.                   0.00925
    #> 2 T10         313  385.       29                 347.                   0.00711
    #> 3 T11         305  369.       29                 335.                   0.00656
    #> 4 T2          315  394.       29                 352.                   0.00773
    #> 5 T3          328  370.       29                 348.                   0.00412
    #> 6 T6          332  398.       29                 363.                   0.00622
    #> 7 T7          310  402.       29                 353.                   0.00900
    #> 8 T8          309  417.       29                 359.                   0.0104
    #> 9 T9          305  378.       29                 340.                   0.00742
    #> # ℹ 1 more variable: relative_growth_rate_arith <dbl>

# Thermal Growth Coefficient

To calculate the Thermal Growth Coefficient (TGC) with the `tgc()` function, we need to add temperature data, which is supplied in the water_params dataset. We will thus join this dataset into our growth dataset after calculating the average temperature throughout the experiment.

    # 1. calculate mean temperature
    # 2. join into growth data
    # 3. calculate TGC
    water_params %>%
      group_by(tank) %>%
      summarise(temp = mean(temp)) %>%
      right_join(df, join_by(tank)) %>%
      mutate(
        thermal_growth_coefficient = tgc(
        ibw = beginning,
        fbw = end,
        duration = duration,
        temp = temp
      ))
    #> # A tibble: 9 × 6
    #>   tank   temp beginning   end duration thermal_growth_coefficient
    #>   <chr> <dbl>     <dbl> <dbl>    <dbl>                      <dbl>
    #> 1 T1     12.1       295  385.       29                      1.77
    #> 2 T10    11.7       313  385.       29                      1.42
    #> 3 T11    11.8       305  369.       29                      1.29
    #> 4 T2     12.3       315  394.       29                      1.48
    #> 5 T3     12.2       328  370.       29                      0.790
    #> 6 T6     12.1       332  398.       29                      1.22
    #> 7 T7     12.2       310  402.       29                      1.73
    #> 8 T8     12.0       309  417.       29                      2.04
    #> 9 T9     11.6       305  378.       29                      1.48

# Session Info

    sessionInfo()
    #> R version 4.5.2 (2025-10-31)
    #> Platform: x86_64-apple-darwin20
    #> Running under: macOS Sequoia 15.7.3
    #>
    #> Matrix products: default
    #> BLAS:   /Library/Frameworks/R.framework/Versions/4.5-x86_64/Resources/lib/libRblas.0.dylib
    #> LAPACK: /Library/Frameworks/R.framework/Versions/4.5-x86_64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1
    #>
    #> locale:
    #> [1] C/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
    #>
    #> time zone: Europe/Prague
    #> tzcode source: internal
    #>
    #> attached base packages:
    #> [1] stats     graphics  grDevices utils     datasets  methods   base
    #>
    #> other attached packages:
    #> [1] lubridate_1.9.4  tidyr_1.3.2      dplyr_1.1.4      magrittr_2.0.4
    #> [5] aquacultuR_1.1.1
    #>
    #> loaded via a namespace (and not attached):
    #>  [1] vctrs_0.7.0       cli_3.6.5         knitr_1.51        rlang_1.1.7
    #>  [5] xfun_0.56         otel_0.2.0        purrr_1.2.1       generics_0.1.4
    #>  [9] jsonlite_2.0.0    glue_1.8.0        htmltools_0.5.9   sass_0.4.10
    #> [13] rmarkdown_2.30    evaluate_1.0.5    jquerylib_0.1.4   tibble_3.3.1
    #> [17] fastmap_1.2.0     yaml_2.3.12       lifecycle_1.0.5   compiler_4.5.2
    #> [21] timechange_0.3.0  pkgconfig_2.0.3   rstudioapi_0.18.0 digest_0.6.39
    #> [25] R6_2.6.1          utf8_1.2.6        tidyselect_1.2.1  pillar_1.11.1
    #> [29] bslib_0.9.0       withr_3.0.2       tools_4.5.2       cachem_1.1.0

    options(oldopts)

# References

Liland N, Rønnestad I, Azevedo M, et al (2024) Dataset on the performance of atlantic salmon (salmo salar) reared at different dissolved oxygen levels under experimental conditions. Data in Brief 57:110983. https://doi.org/10.1016/j.dib.2024.110983
Wickham H (2014) Tidy data. Journal of Statistical Software 59: https://doi.org/10.18637/jss.v059.i10