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

# Calculating Feed Conversion and Nutrient Use Efficiency

#### 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 feed conversion metrics and nutrient use efficiency metrics.

    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 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) in form of a typical Excel workbook and can be found together with additional information in the dedicated publication. The respective 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

The original dataset provides some information on the feed they used, such as the proximate composition and essential amino acid profile.

    feedcomp
    #> # A tibble: 1 × 25
    #>   diet       dry_matter crude_protein crude_lipids   ash gross_energy phosphorus
    #>   <chr>           <dbl>         <dbl>        <dbl> <dbl>        <dbl>      <dbl>
    #> 1 Protec, S…       0.93         0.474         0.22  0.08         22.1      0.012
    #> # ℹ 18 more variables: arginine <dbl>, histidine <dbl>, isoleucine <dbl>,
    #> #   leucine <dbl>, lysine <dbl>, threonine <dbl>, tryptophan <dbl>,
    #> #   valine <dbl>, methionine <dbl>, cysteine <dbl>, phenylalanine <dbl>,
    #> #   tyrosine <dbl>, aspartic_acid <dbl>, glutamic_acid <dbl>, alanine <dbl>,
    #> #   glycine <dbl>, proline <dbl>, serine <dbl>

We can notice that the dry matter content has not been scaled to 1 (=100%). The other data is thus provided on wet weight basis, which will be important later.

After removing the non-numeric diet column, we will convert the data into long format for improved readability.

    feedcomp %>%
      select(-diet) %>%
      pivot_longer(
        everything(),
        names_to = "parameter",
        values_to = "value"
      )
    #> # A tibble: 24 × 2
    #>    parameter       value
    #>    <chr>           <dbl>
    #>  1 dry_matter     0.93
    #>  2 crude_protein  0.474
    #>  3 crude_lipids   0.22
    #>  4 ash            0.08
    #>  5 gross_energy  22.1
    #>  6 phosphorus     0.012
    #>  7 arginine       0.033
    #>  8 histidine      0.0099
    #>  9 isoleucine     0.0174
    #> 10 leucine        0.032
    #> # ℹ 14 more rows

The unit of the data is gram per gram. It is highly recommended to use a package such as `units` to ensure that there are no mistakes occurring with unit conversion.

To calculate the feed conversion and nutrient use efficiency, we need the biomass of the fish stock at the beginning and the end of the growth phase. This data can be found in the samplings dataset. As the data is not in the right shape, we need to make some adjustments. First of all, we will add an additional column (`timepoint`) as identifier for the fish bodyweight at the beginning and the end. In the next step, we will calculate the arithmetic mean of the weights for each or our timepoints of interest because at the beginning of the growth period the weight was determined in bulk, while fish were weighed individually at the end of the growth period. Lastly, we will convert the data into wide format.

    df <- samplings %>%
      mutate(
        timepoint = case_when(
          date == ymd("2023-03-16") ~ "bw_beginning",
          date == ymd("2023-04-14") ~ "bw_end",
          .default = NA
      )) %>%
      group_by(tank, timepoint, sample_type) %>%
      summarise(mean_weight = mean(fish_weight)) %>%
      select(-sample_type, -starts_with("sd")) %>%
      pivot_wider(
        names_from = timepoint,
        values_from = mean_weight
      ) %>%
      print()
    #> `summarise()` has grouped output by 'tank', 'timepoint'. You can override using
    #> the `.groups` argument.
    #> # A tibble: 9 × 3
    #> # Groups:   tank [9]
    #>   tank  bw_beginning bw_end
    #>   <fct>        <dbl>  <dbl>
    #> 1 T1             295   385.
    #> 2 T10            313   385.
    #> 3 T11            305   369.
    #> 4 T2             315   394.
    #> 5 T3             328   370.
    #> 6 T6             332   398.
    #> 7 T7             310   402.
    #> 8 T8             309   417.
    #> 9 T9             305   378.

Additional data we require is that related to the feed intake throughout the growth phase. This can be found in the feed_intake dataset.

    head(feed_intake)
    #> # A tibble: 6 × 4
    #>   date       tank  daily_feed_intake cumulative_feed_intake
    #>   <date>     <fct>             <dbl>                  <dbl>
    #> 1 2023-03-16 T1                 2.80                   2.80
    #> 2 2023-03-16 T2                 2.51                   2.51
    #> 3 2023-03-16 T3                 2.90                   2.90
    #> 4 2023-03-16 T6                 2.57                   2.57
    #> 5 2023-03-16 T7                 2.80                   2.80
    #> 6 2023-03-16 T8                 2.81                   2.81

