Author: Brian Masinde
Date: 2026-03-09

# FlyingR: Predicting birds’ flight range. A Tutorial.

#### Brian Masinde

#### 2026-03-09

# Introduction

In this tutorial, a sample of 28 birds from the program Flight is used to demonstrate how to use the package to estimate flight ranges of migrating birds. Note that in package `FlyingR` two approaches are implemented for this estimation. The first from Pennycuick (1975) and the second from Pennycuick and Battley (1998) and Pennycuick (2008). In this tutorial focus is on the second approach (a time-marching approach).

## Basic usage with default settings.

    data("birds", package = "FlyingR")
    results <-
      FlyingR::migrate(data = birds,
                      method = "cmm",
                      speed_control = 1,
                      min_energy_protein = 0.05)

    ## ## settings not defined. Using default constants.
    ##
    ## Default airDensity = 1.00 kg m^3

    # extract range as a vector
    results$range

    ##           Anser anser  Hydrobates pelagicus   Pachyptila desolata
    ##              3523.341              3015.679              4234.999
    ##       Regulus regulus      Calidris canutus     Aegypius monachus
    ##              1239.048              4427.874              3977.319
    ##      Limosa lapponica           Anas crecca       Hirundo rustica
    ##             16542.199              3957.240              3366.906
    ##         Cygnus cygnus          Sylvia borin     Luscinia luscinia
    ##              3486.521              2626.797              2132.726
    ##       Corvus monedula         Anas penelope   Fregata magnificens
    ##              2282.793              6417.650             11818.913
    ##      Larus ridibundus      Diomedea exulans   Phalacrocorax carbo
    ##              6154.056              6253.491              2940.844
    ##       Gyps rueppellii   Torgos tracheliotus         Ardeotis kori
    ##              7849.544              6885.830              4622.839
    ##      Sturnus vulgaris     Fringilla coelebs      Carduelis spinus
    ##              4342.847              2781.554              2573.194
    ##     Turdus philomelos Calidris tenuirostris     Buteo swainsoni M
    ##              3155.936              7021.358              5943.870
    ##     Buteo swainsoni F
    ##              7832.813

In this case, the muscle mass adjustment criteria is the constant muscle mass (“cmm”, meaning protein is not consumed from muscle mass). Run by holding true airspeed at start of flight constant, the alternative is maintain the ratio between true airspeed and minimum power speed constant (`speed_control = 0`). In this simulation, additional protein is consumed (5% of the total energy) and this comes from the airframe mass.

The default settings are as follows:

These settings could be adjusted. For example, the default value for the induced power factor is high, the recommended being 0.9. This can be adjusted as follows `settings = list(ipf =0.9)`. Basic knowledge of defining lists is required.

    results <-
      FlyingR::migrate(data = birds,
                      method = "cmm",
                      settings = list(ipf = 0.9),
                      speed_control = 1,
                      min_energy_protein = 0.05)

