Willow Flycatcher

Species Distribution

Willow Flycatcher

Species Description

Willow Flycatchers (Empidonax traillii) specialize in areas with willows and other shrubs near running and still water. They are about 6 inches in length with brown, gray, and white plumage, a rounded wing, and a square-tipped tail. Calls are in the form of a chirp, buzz, or trill and match an undulating pattern. Nests are placed 4-15 feet above water or damp ground and constructed as an open cup of grass, bark, and plant fibers. The species migrates long distances, breeding in the U.S. and Canada and wintering in Mexico, Central America, and northern South America. They are common in most locations in their range despite a 25% decline in population between 1966 to 2019. The loss of wet marshes, wet meadows, and riparian vegetation has contributed to declining species abundance. According to the Bird Genoscape Project, there are seven geneticially distinct populations of Willow Flycatcher in North America: the Pacific Northwest, Kern, California, southern California, White Mountain, Arizona, Interior West, Southwest, and Eastern. However, there are only four recognized subspecies: E. t. brewsteri, E. t. adastus, E. t. extimus, and E. t. traillii.

In my home state of California, there are three endangered subspecies: Southwestern Willow Flycatcher in central and southern California (Federal and State), Little Willow Flycatcher in high elevation Sierra Nevada (State), and Great Basin Willow Flycatcher in desert riparian area (State). Researchers have found that the Southwestern Willow Flycatcher has a higher prevalence of gene variants today compared to 100 years ago that are associated with adapting to wet and humid conditions. This difference is likely due to interbreeding with species in the Southwest and Pacific Northwest, producing an evolutionary response to climate change. Adaptations like these are why it is vital to preserve the interconnectivity of species populations through the protection of habitat and landscape mobility.

Data Description

The GBIF Occurrence dataset was retrived from the Global Biodiversity Information Facility Occurrence Store and is scoped to the year of interest (2023) and species under observation. There are 110,725 species occurrences total which are indicated by geographic coordinates spanning across five aggregated datasets.

The RESOLVE Ecoregions dataset (2017) depicts Earth’s 846 terrestrial ecoregions and was obtained as a shapefile. Ecoregions represent boundaries formed by biotic and abiotic conditions: geology, landforms, soils, vegetation, land use, wildlife, climate, and hydrology.

Data Citation

Global Biodiversity Information Facility. (2024). GBIF Occurrence Download [Data set]. https://doi.org/10.15468/dl.jqrwjf

RESOLVE. (2017). RESOLVE Ecoregions dataset [Data set]. https://doi.org/10.1093/biosci/bix014

Methods

The occurrences data was accessed with the Python client for the GBIF API and queried for the year (2023), species, and coordinates. The occurrences CSV file was ingested using the pandas library and country, state/province, latitude, longitude, month, and year records were selected. The resulting DataFrame was converted to a GeoDataFrame with monthly scope using geopandas, providing coordinates as the geometry and the WGS84 projection, which represents the Earth as a 3D ellipsoid, as the coordinate reference system (CRS).

Data for ecoregions were gathered from a RESOLVE shapefile (2017) and read into a GeoDataFrame with geopandas. This data was joined spatially with occurrences on the month and name, matching the WGS84 CRS. Monthly regional observations were counted from this GeoDataFrame. Next, the mean by region and by month was calculated and used for normalization. The data was normalized by space (ecoregion average) and time (monthly average) to account for the sampling effort.

For visualization, the GeoDataFrame was simplified to a Mercator projection from the Cartopy library which is compatible with the hvplot API and GeoViews. The GeoDataFrame was also joined with the normalized occurrences data. The plot produced highlights monthly migration patterns and is interactive due to the sliding widget from the HoloViews panel library.

Analysis

Import Libraries

import os
import pathlib
import time
import zipfile
from getpass import getpass
from glob import glob
import pandas as pd
import geopandas as gpd

import pygbif.occurrences as occ
import pygbif.species as species

