import pandas as pd
import geopandas as gpd
import movingpandas as mpd
Barn Swallows are the most widespread species of swallow in the world, occurring on all continents. They prefer habitats in the open countryside with low vegetation such as pasture, meadows and farmland, with nearby water. The majority of barn swallows breed across the Northern Hemisphere in the summer, migrating to southern Africa and South America for the winter. In this post we’ll visualize the migration of Barn Swallows across the globe using Python, GeoPandas and MovingPandas, with data sourced from Movebank.
The first step is to download the relevant datasets from Movebank. We’ll use data from two studies to cover parts of Europe/Africa and North/South America, namely:
- Study 1
- Barn swallows breeding in Kraghede
- Data spans 2016-2017, where the northward migration is in 2017
- 4 individuals were tracked
- Study 2
- CLSW_Nebraska_2022
- Data spans 2021-2022, where the northward migration is in 2022
- 1 individual was tracked
The data can be downloaded as CSV files, which we’ll use below
= pd.read_csv('Barn swallows breeding in Kraghede.csv')
study_1 = pd.read_csv('CLSW_Nebraska_2022.csv')
study_2
def preprocess(df):
# Drop missing coordinates
=['location-lat', 'location-long'], inplace=True)
df.dropna(subset
# Convert timstamp to datetime
'timestamp'] = pd.to_datetime(df['timestamp'])
df[
# Convert to GeoDataFrame
= gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df['location-long'], df['location-lat']), crs='EPSG:4326')
gdf
return gdf
= preprocess(study_1)
study_1 = preprocess(study_2)
study_2
# Add migration direction
'timestamp'].dt.year.eq(2016), 'd'] = 'southward'
study_1.loc[study_1['timestamp'].dt.year.eq(2017), 'd'] = 'northward'
study_1.loc[study_1['timestamp'].dt.year.eq(2021), 'd'] = 'southward'
study_2.loc[study_2['timestamp'].dt.year.eq(2022), 'd'] = 'northward'
study_2.loc[study_2[= pd.concat([study_1, study_2]) gdf
Individual birds are identified by the ‘individual-local-identifier’ column. The dataframes have timestamps for each observation, with point coordinates in the ‘location-lat’ and ‘location-long’ columns. Let’s quickly check how many observations we have for each individual, per year
'study-name', 'individual-local-identifier', 'd']).size() gdf.groupby([
study-name individual-local-identifier d
Barn swallows breeding in Kraghede 9AD3712 northward 232
southward 220
9AK1947 northward 257
southward 185
9AK1973 northward 310
southward 219
9AP0465 northward 230
southward 191
CLSW_Nebraska_2022 2401-22580 northward 251
southward 310
dtype: int64
To plot the migration paths, we’ll need to create LineStrings from the point coordinates, which we can easily do with MovingPandas. We’ll split the data into two separate dataframes, one for the southward migration and one for the northward migration
= gdf.loc[gdf['d'].eq('southward')]
gdf_southward = gdf.loc[gdf['d'].eq('northward')] gdf_northward
Now we can generate the trajectories
= mpd.TrajectoryCollection(gdf_southward, 'individual-local-identifier', t='timestamp')
southward_traj = mpd.TrajectoryCollection(gdf_northward, 'individual-local-identifier', t='timestamp') northward_traj
With the trajectories, we can see the time taken and distance travelled by the birds in each direction
# The study in which individual birds belong
= gdf.groupby('individual-local-identifier')['study-name'].first().to_dict()
individual_study_map
= []
total for direction, traj, in zip(['southward', 'northward'], [southward_traj, northward_traj]):
= traj.to_traj_gdf()
_gdf 'direction'] = direction
_gdf['duration'] = _gdf['end_t'] - _gdf['start_t']
_gdf['km'] = _gdf['length'] / 1_000
_gdf['km/day'] = _gdf['km'] / _gdf['duration'].dt.days
_gdf[
total.append(_gdf)= pd.concat(total)
total 'study'] = total['individual-local-identifier'].map(individual_study_map) total[
'duration', 'km', 'km/day']].max(axis=0) total[[
duration 158 days 22:49:55
km 23144.292571
km/day 178.636679
dtype: object
The barn swallows in these studies flew up to ~23,100 km in ~158 days, an average of ~179 km/day
'study', 'direction'])[['duration', 'km', 'km/day']].mean().round(1) total.groupby([
duration | km | km/day | ||
---|---|---|---|---|
study | direction | |||
Barn swallows breeding in Kraghede | northward | 137 days 00:45:49 | 14688.1 | 106.6 |
southward | 108 days 21:29:45.750000 | 10282.7 | 94.9 | |
CLSW_Nebraska_2022 | northward | 125 days 03:06:10 | 22329.6 | 178.6 |
southward | 154 days 08:41:58 | 18345.9 | 119.1 |
On average, the birds flying from Northern Europe to Africa took less time to migrate South, but longer to migrate North compared to the one example from North America to South America. The average distance covered was also much lower for the Europe/Africa migration.
We can visualize these trajectories using the MovingPandas TrajectoryCollection
objects. On the map the starting points are shown as triangles, where the solid lines represent the southward migration and the dashed lines represent the northward migration
= dict(line_width=3, geo=True, tiles="EsriImagery", width=760, height=600)
params **params) * northward_traj.hvplot(**params, line_dash='dashed') southward_traj.hvplot(