fb4package: Fish Bioenergetics 4.0 in R

A step-by-step tutorial on using fb4package — an R implementation of Fish Bioenergetics 4.0 — to model fish consumption, growth, and energy budgets.
R
bioenergetics
fisheries
package
Author

Hans Ttito

Published

April 1, 2026

What is fb4package?

fb4package is an R implementation of Fish Bioenergetics 4.0 (Deslauriers et al. 2017). Bioenergetics models answer a deceptively simple question: how much food does a fish need to grow from weight A to weight B under these environmental conditions?

The model partitions consumed energy among metabolism, waste, and growth to produce daily estimates of fish consumption and weight gain. It includes a built-in species database with 105 parameterizations for 73 fish species, so you can get started without hunting down literature values.

You can explore the full documentation at hansttito.github.io/fb4package.

Installation

Install the development version from GitHub:

# install.packages("devtools")
devtools::install_github("HansTtito/fb4package")
library(fb4package)

Core workflow

The package is built around two functions:

  • Bioenergetic() — builds the model object (species, temperature, diet, initial conditions)
  • run_fb4() — runs the simulation or fits parameters to observed data

Every analysis follows the same three steps:

  1. Gather species parameters, temperature data, and diet composition
  2. Build a Bioenergetic object
  3. Run run_fb4() with the strategy of your choice

Step 1: Species parameters

The package ships with a built-in database you can explore immediately:

data(fish4_parameters)

# List all available species
names(fish4_parameters)

# Load Chinook salmon
chinook_db <- fish4_parameters[["Oncorhynchus tshawytscha"]]

# Pick a life stage
stage <- "juvenile"
sp_params <- chinook_db$life_stages[[stage]]
sp_info   <- chinook_db$species_info
sp_info$life_stage <- stage

cat("Consumption equation (CEQ):", sp_params$consumption$CEQ, "\n")
cat("Respiration equation (REQ):", sp_params$respiration$REQ, "\n")

Step 2: Environmental data (temperature)

Temperature drives virtually every rate in the bioenergetics model, so you need a daily time series:

set.seed(42)
days     <- 1:180
base_temp <- 9 + 7 * sin(pi * (days - 20) / 180)   # sinusoidal season
temp_vec  <- pmax(2, base_temp + rnorm(180, 0, 0.4)) # small daily noise

temp_data <- data.frame(
  Day         = days,
  Temperature = round(temp_vec, 2)
)

cat(sprintf("Temperature range: %.1f – %.1f °C\n",
  min(temp_data$Temperature), max(temp_data$Temperature)))

This mimics a Pacific Northwest lake warming from ~2°C in early spring to a peak of ~14°C in midsummer, then cooling again — a 180-day growing season.

Step 3: Diet composition

The model needs daily prey proportions and energy densities (J/g wet weight):

alewife <- pmax(0, 0.55 + 0.25 * sin(pi * (days - 30) / 180))
shrimp  <- pmax(0, 0.28 - 0.10 * sin(pi * (days - 30) / 180))
inverts <- pmax(0, 1 - alewife - shrimp)
total   <- alewife + shrimp + inverts

diet_props <- data.frame(
  Day     = days,
  Alewife = round(alewife / total, 4),
  Shrimp  = round(shrimp  / total, 4),
  Inverts = round(inverts / total, 4)
)

prey_energy <- data.frame(
  Day     = days,
  Alewife = 4900,   # J/g
  Shrimp  = 3200,
  Inverts = 2600
)

Diet shifts seasonally: alewife dominates in summer while shrimp and invertebrates fill in spring and autumn.

Step 4: Building the Bioenergetic object

Now assemble everything into a single model object:

bio <- Bioenergetic(
  species_params    = sp_params,
  species_info      = sp_info,
  environmental_data = list(temperature = temp_data),
  diet_data = list(
    proportions = diet_props,
    prey_names  = c("Alewife", "Shrimp", "Inverts"),
    energies    = prey_energy
  ),
  simulation_settings = list(
    initial_weight = 5,     # grams (post-emergence juvenile)
    duration       = 180
  )
)

# Set predator energy density (increases as fish accumulates lipids)
bio$species_params$predator$ED_ini <- 4200  # J/g at start
bio$species_params$predator$ED_end <- 5000  # J/g at end

print(bio)

You can visualize the inputs before running any simulation:

plot(bio, type = "dashboard")
plot(bio, type = "temperature")
plot(bio, type = "diet")

Estimation strategies

run_fb4() supports four strategies. The right choice depends on what data you have.

Binary search — “what p produces my target weight?”

This is the most common starting point. You have a target final weight and want to find the feeding level p (proportion of maximum ration) that achieves it:

res_bs <- run_fb4(
  x         = bio,
  fit_to    = "Weight",
  fit_value = 40,             # target: 40 g after 180 days
  strategy  = "binary_search",
  verbose   = FALSE
)

cat(sprintf("Estimated p : %.4f\n",    res_bs$summary$p_value))
cat(sprintf("Final weight: %.1f g\n",  res_bs$summary$final_weight))
cat(sprintf("Converged   : %s\n",      res_bs$summary$converged))