# get month names
import calendar

# libraries for Dynamic mapping
import geoviews as gv
import hvplot.pandas
import cartopy.crs as ccrs
import panel as pn
pn.extension()
# Create data directory in the home folder
data_dir = os.path.join(
    # Home directory
    pathlib.Path.home(),
    # Earth analytics data directory
    'earth-analytics',
    'data',
    # Project directory
    'species-distribution',
)
os.makedirs(data_dir, exist_ok=True)

# Define the directory name for GBIF data
gbif_dir = os.path.join(data_dir, 'willow-flycatcher', '2023')

Access GBIF

reset_credentials = False
# GBIF needs a username, password, and email
credentials = dict(
    GBIF_USER=(input, ''),
    GBIF_PWD=(getpass, ''),
    GBIF_EMAIL=(input, ''),
)

for env_variable, (prompt_func, prompt_text) in credentials.items():
    # Delete credential from environment if requested
    if reset_credentials and (env_variable in os.environ):
        os.environ.pop(env_variable)
    # Ask for credential and save to environment
    if not env_variable in os.environ:
        os.environ[env_variable] = prompt_func(prompt_text)

# Query species
species_info = species.name_lookup('Empidonax traillii', rank='SPECIES')

# Get the first result
first_result = species_info['results'][0]

# Get the species key (nubKey)
species_key = first_result['nubKey']

Download GBIF data

# Only download once
gbif_pattern = os.path.join(gbif_dir, '*.csv')
if not glob(gbif_pattern):
    # Submit query to GBIF
    gbif_query = occ.download([
        "speciesKey = 2482786",
        "year = 2023",
        "hasCoordinate = TRUE",
    ],
    user=credentials['GBIF_USER'][1], 
    pwd=credentials['GBIF_PWD'][1], 
    email=credentials['GBIF_EMAIL'][1])

    # Only download once
    if not 'GBIF_DOWNLOAD_KEY' in os.environ:
        os.environ['GBIF_DOWNLOAD_KEY'] = gbif_query[0]

        # Wait for the download to build
        wait = occ.download_meta(os.environ['GBIF_DOWNLOAD_KEY'])['status'] 
        while not wait=='SUCCEEDED':
            wait = occ.download_meta(os.environ['GBIF_DOWNLOAD_KEY'])['status'] 
            time.sleep(5)

        # Download GBIF data
        download_info = occ.download_get(
            os.environ['GBIF_DOWNLOAD_KEY'], 
            path=data_dir)

        # Unzip GBIF data
        with zipfile.ZipFile(download_info['path']) as download_zip:
            download_zip.extractall(path=gbif_dir)

# Find the extracted .csv file path
gbif_path = glob(gbif_pattern)[0]

Load GBIF 2023 data

# Load the GBIF data
gbif_df_2023 = pd.read_csv(
    gbif_path, 
    delimiter='\t',
    index_col='gbifID',
    on_bad_lines='skip',
    usecols=['gbifID', 'month', 'year', 'countryCode', 'stateProvince', 'decimalLatitude', 'decimalLongitude']
)

Breeding Locations

Canada

wf_CA = gbif_df_2023.loc[gbif_df_2023['countryCode'] == 'CA']
wf_CA.value_counts()
countryCode  stateProvince     decimalLatitude  decimalLongitude  month  year
CA           Ontario           43.628270        -79.32917         5      2023    160
             British Columbia  49.234290        -122.79964        7      2023    142
                               48.319800        -123.54715        8      2023    139
                               49.234290        -122.79964        6      2023    115
             Ontario           41.955400        -82.51400         5      2023    109
                                                                                ... 
                               42.325730        -82.89813         6      2023      1
             British Columbia  49.235115        -122.89093        6      2023      1
             Ontario           42.325302        -82.92369         5      2023      1
             British Columbia  49.235120        -122.63271        6      2023      1
             Ontario           42.335026        -81.85847         7      2023      1