To calculate nutrient use efficiency, we also need some data on the bodycomposition at the beginning and the end of the growth period, which can be found in the bodycomp dataset.

    head(bodycomp)
    #> # A tibble: 6 × 14
    #>   date       treatment tank     dm water    ash energy   fat protein    ca     k
    #>   <date>     <fct>     <fct> <dbl> <dbl>  <dbl>  <dbl> <dbl>   <dbl> <dbl> <dbl>
    #> 1 2023-03-16 Initial   init… 0.343 0.657 0.0188 0.0946 0.148    0.19  4200  3700
    #> 2 2023-03-16 Initial   init… 0.339 0.661 0.0183 0.0928 0.136    0.18  3500  3600
    #> 3 2023-03-16 Initial   init… 0.334 0.666 0.016  0.0916 0.134    0.18  4900  3700
    #> 4 2023-04-14 DO50      T1    0.325 0.675 0.0179 0.0873 0.12     0.18  4500  3700
    #> 5 2023-04-14 DO50      T2    0.332 0.668 0.0158 0.0877 0.123    0.19  4600  3700
    #> 6 2023-04-14 DO50      T3    0.338 0.662 0.0188 0.0923 0.133    0.19  4000  3900
    #> # ℹ 3 more variables: mg <dbl>, na <dbl>, phosphorus <dbl>

# Feed Conversion Ratio and Efficiency

To assess feed conversion, we need data on the feed intake throughout the experiment. The feed_intake dataset already comes with the cumulative feed intake pre-calculated, which means we only need to retain the ultimate value. We then join the bodyweight at the beginning and the end of the growth period with the feed intake dataset.

    df <- feed_intake %>%
      filter(date == max(date)) %>%
      select(tank, cumulative_feed_intake) %>%
      right_join(df, join_by(tank)) %>%
      print()
    #> # A tibble: 9 × 4
    #>   tank  cumulative_feed_intake bw_beginning bw_end
    #>   <fct>                  <dbl>        <dbl>  <dbl>
    #> 1 T1                      64.9          295   385.
    #> 2 T2                      67.3          315   394.
    #> 3 T3                      69.2          328   370.
    #> 4 T6                      74.2          332   398.
    #> 5 T7                      69.4          310   402.
    #> 6 T8                      73.8          309   417.
    #> 7 T9                      76.5          305   378.
    #> 8 T10                     78.6          313   385.
    #> 9 T11                     67.9          305   369.

It is eventually possible to calculate the Feed Conversion Ratio (FCR) with the `fcr()` function and the Feed Conversion Efficiency (FCE) with the `fce()` function. Note that we account for the dry matter content of the feed, which is not 100%.

    feed_conversion <- df %>%
      group_by(tank) %>%
      summarise(
        feed_conversion_ratio = fcr(
          ibw = bw_beginning,
          fbw = bw_end,
          feed = cumulative_feed_intake,
          dm = 0.93
        ),
        feed_conversion_efficiency = fce(
          ibw = bw_beginning,
          fbw = bw_end,
          feed = cumulative_feed_intake,
          dm = 0.93
        )
      ) %>%
      print()
    #> # A tibble: 9 × 3
    #>   tank  feed_conversion_ratio feed_conversion_efficiency
    #>   <fct>                 <dbl>                      <dbl>
    #> 1 T1                    0.667                      1.50
    #> 2 T10                   1.02                       0.979
    #> 3 T11                   0.989                      1.01
    #> 4 T2                    0.793                      1.26
    #> 5 T3                    1.55                       0.646
    #> 6 T6                    1.05                       0.950
    #> 7 T7                    0.701                      1.43
    #> 8 T8                    0.633                      1.58
    #> 9 T9                    0.974                      1.03

The FCR indicates the quantity of feed required to gain a unit of bodyweight. The FCE, meanwhile, is the inverse of the FCR and thus describes the bodyweight gain per mass unit of feed. Please note that the specific FCR or FCE (economic, biological, etc.) depend on whether the feed quantity is corrected for feed refusal or other kinds of feed losses and whether both the feed quantity and biomass are corrected for their respective dry matter contents.

# Nutrient Use Efficiency

## Nutrient Efficiency Ratio

To look deeper into the Nutrient Use Efficiency, we are interested in calculating metrics such as the Nutrient Efficiency Ratio (NER). This can be achieved with the `ner()` function. For this purpose, we need to add the feed composition to the dataset as well. As an example, we will focus on the crude protein and calculate the Protein Efficiency Ratio. Note that the `ner()` function requires the feed composition data to be provided in gram per gram, which corresponds to possible values within the interval [0,1].

    df %>%
      bind_cols(feedcomp %>%
                  select(dry_matter, crude_protein)) %>%
      mutate(
        protein_efficiency_ratio = ner(ibw = bw_beginning,
                                       fbw = bw_end,
                                       fi = cumulative_feed_intake,
                                       nut_f = crude_protein,
                                       dm = dry_matter)
      ) %>%
      relocate(tank, protein_efficiency_ratio) # change column order
    #> # A tibble: 9 × 7
    #>   tank  protein_efficiency_ratio cumulative_feed_intake bw_beginning bw_end
    #>   <fct>                    <dbl>                  <dbl>        <dbl>  <dbl>
    #> 1 T1                        3.16                   64.9          295   385.
    #> 2 T2                        2.66                   67.3          315   394.
    #> 3 T3                        1.36                   69.2          328   370.
    #> 4 T6                        2.00                   74.2          332   398.
    #> 5 T7                        3.01                   69.4          310   402.
    #> 6 T8                        3.33                   73.8          309   417.
    #> 7 T9                        2.17                   76.5          305   378.
    #> 8 T10                       2.06                   78.6          313   385.
    #> 9 T11                       2.13                   67.9          305   369.
    #> # ℹ 2 more variables: dry_matter <dbl>, crude_protein <dbl>