A p ≈ 0.62 means the fish consumed roughly 62% of their bioenergetically predicted maximum ration to reach 40 g in 180 days.

plot(res_bs, type = "growth")
plot(res_bs, type = "energy")
plot(res_bs, type = "dashboard")

Direct simulation — fixed p, observe growth

If you already know (or want to explore) a specific feeding level:

res_direct <- run_fb4(
  x         = bio,
  fit_to    = "p_value",
  fit_value = 0.75,
  strategy  = "direct_p_value",
  verbose   = FALSE
)

cat(sprintf("Final weight at p = 0.75: %.1f g\n", res_direct$summary$final_weight))

Bootstrap — uncertainty from observed weights

When you have a sample of observed final weights and want to propagate measurement uncertainty into your p estimate:

set.seed(123)
obs_weights <- rnorm(25, mean = 40, sd = 40 * 0.08)  # 25 fish, CV = 8%

res_boot <- run_fb4(
  x                = bio,
  fit_to           = "Weight",
  observed_weights = obs_weights,
  strategy         = "bootstrap",
  n_bootstrap      = 100,
  confidence_level = 0.95,
  parallel         = FALSE,
  verbose          = FALSE
)

cat(sprintf("p mean      : %.4f\n", res_boot$summary$p_mean))
cat(sprintf("p SD        : %.4f\n", res_boot$summary$p_sd))
cat(sprintf("95%% CI     : [%.4f, %.4f]\n",
  res_boot$method_data$confidence_intervals$p_ci_lower,
  res_boot$method_data$confidence_intervals$p_ci_upper))

plot(res_boot, type = "uncertainty")

MLE — likelihood-based estimation

Maximizes the likelihood of your observed weights under a log-normal model, returning a standard error and confidence interval via profile likelihood:

res_mle <- run_fb4(
  x                = bio,
  strategy         = "mle",
  fit_to           = "Weight",
  observed_weights = obs_weights
)

cat("p estimate:", round(res_mle$summary$p_value, 4), "\n")
cat("SE        :", round(res_mle$summary$p_se,    4), "\n")
cat("Converged :", res_mle$summary$converged,          "\n")

Analyzing results

Once you have a result object, extract ecologically meaningful metrics:

growth_stats  <- analyze_growth_patterns(res_bs)
feeding_stats <- analyze_feeding_performance(res_bs)
energy_budget <- analyze_energy_budget(res_bs)

# Growth
cat(sprintf("Final weight        : %.1f g\n",   growth_stats$final_weight$estimate))
cat(sprintf("Total growth        : %.1f g\n",   growth_stats$total_growth$estimate))
cat(sprintf("Specific growth rate: %.4f g/g/day\n",
  growth_stats$specific_growth_rate$estimate))

# Feeding performance
cat(sprintf("Total consumption   : %.1f g\n",   feeding_stats$total_consumption$estimate))
cat(sprintf("Gross conv. eff.    : %.3f\n",
  feeding_stats$gross_conversion_efficiency$estimate))

Interpreting the energy budget

A typical juvenile Chinook energy budget over a 180-day summer season:

Component % of consumed energy
Respiration (metabolism) ~55–60%
Egestion + Excretion (waste) ~20%
Somatic growth ~20–25%

This breakdown tells you how efficiently the fish converts food into body mass under the simulated conditions.


Sensitivity analysis

A useful diagnostic: how does final weight respond to different temperatures and feeding levels?

# Grid of temperature offsets × p values
sensitivity <- expand.grid(
  temp_offset = seq(-2, 4, by = 1),
  p_value     = seq(0.3, 1.0, by = 0.1)
)

# For each combination, run a direct simulation and extract final weight
# (see full vignette for the loop implementation)

This produces a sensitivity surface that reveals whether growth is more limited by temperature or by food availability — critical for climate impact assessments.


Why bioenergetics?

Bioenergetics models are workhorses in fisheries science for good reason:

  • Consumption estimation — infer how much a fish population eats without direct diet sampling
  • Climate impact — project how warming temperatures change energy demand and growth
  • Ecosystem modeling — link individual-level physiology to population and ecosystem dynamics
  • Management — evaluate stocking scenarios, prey availability, or habitat quality

fb4package makes this framework accessible in R, with a tidy interface and a species database that covers most commercially and ecologically important taxa.


References

Deslauriers D, Chipps SR, Breck JE, Rice JA, Madenjian CP (2017). Fish Bioenergetics 4.0: An R-Based Modeling Application. Fisheries 42(11):586–596.

Stewart DJ, Ibarra M (1991). Predation and production by salmonine fishes in Lake Michigan, 1978–88. Canadian Journal of Fisheries and Aquatic Sciences 48(5):909–922.

Chipps SR, Wahl DH (2008). Bioenergetics modeling in the 21st century: Reviewing new insights and revisiting old constraints. Transactions of the American Fisheries Society 137(1):298–313.