Name: count, Length: 6818, dtype: int64
april_CA = wf_CA.loc[wf_CA['month'] == 4]
april_CA.value_counts()
Series([], Name: count, dtype: int64)
may_CA = wf_CA.loc[wf_CA['month'] == 5]
may_CA.value_counts()
countryCode  stateProvince     decimalLatitude  decimalLongitude  month  year
CA           Ontario           43.628270        -79.329170        5      2023    160
                               41.955400        -82.514000        5      2023    109
                               42.038967        -82.509125        5      2023     91
             British Columbia  49.234290        -122.799640       5      2023     75
             Ontario           43.591060        -79.511246        5      2023     66
                                                                                ... 
             Quebec            46.547390        -71.153400        5      2023      1
                               46.834225        -71.378040        5      2023      1
                               46.841812        -71.317505        5      2023      1
                               47.051140        -70.815550        5      2023      1
                               47.078094        -70.785550        5      2023      1
Name: count, Length: 1364, dtype: int64

United States

wf_US = gbif_df_2023.loc[gbif_df_2023['countryCode'] == 'US']
wf_US.value_counts()
countryCode  stateProvince  decimalLatitude  decimalLongitude  month  year
US           Pennsylvania   39.889350        -75.260150        5      2023    227
                                                               6      2023    216
             Illinois       41.963383        -87.634420        5      2023    177
             Ohio           41.451912        -82.667366        5      2023    169
             Pennsylvania   40.271667        -76.247734        7      2023    166
                                                                             ... 
             Wyoming        44.811672        -108.800170       5      2023      1
                            44.659695        -106.948850       5      2023      1
                            44.510040        -109.146800       6      2023      1
                            44.462100        -110.853570       5      2023      1
                            44.438520        -109.217010       8      2023      1
Name: count, Length: 32661, dtype: int64
april_US = wf_US.loc[wf_US['month'] == 4]
april_US.value_counts()
countryCode  stateProvince   decimalLatitude  decimalLongitude  month  year
US           Illinois        37.312748        -89.018160        4      2023    11
             Texas           29.698408        -93.948180        4      2023    11
             New Mexico      33.802000        -106.880000       4      2023     6
                             35.130000        -106.683000       4      2023     4
             Texas           26.140090        -97.174820        4      2023     4
             Kentucky        38.059917        -84.508550        4      2023     3
             Texas           27.791317        -97.399180        4      2023     3
                             29.820465        -99.587930        4      2023     3
                             26.233800        -97.364300        4      2023     3
                             27.625025        -97.219790        4      2023     2
                             26.098804        -97.167860        4      2023     2
             Arizona         34.827730        -111.803630       4      2023     2
             Texas           29.600145        -94.526610        4      2023     2
                             26.149363        -97.172190        4      2023     2
             Arizona         33.730087        -111.511020       4      2023     1
             New Mexico      33.325077        -107.189125       4      2023     1
             Arkansas        35.278620        -93.263680        4      2023     1
             Alabama         30.250070        -88.082910        4      2023     1
             South Dakota    43.879400        -97.154140        4      2023     1
             North Carolina  36.167034        -81.509890        4      2023     1
             Oregon          43.265370        -118.843310       4      2023     1
             Ohio            39.624250        -82.910810        4      2023     1
                             39.271570        -81.826190        4      2023     1
             Texas           27.626500        -97.220436        4      2023     1
                             27.624062        -97.219980        4      2023     1
             North Carolina  35.245250        -82.715096        4      2023     1
             Texas           27.827530        -97.078960        4      2023     1
                             28.009558        -97.060326        4      2023     1
                             28.240393        -96.818820        4      2023     1
                             27.893673        -97.327220        4      2023     1
                             27.884823        -97.191480        4      2023     1
                             29.302603        -100.416595       4      2023     1
                             29.216900        -94.934900        4      2023     1
                             29.614280        -94.538720        4      2023     1
                             29.613834        -94.545235        4      2023     1
                             29.815508        -99.576256        4      2023     1
                             29.906994        -97.895510        4      2023     1
                             30.076506        -95.568390        4      2023     1
                             30.545515        -97.660820        4      2023     1