Other methods are the constant specific work (`method = "csw"`) and constant specific power (`method = "csp"`).

    results <-
      FlyingR::migrate(data = birds,
                      method = "csw",
                      settings = list(ipf = 0.9),
                      speed_control = 1,
                      min_energy_protein = 0.05)

    # obtain remaining body mass
    results$bodyMass

    ##           Anser anser  Hydrobates pelagicus   Pachyptila desolata
    ##           2.519994936           0.017024028           0.097398903
    ##       Regulus regulus      Calidris canutus     Aegypius monachus
    ##           0.003760447           0.075167275           6.901150950
    ##      Limosa lapponica           Anas crecca       Hirundo rustica
    ##           0.072327343           0.138060363           0.010553746
    ##         Cygnus cygnus          Sylvia borin     Luscinia luscinia
    ##           8.807117551           0.012255185           0.017009737
    ##       Corvus monedula         Anas penelope   Fregata magnificens
    ##           0.127357220           0.349389454           0.843895225
    ##      Larus ridibundus      Diomedea exulans   Phalacrocorax carbo
    ##           0.168160520           6.415392298           1.809294431
    ##       Gyps rueppellii   Torgos tracheliotus         Ardeotis kori
    ##           3.579893319           3.626748479           6.821882187
    ##      Sturnus vulgaris     Fringilla coelebs      Carduelis spinus
    ##           0.038112871           0.012801287           0.006230200
    ##     Turdus philomelos Calidris tenuirostris     Buteo swainsoni M
    ##           0.039904948           0.100881801           0.445811279
    ##     Buteo swainsoni F
    ##           0.512133471

    # starting minimum power speed
    results$startMinSpeed

    ##           Anser anser  Hydrobates pelagicus   Pachyptila desolata
    ##             17.943034              7.226151              9.809614
    ##       Regulus regulus      Calidris canutus     Aegypius monachus
    ##              6.478470              9.987883             17.961954
    ##      Limosa lapponica           Anas crecca       Hirundo rustica
    ##             12.067335             11.790593              6.894405
    ##         Cygnus cygnus          Sylvia borin     Luscinia luscinia
    ##             21.156584              8.333710              8.523696
    ##       Corvus monedula         Anas penelope   Fregata magnificens
    ##             10.644033             14.738550             11.825379
    ##      Larus ridibundus      Diomedea exulans   Phalacrocorax carbo
    ##              9.754916             17.701882             17.168295
    ##       Gyps rueppellii   Torgos tracheliotus         Ardeotis kori
    ##             17.893455             16.836893             21.188122
    ##      Sturnus vulgaris     Fringilla coelebs      Carduelis spinus
    ##             10.213171              8.064553              7.079332
    ##     Turdus philomelos Calidris tenuirostris     Buteo swainsoni M
    ##             10.071773             11.706853             11.977692
    ##     Buteo swainsoni F
    ##             12.890201

    # end of flight minimum power speed
    results$endMinSpeed

    ##           Anser anser  Hydrobates pelagicus   Pachyptila desolata
    ##             15.687467              6.290587              8.401544
    ##       Regulus regulus      Calidris canutus     Aegypius monachus
    ##              5.734903              8.385144             15.925371
    ##      Limosa lapponica           Anas crecca       Hirundo rustica
    ##              7.020574              9.874101              5.666809
    ##         Cygnus cygnus          Sylvia borin     Luscinia luscinia
    ##             18.824703              6.856383              7.306440
    ##       Corvus monedula         Anas penelope   Fregata magnificens
    ##              9.466638             11.324106              9.417949
    ##      Larus ridibundus      Diomedea exulans   Phalacrocorax carbo
    ##              8.181101             15.491528             15.291795
    ##       Gyps rueppellii   Torgos tracheliotus         Ardeotis kori
    ##             14.108853             13.789329             17.599673
    ##      Sturnus vulgaris     Fringilla coelebs      Carduelis spinus
    ##              7.913486              6.633048              5.821629
    ##     Turdus philomelos Calidris tenuirostris     Buteo swainsoni M
    ##              8.287716              8.855204              9.960540
    ##     Buteo swainsoni F
    ##             10.113454

# Examples with real-world data

## Examples 1: Garden Warbler and Lesser Whitethroat

