This vignette for the sizeSpectraFit R package is for the MLEbins method, which we introduced in our MEPS paper. We use an example data set from the Mediterranean from our new Global Ecology and Conservation paper.
The MLEbins method calculates the maximum likelihood estimate of the size-spectrum exponent, \(b\), when the data are collected as lengths and we have species-specific length-weight coefficients. So individual body masses are not available; if they were we could use the MLE method in the fit-data.html vignette. If you have binned body masses but the bins overlap, then you also need to use MLEbins.
The lengths may already be binned, for example a length bin could be 10-15 cm. And even when measured more accurately, the resolution of the measurements essentially adds uncertainty to a length. For example, a fish of length 15 cm measured to the nearest cm has a true length in the range 14.5-15.5 cm, which then gets converted into a body-mass bin using that species specific length-weight coefficients. The resulting body-mass bins can overlap each other (unlike for the MLEbin method), because the length bins of differently shaped species get converted to different body-mass bins. See the MEPS paper paper for further explanation (rather than it being repeated here, where we focus on applying the method).
We use real data of measured lengths from experimental trawl surveys conducted in the Northwestern Mediterranean Sea, as part of the research projects RESNEP and BITER by the Instituto de Ciencias del Mar (ICM-CSIC), Spain; data courtesy of Juliana Quevedo, Joan B. Company, Nixon Bahamon, and Jordi Ribera Altimir, as used in Quevedo et al. (2026).
The full raw data are available in sizeSpectraFit as
mediterranean_data
# A tibble: 17,850 × 5
strata group species length number
<fct> <fct> <fct> <dbl> <dbl>
1 baseline Actinopterygii Phycis blennoides 124 30.3
2 baseline Crustacea Nephrops norvegicus 31.0 15.2
3 baseline Crustacea Nephrops norvegicus 30.4 7.58
4 baseline Actinopterygii Phycis blennoides 122 30.3
5 baseline Actinopterygii Helicolenus dactylopterus 140 7.58
6 baseline Crustacea Nephrops norvegicus 31.0 15.2
7 baseline Actinopterygii Phycis blennoides 128 30.3
8 baseline Crustacea Nephrops norvegicus 38.1 7.58
9 baseline Actinopterygii Phycis blennoides 148 30.3
10 baseline Actinopterygii Phycis blennoides 127 30.3
# ℹ 17,840 more rowsfor which each row has strata (baseline, ntr: the
no-take reserve four years after its implementation, or fg:the fishing
grounds four years after the reserve’s implementation), species group
given as group, species, number
of individuals of that species that were measured as length
length (mm). Values of number can be
non-integer because they are standardised by the area trawled in each
tow; the MLEbins method can explicitly deal with this.
A summary of the data is
summary(mediterranean_data)
strata group species
fg :6270 Actinopterygii:7228 Parapenaeus longirostris:2986
ntr :7550 Cephalopoda : 607 Nephrops norvegicus :2550
baseline:4030 Chondrichthyes:1310 Plesionika heterocarpus :2023
Crustacea :8705 Phycis blennoides :1930
Scyliorhinus canicula :1307
Trigla lyra : 806
(Other) :6248
length number
Min. : 12.10 Min. : 6.006
1st Qu.: 27.05 1st Qu.: 7.353
Median : 40.00 Median : 14.706
Mean : 102.20 Mean : 24.301
3rd Qu.: 164.00 3rd Qu.: 29.594
Max. :1000.00 Max. :170.374
We also have the species-specific length-weight coefficients \(\alpha\) and \(\beta\) for each species:
mediterranean_length_weight_coefficients
# A tibble: 53 × 3
species alpha beta
<fct> <dbl> <dbl>
1 Abralia veranyi 0.689 1.42
2 Aegaeon lacazei 0.0275 2.44
3 Argentina sphyraena 0.0047 3.05
4 Arnoglossus rueppelii 0.0051 3.01
5 Bathypolypus sponsalis 0.562 2.56
6 Capros aper 0.0138 3.11
7 Chlorotocus crassicornis 0.585 2.43
8 Conger conger 0.0006 3.21
9 Eusergestes arcticus 0.287 2.31
10 Gaidropsarus macrophthalmus 0.0075 2.85
# ℹ 43 more rowswhere the coefficients are for the equation \(w = \alpha_s l^{\beta_s}\) relating weights (g) to lengths (mm) for each species \(s\).
The above shows what kind of data are needed to implement the MLEbins method.
We first wrangle the data into the format required for using the MLEbins method. Similar wrangling would need to be done for users’ data to get the required inputs for the likelihood calculations.
For demonstration purposes we will fit the size spectrum for just one species group (Cephalopoda) and for one strata, the fishing grounds (fg). So we first filter to obtain just those values:
dat <- dplyr::filter(mediterranean_data,
strata == "fg",
group == "Cephalopoda")
dat
# A tibble: 157 × 5
strata group species length number
<fct> <fct> <fct> <dbl> <dbl>
1 fg Cephalopoda Abralia veranyi 42 58.8
2 fg Cephalopoda Todarodes sagittatus 294 7.35
3 fg Cephalopoda Todarodes sagittatus 185 7.35
4 fg Cephalopoda Todarodes sagittatus 269 7.35
5 fg Cephalopoda Todaropsis eblanae 116 7.35
6 fg Cephalopoda Todaropsis eblanae 126 7.35
7 fg Cephalopoda Todaropsis eblanae 87 7.35
8 fg Cephalopoda Todaropsis eblanae 130 7.35
9 fg Cephalopoda Todaropsis eblanae 94 7.35
10 fg Cephalopoda Todaropsis eblanae 120 7.35
# ℹ 147 more rows
summary(dat)
strata group species length
fg :157 Actinopterygii: 0 Illex coindetii :53 Min. : 17.00
ntr : 0 Cephalopoda :157 Todaropsis eblanae :38 1st Qu.: 46.00
baseline: 0 Chondrichthyes: 0 Abralia veranyi :37 Median : 95.00
Crustacea : 0 Rossia macrosoma :11 Mean : 90.62
Todarodes sagittatus: 9 3rd Qu.:115.00
Sepia orbignyana : 7 Max. :335.00
(Other) : 2
number
Min. : 7.151
1st Qu.: 7.204
Median : 7.353
Mean :12.505
3rd Qu.: 7.435
Max. :61.345
Thus, dat contains data for just one strata
and one group, but multiple species.
The lengths are in mm, with integer values measured to nearest mm using standard lab metric tape. We convert these length values to minima and maxima of bins, with the measured lengths representing the midpoints. This keeps track of the uncertainty in the ‘true’ length of individuals and is needed for the MLEbins method, which also allows for other species groups having different resolution.
This conversion is done using calc_bin_breaks(), which
takes a tibble that contains a column of measurements and appends
minimum and maximum values for each bin (see
?calc_bin_breaks):
dat_with_breaks <- calc_bin_breaks(dat,
bin_width = 1) %>%
dplyr::rename(bin_count = number)
dat_with_breaks
# A tibble: 157 × 7
strata group species length bin_count length_bin_min length_bin_max
<fct> <fct> <fct> <dbl> <dbl> <dbl> <dbl>
1 fg Cephalopoda Abralia veranyi 42 58.8 41.5 42.5
2 fg Cephalopoda Todarodes sagi… 294 7.35 294. 294.
3 fg Cephalopoda Todarodes sagi… 185 7.35 184. 186.
4 fg Cephalopoda Todarodes sagi… 269 7.35 268. 270.
5 fg Cephalopoda Todaropsis ebl… 116 7.35 116. 116.
6 fg Cephalopoda Todaropsis ebl… 126 7.35 126. 126.
7 fg Cephalopoda Todaropsis ebl… 87 7.35 86.5 87.5
8 fg Cephalopoda Todaropsis ebl… 130 7.35 130. 130.
9 fg Cephalopoda Todaropsis ebl… 94 7.35 93.5 94.5
10 fg Cephalopoda Todaropsis ebl… 120 7.35 120. 120.
# ℹ 147 more rowsWe then convert data of counts of species in length bins to counts in
body-mass bins calculated using the species-specific length-weight
coefficients. This is done using
length_bins_to_body_mass_bins()
dat_joined <-
length_bins_to_body_mass_bins(dat_with_breaks,
mediterranean_length_weight_coefficients,
length_data_unit = "mm")
dat_joined
# A tibble: 157 × 11
strata group species length bin_count length_bin_min length_bin_max alpha beta
<fct> <fct> <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 fg Cepha… Abrali… 42 58.8 4.15 4.25 0.689 1.42
2 fg Cepha… Todaro… 294 7.35 29.4 29.4 0.0102 3.31
3 fg Cepha… Todaro… 185 7.35 18.4 18.6 0.0102 3.31
4 fg Cepha… Todaro… 269 7.35 26.8 27.0 0.0102 3.31
5 fg Cepha… Todaro… 116 7.35 11.6 11.6 0.0996 2.79
6 fg Cepha… Todaro… 126 7.35 12.6 12.6 0.0996 2.79
7 fg Cepha… Todaro… 87 7.35 8.65 8.75 0.0996 2.79
8 fg Cepha… Todaro… 130 7.35 13.0 13.0 0.0996 2.79
9 fg Cepha… Todaro… 94 7.35 9.35 9.45 0.0996 2.79
10 fg Cepha… Todaro… 120 7.35 12.0 12.0 0.0996 2.79
# ℹ 147 more rows
# ℹ 2 more variables: weight_bin_min <dbl>, weight_bin_max <dbl>We then retain just the columns needed to fit using the MLEbins
method for input into determine_xmin_and_fit_mlebins() and
also sum the counts for the same combinations of species and weight
bins, which is done in mediterranean_for_mlebins(). That
function should work for similarly structured data (requires
group and strata, but the function can be
easily adapted if needed). Since we repeated the same types of analysis
for multiple species groups and strata combinations, it made sense to
write a function. As an extra step here we also are going to look at
only small cephalopods below 20.9 g; see page 8 of Appendix B of [Quevedo
et al. (2026)] for details.
data_cephsmall_fg <- mediterranean_for_mlebins(dat_joined) %>%
dplyr::filter(bin_min < 20.9) %>%
dplyr::arrange(bin_min)
data_cephsmall_fg
# A tibble: 29 × 4
species bin_min bin_max bin_count
<fct> <dbl> <dbl> <dbl>
1 Abralia veranyi 2.46 2.60 61.3
2 Abralia veranyi 2.60 2.75 64.6
3 Abralia veranyi 2.89 3.05 7.35
4 Illex coindetii 3.14 3.35 43.9
5 Abralia veranyi 3.35 3.51 14.8
6 Abralia veranyi 3.51 3.67 64.8
7 Abralia veranyi 3.67 3.83 79.4
8 Illex coindetii 3.79 4.03 43.2
9 Abralia veranyi 3.83 3.99 58.4
10 Abralia veranyi 3.99 4.16 29.4
# ℹ 19 more rowsNote the overlapping bins in rows 7 and 8, and also in rows 8 and 9, because the bins correspond to different species which have different length-weight coefficients. Hence the need for the MLEbins method.
We are now ready to fit the data. The
determine_xmin_and_fit_mlebins() function calculates a
histogram to find the peak from which to start the bounded power-law fit
(Quevedo
et al., 2026). It then fits the size spectrum using
fit_size_spectrum_mlebins(); if you wish to specify
x_min then you would just use
fit_size_spectrum_mlebins(). This can take a few minutes,
so I have saved res_cephsmall_fg as a data object in
sizeSpectraFit (this next chunk is not actually run, don’t tell anyone…;
so do make sure to take out eval = FALSE if you use this
code as a template):
The results are
res_cephsmall_fg
$mlebins_fit
$b_mle
[1] -2.997532
$b_conf
[1] -3.162692 -2.836292
$data
# A tibble: 25 × 7
species bin_min bin_max bin_count count_gte_bin_min low_count high_count
<fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Illex coindetii 3.14 3.35 43.9 952. 908. 952.
2 Abralia veranyi 3.35 3.51 14.8 908. 893. 908.
3 Abralia veranyi 3.51 3.67 64.8 893. 829. 893.
4 Abralia veranyi 3.67 3.83 79.4 829. 706. 829.
5 Illex coindetii 3.79 4.03 43.2 749. 618. 829.
6 Abralia veranyi 3.83 3.99 58.4 706. 648. 749.
7 Abralia veranyi 3.99 4.16 29.4 648. 618. 691.
8 Abralia veranyi 4.16 4.33 75.9 618. 542. 618.
9 Abralia veranyi 4.33 4.49 7.44 542. 535. 542.
10 Abralia veranyi 4.49 4.67 14.7 535. 520. 535.
# ℹ 15 more rows
$x_min
[1] 3.139746
$x_max
[1] 18.04885
$n
[1] 951.977
$method
[1] "MLEbins"
attr(,"class")
[1] "size_spectrum_mlebins" "list"
$h
$breaks
[1] 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
$mids
[1] 0.5 1.5 2.5 3.5 4.5 5.5 6.5 7.5 8.5 9.5 10.5 11.5 12.5 13.5 14.5 15.5
[17] 16.5 17.5
$counts
[1] 0.000 0.000 133.257 333.841 289.859 175.430 58.278 0.000 65.590 7.312
[11] 0.000 0.000 0.000 0.000 7.204 0.000 7.312 7.151
$xname
[1] "Total counts in each bin"
$equidist
[1] TRUE
attr(,"class")
[1] "histogram"
attr(,"class")
[1] "determine_xmin_and_fit_mlebins" "list"This is a list of two objects (each is a list):
$mlebins_fit contains the details of the maximum likelihood
fitting similar to the other methods, and \(h\) is an object of class
histogram that contains details of the histogram used to
determine \(x_{min}\). (Use
remove_hist(res_cephsmall_fg) to remove the histogram
object and retain the class; this also works on lists of results from
multiple applications of
determine_xmin_and_fit_mlebins()).
The object has the class determine_xmin_and_fit_mlebins
such that our customised
plot.determine_xmin_and_fit_mlebins() automatically gets
used:
The top panel is a histogram of total counts of minimum body sizes,
using 1-g body-size bins, used to determine \(x_{min}\). The left-most red bin is the
modal bin, and \(x_{x_min}\) for this
group-strata combination is the minimum value within that 1-g bin.
Middle and bottom panels (as suggested in Figure 7 of [2]) are the fit of the bounded power-law distribution using the MLEbins method with linear and logarithmic y-axes, respectively. For each body-mass bin, the green horizontal line show the range of body sizes, with its value on the y-axis corresponding to the total number of individuals in bins whose minima are \(\geq\) the bin’s minimum; the green lines help to distinguish each bin when bins are overlapping. The vertical span of each grey rectangle shows the possible range of number of individuals with body masses \(\geq\) body mass of individuals in that bin (horizontal span matches the green line). Red curves are fits of the MLE, with dashed lines showing 95% confidence intervals.
These figure are admittedly a little complex, but retain the full uncertainty arising from binned data and conversions through species-specific length-weight relationships. As can be seen, some of the resulting rectangles are quite large, and simply using a single point for each would suggest higher accuracy than is warranted.
Do see the help files for relevant functions, and feel free to email me (or make an issue) with any questions.
sessionInfo()
R version 4.5.1 (2025-06-13 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 22631)
Matrix products: default
LAPACK version 3.12.1
locale:
[1] LC_COLLATE=English_United States.utf8 LC_CTYPE=English_United States.utf8
[3] LC_MONETARY=English_United States.utf8 LC_NUMERIC=C
[5] LC_TIME=English_United States.utf8
time zone: America/Vancouver
tzcode source: internal
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] sizeSpectraFit_0.0.99 pacea_1.0.0 testthat_3.2.3
[4] devtools_2.4.5 usethis_3.2.1
loaded via a namespace (and not attached):
[1] gtable_0.3.6 xfun_0.55 bslib_0.9.0 ggplot2_4.0.1
[5] htmlwidgets_1.6.4 remotes_2.5.0 vctrs_0.6.5 tools_4.5.1
[9] generics_0.1.4 proxy_0.4-27 tibble_3.3.0 pkgconfig_2.0.3
[13] KernSmooth_2.23-26 RColorBrewer_1.1-3 pals_1.10 S7_0.2.1
[17] desc_1.4.3 lifecycle_1.0.4 compiler_4.5.1 farver_2.1.2
[21] brio_1.1.5 mapproj_1.2.12 codetools_0.2-20 httpuv_1.6.16
[25] class_7.3-23 maps_3.4.3 htmltools_0.5.9 sass_0.4.10
[29] yaml_2.3.12 later_1.4.4 pillar_1.11.1 jquerylib_0.1.4
[33] urlchecker_1.0.1 tidyr_1.3.2 ellipsis_0.3.2 classInt_0.4-11
[37] cachem_1.1.0 sessioninfo_1.2.3 iterators_1.0.14 foreach_1.5.2
[41] mime_0.13 tidyselect_1.2.1 digest_0.6.39 sf_1.0-21
[45] dplyr_1.1.4 purrr_1.2.0 rprojroot_2.1.1 fastmap_1.2.0
[49] grid_4.5.1 colorspace_2.1-2 cli_3.6.5 magrittr_2.0.4
[53] utf8_1.2.6 dichromat_2.0-0.1 pkgbuild_1.4.8 e1071_1.7-16
[57] withr_3.0.2 waldo_0.6.2 scales_1.4.0 promises_1.5.0
[61] timechange_0.3.0 lubridate_1.9.4 rmarkdown_2.30 httr_1.4.7
[65] otel_0.2.0 memoise_2.0.1 shiny_1.12.1 evaluate_1.0.5
[69] knitr_1.51 miniUI_0.1.2 profvis_0.4.0 rlang_1.1.6
[73] Rcpp_1.1.0 DBI_1.2.3 xtable_1.8-4 glue_1.8.0
[77] pkgload_1.4.1 rstudioapi_0.17.1 jsonlite_2.0.0 R6_2.6.1
[81] units_0.8-7 fs_1.6.6