Name: count, dtype: int64
may_US = wf_US.loc[wf_US['month'] == 5]
may_US.value_counts()
countryCode  stateProvince  decimalLatitude  decimalLongitude  month  year
US           Pennsylvania   39.889350        -75.260150        5      2023    227
             Illinois       41.963383        -87.634420        5      2023    177
             Ohio           41.451912        -82.667366        5      2023    169
             Massachusetts  42.763900        -70.802300        5      2023    132
             Ohio           41.627710        -83.191890        5      2023    132
                                                                             ... 
             Wisconsin      45.369354        -86.911354        5      2023      1
                            45.302402        -92.665980        5      2023      1
                            45.239445        -92.742970        5      2023      1
             Arizona        31.769333        -110.887330       5      2023      1
                            31.612831        -111.040830       5      2023      1
Name: count, Length: 9415, dtype: int64

Convert the GBIF data to a GeoDataFrame

"""
Coordinate reference system:

EPSG:4326, also known as the WGS84 projection, is a coordinate system that represents the Earth 
as a 3D ellipsoid.
"""

gdf_monthly = (
    gpd.GeoDataFrame(
        gbif_df_2023, 
        geometry=gpd.points_from_xy(
            gbif_df_2023.decimalLongitude, 
            gbif_df_2023.decimalLatitude), 
        crs="EPSG:4326")
    # Select the desired columns
    [['month', 'geometry']]
)

Download and save ecoregion boundaries

Ecoregions represent boundaries formed by biotic and abiotic conditions: geology, landforms, soils, vegetation, land use, wildlife, climate, and hydrology.

# Set up the ecoregion boundary URL
ecoregions_url = "https://storage.googleapis.com/teow2016/Ecoregions2017.zip"

# Set up a path to save the data on your machine
ecoregions_dir = os.path.join(data_dir, 'wwf_ecoregions')

# Make the ecoregions directory
os.makedirs(ecoregions_dir, exist_ok=True)

# Join ecoregions shapefile path
ecoregions_path = os.path.join(ecoregions_dir, 'wwf_ecoregions.shp')

# Only download once
if not os.path.exists(ecoregions_path):
    ecoregions_gdf = gpd.read_file(ecoregions_url)
    ecoregions_gdf.to_file(ecoregions_path)
# Open up the ecoregions boundaries
ecoregions_gdf = (
    gpd.read_file(ecoregions_path)
    .rename(columns={
        'ECO_NAME': 'name',
        'SHAPE_AREA': 'area'})
    [['name', 'area', 'geometry']]
)

# Name the index so it will match the other data later on
ecoregions_gdf.index.name = 'ecoregion'

Identify the ecoregion for each observation

gbif_ecoregion_gdf = (
    ecoregions_gdf
    # Match the coordinate reference system of the GBIF data and the ecoregions
    # transform geometries to a new coordinate reference system
    .to_crs(gdf_monthly.crs)
    # Find ecoregion for each observation
    # spatial join
    .sjoin(
        gdf_monthly,
        how='inner', 
        predicate='contains')
    # Select the required columns
    [['month', 'name']]
)

Count the observations in each ecoregion each month