These two passerine species are trans-Saharan migrants. Lesser whitethroats winter in the Sahel zone of eastern and north-eastern Africa , while garden warblers spend winter further, i.e., in southern Africa . Data on the Garden Warbler and Lesser Whitethroat are obtained from various bird ringing stations situated along their SE European flyway leading from Europe towards African winter quarters. This is split into four regions: Southern Baltic, Eastern Mediterranean, Northern Mediterranean and North Eastern Africa.These data include the 1-st year individuals (immatures): 1,044 garden warblers and 848 lesser whitethroats, and span the years 2000 to 2006. The sample contains the following variables: station code, ring number, age, body mass, fat score, wing and tail measurements. The fat score is used to derive fat fraction and subsequently fat mass by the procedure described by . Body mass and fat mass are converted from grams to kilograms. The wing measurements do not equal the required wingspan as these are measured differently (i.e., the wingspan is measured from tip to tip of wings, particularly the primary feathers, in metres). Other missing variables are the wing area and muscle mass. provide estimates for the wingspan and wing area for several species. For the Garden Warbler, the wingspan and wing area are 0.223 (m) and 0.0093 (\(m^2\)), respectively. While for the Lesser Whitethroat, these are 0.185 (m) and 0.0073 (\(m^2\)). Muscle mass estimates are obtained by using the muscle fraction of 0.17 as recommended by . Figure \(\ref{results_range}\) presents the flight range distribution of these two species in kilometres for each region separately.

    # ------------------------------------------------------------------------------
    # A generic function
    # migration function each species at different regions.
    # Function returns a list (each species migrated by region)
    # ------------------------------------------------------------------------------

    region_migrate <- function(data) {

      # we need dplyr for filter function
      # we need FlyingR for migration
      require(dplyr)
      require(FlyingR)

      # number of regions in dataset
      regions <- unique(data$Region)

      # regions data in a list
      region_data <- list()

      for (i in 1:length(regions)) {

        # filter data by region
        filter_data_region <-data %>% filter(Region == regions[i])

        # migrate filter data
        user_settings <- list(ipf = 0.9)
        results <- FlyingR::migrate(data = filter_data_region[, 3:9], method = "csw",
                              speed_control = 0,
                              settings = user_settings,
                              min_energy_protein = 0.05)

        region_data[[i]] <- cbind(filter_data_region, "range" = as.vector(results$range))

      }

      # return region data as a list
      return(region_data)

    }

    # ------------------------------------------------------------------------------
    # migrating sylvia borin
    # ------------------------------------------------------------------------------
    data("garden_wablers")

    ## Warning in data("garden_wablers"): data set 'garden_wablers' not found

    wablers_migration <- region_migrate(data = garden_wablers)

    ## Loading required package: dplyr

    ##
    ## Attaching package: 'dplyr'

    ## The following objects are masked from 'package:stats':
    ##
    ##     filter, lag

    ## The following objects are masked from 'package:base':
    ##
    ##     intersect, setdiff, setequal, union

    ## Loading required package: FlyingR

    ## Welcome to package FlyingR

    # ------------------------------------------------------------------------------
    # migrating Lesser Whitethroats (sylvia curruca)
    # ------------------------------------------------------------------------------
    data("lesser_whitethroats")
    whitethroats_migration <- region_migrate(data = lesser_whitethroats)

    # ------------------------------------------------------------------------------
    # migration list to one dataframe for Garden Wablers(sylvia_borin)
    # ------------------------------------------------------------------------------
    wablers_results <- data.frame()

    for (i in 1:length(wablers_migration)) {

      wablers_results <- rbind(wablers_results, wablers_migration[[i]])
    }

    cat("number of rows sylvia borin:", nrow(wablers_results), sep = " ")

    ## number of rows sylvia borin: 119

    # ------------------------------------------------------------------------------
    # migration list to one dataframe for Lesser Whitethroats
    # ------------------------------------------------------------------------------

    whitethroats_results <- data.frame()

    for (i in 1:length(whitethroats_migration)) {

      whitethroats_results <- rbind(whitethroats_results, whitethroats_migration[[i]])
    }

    cat("number of rows in sylvia curruca:", nrow(whitethroats_results), sep = " ")

    ## number of rows in sylvia curruca: 84

    # ------------------------------------------------------------------------------
    # order of region
    # rename the regions for clarity
    # ------------------------------------------------------------------------------

    wablers_results$Region <- factor(wablers_results$Region,
                                   levels = c("S Balt", "N Med",
                                              "E Med"), ordered = TRUE)

    levels(wablers_results$Region) <- c("S Baltic", "N Medit",
                               "E Medit")

    whitethroats_results$Region <- factor(whitethroats_results$Region,
                                     levels = c("S Balt", "N Med",
                                              "E Med", "NE Afr"), ordered = TRUE)

    levels(whitethroats_results$Region) <- c("S Baltic", "N Medit",
                               "E Medit", "NE Africa")

    # ------------------------------------------------------------------------------
    # both plots in one
    # combine the data sets first
    # ------------------------------------------------------------------------------

    results <- rbind(wablers_results, whitethroats_results, deparse.level = 0)

    # make sure it sums up
    nrow(wablers_results) + nrow(whitethroats_results) == nrow(results)

    ## [1] TRUE

    # ------------------------------------------------------------------------------
    # new combined plot
    # ------------------------------------------------------------------------------
    require(ggplot2)

    ## Loading required package: ggplot2

    results_combined_plot <- ggplot(results, aes(x = Region, y = range,
                                                 colour = species)) +
      geom_boxplot() +
      #ggtitle("Sylvia curruca flight range by region") +
      theme_bw()+
      labs(y = "Range")+
      ylim(500, 4500)

    results_combined_plot

