Movement data in space and time provide critical information about the behaviour of objects studied, i.e. vehicles, humans, and animals.
Insights from spatial and temporal movements drive many human and ecological studies. These studies enable in-depth analysis and understanding of complex processes like an animal and human migration, forest fires and natural disasters as well as mobility and transportation.

Thanks to data science, artificial intelligence and tracking equipment scientists can now study, model, and predict birds. Also, they can understand their migration patterns, why they migrate, where do they migrate and a lot of other critical questions.
In this article, I perform an in-depth spatiotemporal analysis using a dataset from Movebank. The dataset consists tracks of 33 birds (Golden Eagles) over ten years in western North America.
Exploratory Data Analysis
We read the data with Pandas as usual.
df = pd.read_csv("goldenEagles_dataset.csv")
df.head()
The data consists of a timestamp, latitude and longitude columns as well as an individual local identifier for the bird.

Let us count out how many rows we have for each bird and plot them with Seaborn.
fig, ax = plt.subplots(1, figsize=(14,12))
sns.countplot(y='individual-local-identifier', data=df);

Most birds have more than 500 points, but we also have birds which have more than 1000 points as well. Birds with less 100 points can be due to death or technical issues with the tracker.
Let us create a map of the points in Plotly Express as simple as this one line of code.
px.scatter_mapbox(df, lat='location-lat', lon='location-long', zoom=2, width=900, height=800)
Plotly Express is a high-level API for Plotly. Some have even likened Plotly Express to what Seaborn is for Matplotlib. As we can see from the map below, Bird trajectories are mostly narrow corridors in Mexico and North America. We dig deeper into our analysis next, but for now, some outliers need our attention – Those points in the ocean and some others in the east are outliers.

The data set has outliers column from both algorithmically marked as well as manually. We could exclude based on latitude and longitude, but since we have marker outliers in the dataset, we filter out those marked outliers from our DataFrame.
# Filter out outliers
df= df[(df['algorithm-marked-outlier'].isnull())&(df['manually-marked-outlier'].isnull())]
# Plot scatter map with Plotly Express
px.scatter_mapbox(df, lat='location-lat', lon='location-long', zoom=2, width=900, height=800)
As shown below, this is how our plot looks like when we remove outliers.

Finally, for our Exploratory data analysis, we refer to a reference table accompanied by the dataset to get a feeling of additional attributes about these birds.
ref_df = pd.read_csv("reference_table.csv")
ref_df.head()
The following table shows some columns of the Reference table. We have some valuable columns here that can reveal characteristics of the bird, i.e. sex, origin place, life stage and deployment date.

Here are some compelling visualisations of this table.
fig, ax = plt.subplots(2,2, figsize=(18,12))
sns.countplot(ref_df['animal-sex'], ax= ax[0,0])
sns.countplot(ref_df['animal-life-stage'], ax= ax[0,1])
sns.countplot(ref_df['study-site'], ax= ax[1,0])
sns.countplot(ref_df['age'], ax= ax[1,1])
plt.tight_layout()
plt.show()
The figures below show the Countplots of certain features. Upper left indicates the distribution of female and male birds – Most birds happen to be a male. Top right is the age class or life stage of the animal at the beginning of the deployment. Lower left shows the original site of study while lower right is the age distribution of the birds

Furthermore, we can relate to the age of the bird during tracking to these Categorical features. For example, we can see the relationship between the study site and age.
sns.catplot(x="age", y='study-site', kind="boxen", orient="h", height=8, aspect=10/8, data=ref_df);

Now, that we have a better understanding of our dataset, let us jump into animations and analysing movement data. However, before that, we merge the reference table to our Dataframe.
df_merged = pd.merge(df, ref, left_on="individual-local-identifier", right_on="animal-id")
Where do these birds migrate? At What time?
Let us create some timestamps for our Dataframe.
df_merged['month'] = pd.DatetimeIndex(df_merged['timestamp']).month
df_merged['month_year'] = pd.to_datetime(df_merged['timestamp']).dt.to_period('M')
df_merged['quarter_year'] = pd.to_datetime(df_merged['timestamp']).dt.to_period('Q')
df_merged['quarter'] = df_merged['quarter_year'].astype(str).str[-2:]
df_merged['month_year_st'] = df_merged["month_year"].dt.strftime('%Y-%m')
The maps shown above are crowded and do not offer that much insight into where and when these migrate. Here is where animations come to play a crucial role.
Let us first animate the Birds for every month in the year (From 1999 to 2009).
px.scatter_mapbox(df,
lat='location-lat',
lon='location-long',
color= 'individual-local-identifier',
animation_frame="month_year",
zoom=2
).update(layout={"mapbox":{"style": "satellite-streets"}})
The following GIF shows the animation of each bird every month over the ten years of the dataset.

Better insights but not that much of fine-grained movement insight as this shows all months of the year sequentially in every year. However, you can see all the movements of the birds to and from Mexico, USA and Canada. We can also aggregate all data into quarters of the year and animate it to see if there are any patterns.
px.scatter_mapbox(df_merged,
lat='location-lat',
lon='location-long',
color= 'study-site',
animation_frame=str('quarter'),
zoom=2,
)
Also, here is the GIF for the quarter based animation.

This higher aggregate shows a better pattern. In Q1 almost all birds are inside the USA border except few in Canada. On the contrary Q3, none of the birds is in Mexico.
Conclusion
In this tutorial, we have seen how to gain insights by animating bird migration using Panda and Plotly libraries. The code for this article is available at Github.
To directly experiment with this tutorial, use Binder without any installations by following this link.