def get_monthly_regional_observations(df, region_type, occurrence_name):

    occurrence_df = (
        df
        # For each region, for each month...
        .groupby([region_type, 'month'])
        # count the number of occurrences
        .agg(occurrences=(occurrence_name, 'count'))
    )

    # Get rid of rare observations (possible misidentification)
    occurrence_df = occurrence_df[occurrence_df["occurrences"] > 1]

    # Take the mean by region
    mean_occurrences_by_region = (
        occurrence_df
        .groupby([region_type])
        .mean()
    )

    # Take the mean by month
    mean_occurrences_by_month = (
        occurrence_df
        .groupby(['month'])
        .mean()
    )

    # Normalize by space and time for sampling effort
    # This accounts for the number of active observers in each location and time of year
    occurrence_df['norm_occurrences'] = (
        occurrence_df
        / mean_occurrences_by_region
        / mean_occurrences_by_month
    )

    return occurrence_df
occurrence_df = get_monthly_regional_observations(gbif_ecoregion_gdf, 'ecoregion', 'name')

Create a simplified GeoDataFrame for plot

"""
Streamlining plotting with hvplot by simplifying the geometry, projecting it to a Mercator projection 
that is compatible with geoviews, and cropping off areas in the Arctic.
"""

# Speed up processing
ecoregions_gdf.geometry = ecoregions_gdf.simplify(
    .1, preserve_topology=False)

# Change the CRS to Mercator for mapping
ecoregions_gdf = ecoregions_gdf.to_crs(ccrs.Mercator())

Mapping monthly distribution

# Join the occurrences with the plotting GeoDataFrame
occurrence_gdf = ecoregions_gdf.join(occurrence_df)

# Get the plot bounds so they don't change with the slider
xmin, ymin, xmax, ymax = occurrence_gdf.total_bounds

# Define the slider widget
slider = pn.widgets.DiscreteSlider(
    name='month', 
    options={calendar.month_name[i]: i for i in range(1, 13)}
)

# Plot occurrence by ecoregion and month
migration_plot = (
    occurrence_gdf
    .hvplot(
        c='norm_occurrences',
        groupby='month',
        # Use background tiles
        title='Willow Flycatcher Migration',
        geo=True, crs=ccrs.Mercator(), tiles='CartoLight',
        xlim=(xmin, xmax), ylim=(ymin, ymax),
        frame_height=600,
        colorbar=False,
        widgets={'month': slider},
        widget_location='bottom',
        width=500,
        height=500
    )
)

# Save the plot
migration_plot.save('willow-flycatcher-migration.html', embed=True)

April Observations

occurrence_gdf_complete = occurrence_gdf.reset_index()

april_occ = occurrence_gdf_complete.loc[occurrence_gdf_complete['month'] == 4].sort_values(by=['norm_occurrences'], ascending=False)

april_occ_top_5 = april_occ[0:5]
april_occ_bottom_5 = april_occ[-5:]
# Top Five Ecoregions