Based on these results the two species clearly differ in their potential flight ranges and therefore show species-specific migration strategy, when travelling along the same SE flyway, as was described in the original paper based on Flight .

## Examples 2: Curlew Sandpiper

These two passerine species are trans-Saharan migrants. Lesser whitethroats winter in the Sahel zone of eastern and north-eastern Africa , while garden warblers spend winter further, i.e., in southern Africa . Data on the Garden Warbler and Lesser Whitethroat are obtained from various bird ringing stations situated along their SE European flyway leading from Europe towards African winter quarters. This is split into four regions: Southern Baltic, Eastern Mediterranean, Northern Mediterranean and North Eastern Africa. These data include the 1-st year individuals (immatures): 1,044 garden warblers and 848 lesser whitethroats, and span the years 2000 to 2006. The sample contains the following variables: station code, ring number,age, body mass, fat score, wing and tail measurements. The fat score is used to derive fat fraction and subsequently fat mass by the procedure described by . Body mass and fat mass are converted from grams to kilograms. The wing measurements do not equal the required wingspan as these are measured differently (i.e., the wingspan is measured from tip to tip of wings, particularly the primary feathers, in metres). Other missing variables are the wing area and muscle mass. provide estimates for the wingspan and wing area for several species. For the Garden Warbler, the wingspan and wing area are 0.223 (m) and 0.0093 (\(m^2\)), respectively. While for the Lesser Whitethroat, these are 0.185 (m) and 0.0073 (\(m^2\)). Muscle mass estimates are obtained by using the muscle fraction of 0.17 as recommended by . Figure below presents the flight range distribution of these two species in Kilometres for each region separately.

    data("curlew_sandpiper")

    # needed data for migration columns 2:9

    curlew_range <-
        FlyingR::migrate(
            data = curlew_sandpiper[, 2:9],
            method = "cmm",
            speed_control = 0,
            min_energy_protein = 0.05,
            settings = list(ipf = 0.9)
        )

    # Addin computed range to the dataset
    curlew_results <- curlew_sandpiper[, 1:2]
    curlew_results$range <- as.vector(curlew_range$range)

## Visualize the results

    # migrate curlew sandpiper
    require(ggplot2)
    require(dplyr)

    curlew_plot <- ggplot(data = curlew_results)+
      geom_point(aes(x = day, y = range, color = name))+
      theme_bw(base_size = 10)+
      ggtitle("")+
      scale_color_discrete(name = "Individual")+
        xlab("Day")+
        ylab("Range (km)")

    curlew_plot

# References

Pennycuick, Colin J. 1975. “Mechanics of Flight.” In Avian Biology, edited by D. S. Farner and King J. R, 5:1–75. New York.
———. 2008. Modelling the Flying Bird. First Edition. Vol. 5. New Jersey: Elsevier.
Pennycuick, Colin J., and Philip F. Battley. 1998. “Burning the Engine: A Time-Marching Computation of Fat and Protein Consumption in a 5420-Km Non-Stop Flight by Great Knots, Calidris Tenuirostris.” Journal of Theoretical Biology 191: 47–61. https://doi.org/https://doi.org/10.1006/jtbi.1997.0572.