## Nutrient Retention

An additional metric we might be interested in is the actual Nutrient Retention (NR), which can be calculated with the `nr()` function. The Nutrient Retention indicates how much of a specific nutrient provided has actually been retained in the tissue of the fed organism. For this purpose, we require data on the tissue composition.

    summary(bodycomp)
    #>       date              treatment             tank         dm
    #>  Min.   :2023-03-16   DO50   :3   initial sample:3   Min.   :0.302
    #>  1st Qu.:2023-04-06   DO60   :3   T1            :1   1st Qu.:0.325
    #>  Median :2023-04-14   DO95   :3   T10           :1   Median :0.331
    #>  Mean   :2023-04-06   Initial:3   T11           :1   Mean   :0.328
    #>  3rd Qu.:2023-04-14               T2            :1   3rd Qu.:0.335
    #>  Max.   :2023-04-14               T3            :1   Max.   :0.343
    #>                                   (Other)       :4
    #>      water            ash             energy            fat
    #>  Min.   :0.657   Min.   :0.0158   Min.   :0.0793   Min.   :0.099
    #>  1st Qu.:0.665   1st Qu.:0.0167   1st Qu.:0.0868   1st Qu.:0.120
    #>  Median :0.669   Median :0.0185   Median :0.0877   Median :0.124
    #>  Mean   :0.672   Mean   :0.0180   Mean   :0.0880   Mean   :0.125
    #>  3rd Qu.:0.675   3rd Qu.:0.0190   3rd Qu.:0.0918   3rd Qu.:0.133
    #>  Max.   :0.698   Max.   :0.0202   Max.   :0.0946   Max.   :0.148
    #>
    #>     protein            ca             k              mg            na
    #>  Min.   :0.180   Min.   :3500   Min.   :3600   Min.   :330   Min.   :760
    #>  1st Qu.:0.180   1st Qu.:3950   1st Qu.:3700   1st Qu.:340   1st Qu.:778
    #>  Median :0.185   Median :4250   Median :3750   Median :350   Median :805
    #>  Mean   :0.185   Mean   :4525   Mean   :3767   Mean   :351   Mean   :803
    #>  3rd Qu.:0.190   3rd Qu.:4675   3rd Qu.:3825   3rd Qu.:360   3rd Qu.:822
    #>  Max.   :0.190   Max.   :6800   Max.   :3900   Max.   :390   Max.   :860
    #>
    #>    phosphorus
    #>  Min.   :3700
    #>  1st Qu.:4175
    #>  Median :4200
    #>  Mean   :4358
    #>  3rd Qu.:4400
    #>  Max.   :5500
    #>

In an initial step, we specify the start and end date again.

    df2 <- bodycomp %>%
        mutate(
          timepoint = case_when(
            date == ymd("2023-03-16") ~ "beginning",
            date == ymd("2023-04-14") ~ "end",
            .default = NA
            )
          )

Now we calculate the initial tissue protein content based on the three samples that were taken to determine the body composition and save the result in a new R object.

    initial <- df2 %>%
      group_by(timepoint) %>%
      summarise(tissue_protein_start = mean(protein)) %>%
      ungroup() %>%
      filter(timepoint == "beginning") %>%
      select(tissue_protein_start) %>% print()
    #> # A tibble: 1 × 1
    #>   tissue_protein_start
    #>                  <dbl>
    #> 1                0.183

Eventually, we can remove the initial timepoint and add the data together with the feed intake, weight gain, and feed composition data.

    nutrient_retention <- df2 %>%
      filter(timepoint != "beginning") %>%
      rename(tissue_protein = "protein") %>%
      select(-timepoint) %>%
      bind_cols(initial) %>%
      right_join(df, join_by(tank)) %>%
      bind_cols(feedcomp %>% select(dry_matter, crude_protein))

After joining all necessary data, we can calculate the NR.

    nutrient_retention %>%
      mutate(
        protein_retention = nr(ibw = bw_beginning,
                               fbw = bw_end,
                               ibn = tissue_protein_start,
                               fbn = tissue_protein,
                               fi = cumulative_feed_intake,
                               nut_f = crude_protein
                               )
        ) %>%
      select(treatment, protein_retention)
    #> Inputs differ in length.
    #> # A tibble: 9 × 2
    #>   treatment protein_retention
    #>   <fct>                 <dbl>
    #> 1 DO50                  0.497
    #> 2 DO50                  0.536
    #> 3 DO50                  0.307
    #> 4 DO60                  0.304
    #> 5 DO60                  0.594
    #> 6 DO60                  0.647
    #> 7 DO95                  0.335
    #> 8 DO95                  0.421
    #> 9 DO95                  0.326

# 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