april_occ_top_5
ecoregion month name area geometry occurrences norm_occurrences
277 414 4 Maracaibo dry forests 2.481389 POLYGON ((-8048949.511 1231627.426, -8041387.6... 20 0.285152
417 630 4 Sinaloan dry forests 6.884548 MULTIPOLYGON (((-12067130.607 3264698.826, -12... 9 0.207284
208 284 4 Guajira-Barranquilla xeric scrub 2.604593 MULTIPOLYGON (((-7970838.065 1386794.59, -7930... 12 0.164981
38 62 4 Balsas dry forests 5.324418 MULTIPOLYGON (((-10833561.03 2082462.339, -107... 5 0.160398
271 405 4 Magdalena Valley montane forests 8.540413 POLYGON ((-8307012.68 589985.897, -8317744.887... 17 0.148733
# Bottom Five Ecoregions

april_occ_bottom_5
ecoregion month name area geometry occurrences norm_occurrences
148 172 4 Colorado Plateau shrublands 28.732790 POLYGON ((-11987948.079 4867927.193, -12010342... 4 0.005992
141 162 4 Chihuahuan desert 46.807295 MULTIPOLYGON (((-12343440.455 3790837.437, -12... 7 0.004905
214 334 4 Interior Plateau US Hardwood Forests 12.513661 MULTIPOLYGON (((-9828084.583 4511412.866, -980... 3 0.004301
121 138 4 Central US forest-grasslands transition 24.173692 POLYGON ((-9846707.397 4459166.075, -9846156.6... 11 0.001111
9 32 4 Appalachian-Blue Ridge forests 16.637804 POLYGON ((-9634407.167 3977927.22, -9596388.67... 2 0.000405

May Observations

may_occ = occurrence_gdf_complete.loc[occurrence_gdf_complete['month'] == 5].sort_values(by=['norm_occurrences'], ascending=False)

may_occ_top_5 = may_occ[0:5]
may_occ_bottom_5 = may_occ[-5:]
# Top Five Ecoregions

may_occ_top_5
ecoregion month name area geometry occurrences norm_occurrences
215 334 5 Interior Plateau US Hardwood Forests 12.513661 MULTIPOLYGON (((-9828084.583 4511412.866, -980... 252 0.008285
189 242 5 Edwards Plateau savanna 7.051608 MULTIPOLYGON (((-11164874.082 3708334.248, -11... 51 0.007983
10 32 5 Appalachian-Blue Ridge forests 16.637804 POLYGON ((-9634407.167 3977927.22, -9596388.67... 1686 0.007825
122 138 5 Central US forest-grasslands transition 24.173692 POLYGON ((-9846707.397 4459166.075, -9846156.6... 3175 0.007354
446 674 5 Southern Great Lakes forests 24.575679 POLYGON ((-9602909.232 5460174.351, -9585160.8... 6865 0.007281
# Bottom Five Ecoregions

may_occ_bottom_5
ecoregion month name area geometry occurrences norm_occurrences
463 681 5 Southern Pacific dry forests 3.581219 POLYGON ((-11299123.63 2030599.485, -11280169.... 2 0.000669
299 446 5 Montana Valley and Foothill grasslands 11.775151 MULTIPOLYGON (((-12035828.537 5708472.595, -12... 50 0.000656
180 228 5 Eastern Canadian Forest-Boreal transition 37.723227 POLYGON ((-8271700.066 6325820.63, -8209132.17... 3 0.000646
435 643 5 South Central Rockies forests 20.091093 MULTIPOLYGON (((-11576546.818 5532640.689, -11... 34 0.000631
272 405 5 Magdalena Valley montane forests 8.540413 POLYGON ((-8307012.68 589985.897, -8317744.887... 3 0.000602

Species Distribution

The Willow Flycatcher Migration plot demonstrates changes in Willow Flycatcher distribution as the result of migration patterns across a single year (2023). Timing in migration reflects the annual cycles of breeding and wintering. Typically this species will journey between 2,000 to over 5,000 miles per year in keeping with these cycles. Dates of migration (arrival and departure) vary depending on the latitude. In the early 1990s, spring arrivals were indicated to be near 30-35° North around late April and early May. At 46-50° North, the spring arrival window was between late May and mid-June. Fall departure dates were around late August and early October at 30-35° North whereas in the 46-50° North range the dates were between late August and late September.

The northern limit of the species distribution and as a consequence, migration dates, are likely to move in response to climate change. Between the late 1960s and early 2000s, the northern hemisphere spring maximum temperatures increased about 1°C and during that period a mean shift north in breeding range for the Willow Flycatcher was observed (135.44 ± 59.37 km). Moreover, global temperatures are continuing to increase; 2023 was approximately 1.36°C warmer than the preindustrial average (1850-1900). The 2023 migration plot illustrates a latitude realignment compared to the early 2000s in the distribution corresponding to temperature signals. Spring arrivals in April approached 45° North and fall departures in the 46-50° North range were closer to October. Within the breeding range, there were no observations for April 2023 in Canada. The earliest spring arrivals appeared in May with the greatest in Ontario about 6 degrees north of the 1990s latitude. Between 46 and 50 degrees north latitude, few observations were recorded in May at 46° North in the Eastern Canadian Forest-Boreal transition compared to the largest occurrences near the top of the range at 49° North within the British Columbia coastal conifer forests. April arrivals in the United States were most prominent near 37° North in the Interior Plateau US Hardwood Forests ecoregion. During May the lowest observations were recorded in the 30-35° North range (Arizona Mountains forests) and the highest occurrences surpassed that range at 39° North (Appalachian-Blue Ridge forests). The flycatcher distribution is strongly correlated with temperature and as evidenced by these observations, is likely expanding its northern limit and migration timing in response to climate change.

References

Afzal, P. (2024, January 4). Endangered Willow Flycatchers in San Diego are adapting to climate change. Cornell Lab of Ornithology. https://www.allaboutbirds.org/news/endangered-willow-flycatchers-in-san-diego-are-adapting-to-climate-change

Bird Genoscape Project. (n.d.). Willow Flycatcher. https://www.birdgenoscape.org/willow-flycatcher

Chamberlain, S. (2024). pygbif (Version 0.6.4) [Computer software]. GitHub. https://github.com/gbif/pygbif/releases/tag/v0.6.4

Cornell Lab of Ornithology. (n.d.). Willow Flycatcher life history. All About Birds. https://www.allaboutbirds.org/guide/Willow_Flycatcher/lifehistory

Dinerstein, E., Olson, D., Joshi, A., Vynne, C., Burgess, N. D., Wikramanayake, E., … Saleem, M. (2017). An ecoregion-based approach to protecting half the terrestrial realm. BioScience, 67(6), 534–545. https://doi.org/10.1093/biosci/bix014

Finch, D. M. & Stoleson, S. H. (2000). Status, ecology, and conservation of the southwestern willow flycatcher. U.S. Department of Agriculture, Forest Service, Rocky Mountain Research Station. https://doi.org/10.2737/RMRS-GTR-60

Hitch, A. & Leberg, P. (2007). Breeding distributions of North American bird species moving north as a result of climate change. Conservation Biology, 21(2), 534-539. https://doi.org/10.1111/j.1523-1739.2006.00609.x

Jordahl, K., Van den Bossche, J., Fleischmann, M., Wasserman, J., McBride, J., Gerard, J., … Leblanc, F. (2024). geopandas/geopandas: v1.0.1 (Version 1.0.1) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.12625316

Met Office. (2024). Cartopy: a cartographic python library with a Matplotlib interface (Version 0.24.1) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.13905945

NASA. (n.d.). Global temperature. https://climate.nasa.gov/vital-signs/global-temperature

National Audubon Society. (n.d.). Willow Flycatcher. Audubon. https://www.audubon.org/field-guide/bird/willow-flycatcher

Python Software Foundation. (2024). Python (Version 3.12.6) [Computer software]. https://docs.python.org/release/3.12.6

Rudiger, P., Hansen, S. H., Bednar, J. A., Steven, J., Liquet, M., Little, B., … Bampton, J. (2024). holoviz/geoviews: Version 1.13.0 (Version 1.13.0) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.13782761

Rudiger, P., Liquet, M., Signell, J., Hansen, S. H., Bednar, J. A., Madsen, M. S., … Hilton, T. W. (2024). holoviz/hvplot: Version 0.11.0 (Version 0.11.0) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.13851295

Rudiger, P., Hansen, S. H., Madsen, M. S., & Wu, J. (2024). holoviz/panel: Version 1.5.2 (Version 1.5.2) [Computer software]. GitHub. https://github.com/holoviz/panel/releases/tag/v1.5.2

Tahoe National Forest. (n.d.). Willow Flycatcher - introduction. U.S. Forest Service. https://www.fs.usda.gov/detail/tahoe/landmanagement/resourcemanagement/?cid=stelprdb5357314#:~:text=The%20scientific%20name%20for%20willow,t

The pandas development team. (2024). pandas-dev/pandas: Pandas (Version 2.2.2) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.3509134