GeoPandas Map Animation Is Not Animating

Viewed 45

I am trying to animate a GeoPandas map using dates (iterates through the day). I'm using MPL's FuncAnimation function and although there are no errors, there is no animation happening. Here is my code:

link = 'https://raw.githubusercontent.com/leakyMirror/map-of-europe/master/GeoJSON/europe.geojson'
europe = gpd.read_file(link)
europe_w_needed_countries = europe[europe.NAME.isin(train.country.unique())]

def create_gdf(date):
    a = new_train.query(f"date == '{date}'").groupby('country').sum().reset_index()
    b = europe_w_needed_countries[['LON', 'LAT', 'geometry']]
    b.index = a.index
    return gpd.GeoDataFrame(pd.concat([a,b], axis=1))

create_gdf('2017-01-01')
    country num_sold    LON LAT geometry
0   Belgium 3320    2.550   46.565  MULTIPOLYGON (((9.48583 42.61527, 9.49472 42.6...
1   France  2939    9.851   51.110  MULTIPOLYGON (((8.71026 47.69681, 8.67859 47.6...
2   Germany 3437    12.800  42.700  MULTIPOLYGON (((12.12778 47.00166, 12.13611 46...
3   Italy   2431    4.664   50.643  POLYGON ((4.30237 51.26318, 4.30968 51.26203, ...
4   Poland  1074    19.401  52.125  POLYGON ((18.85125 49.51736, 18.85222 49.52777...
5   Spain   2151    -3.649  40.227  MULTIPOLYGON (((-2.92528 35.26666, -2.93694 35...

For more context of this dataset, a is supposed to represent the amount of books sold in European countries from 2017 to 2020. The data is aggregated. b is a geojson-loaded Data Frame that displays the polygons of the countries. Both datasets are merged together, which is seen in the output code.

This is my code for the animation plot:

fig, ax = plt.subplots(figsize=(15,8))
date = '2017-01-01'
fontsize = 13

def animate(date):
    gdf = create_gdf(date)
    gdf.plot(ax=ax, 
             column='num_sold', 
             cmap='OrRd', 
             edgecolor='black', 
             legend=True, 
             legend_kwds={'label': 'Books Sold'})
    ax.set_title('Books Sold From 1/1/2017 - 12/31/2020')
    ax.set_axis_off()
    ax.text(20, 45, 'Date:\n{}'.format(date), fontsize=fontsize, horizontalalignment='center')
    
    for lon, lat, country in zip(gdf.LON, gdf.LAT, gdf.country):
        ax.text(lon - 1.5, lat, country, fontsize=fontsize)
    date += TimeDelta(1)
    
FuncAnimation(fig, update, interval=250, frames=new_train.index.unique())

As seen here, the map does not change. Also, I know the country names are in the wrong places, I'll change that later.

enter image description here

I don't know why this is happening, I followed other threads and articles and it never works.

2 Answers

Animation of geopandas can be achieved with plot_polygon_collection(). I modified it to fit your code with this information. one difference from your image is that I added a string annotation to the graph area, but I could not delete the string in gdf.plotting, so I changed the title to start date and end date for equivalent functionality. Also, I couldn't add a color bar in the collection, so I added a new one. (I referenced it here) User data was created as a sample in a similar format.

import geopandas as gpd

link = 'https://raw.githubusercontent.com/leakyMirror/map-of-europe/master/GeoJSON/europe.geojson'
europe = gpd.read_file(link)
countries = ['Spain','France','Italy','Belgium','Germany','Poland']
europe_w_needed_countries = europe[europe.NAME.isin(countries)]

import pandas as pd
import numpy as np

df = pd.DataFrame({'date': np.tile(pd.date_range('2020-08-01', periods=30), 6),
                   'country': np.tile(countries, 30),
                   'num_sold': np.random.randint(1000,5000,180)})

df['date'] = pd.to_datetime(df['date']).dt.date

import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from datetime import datetime

fig, ax = plt.subplots()
fontsize = 12

ims = []
t = []
dates = df['date'].unique()
vmin, vmax = df['num_sold'].min(),df['num_sold'].min()

def update_fig(i):
    if len(ims) > 0:
        del ims[0]
    geos = europe_w_needed_countries['geometry']
    num_sold = df[df['date'] == dates[i]]['num_sold'].tolist()
    artist = gpd.plotting.plot_polygon_collection(ax, geos, num_sold, cmap="Reds")
    ims.append(artist)
    #ax.text(20, 45, 'Date:\n{}'.format(dates[i]), fontsize=fontsize, horizontalalignment='center')
    for lon, lat, country in zip(europe_w_needed_countries.LON, europe_w_needed_countries.LAT, europe_w_needed_countries.NAME):
        ax.text(lon - 1.5, lat, country, fontsize=fontsize)
    ax.set_title('Books Sold From {} - {}'.format(dates[0], dates[i]))
    ax.set_axis_off()
    fig = ax.get_figure()
    cax = fig.add_axes([0.9, 0.1, 0.03, 0.8])
    sm = plt.cm.ScalarMappable(cmap='Reds', norm=plt.Normalize(vmin=vmin, vmax=vmax))
    # fake up the array of the scalar mappable. Urgh...
    sm._A = []
    fig.colorbar(sm, cax=cax)
    return ims

anim = FuncAnimation(fig, update_fig, interval=1000, repeat_delay=300, frames=len(df['date'].unique()))

fig.show()

enter image description here

Alternative approach, use plotly express Animation is built into the high level API

import geopandas as gpd
import pandas as pd
import numpy as np
import plotly.express as px

countries = ["Spain", "France", "Italy", "Belgium", "Germany", "Poland"]
europe = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres")).loc[
    lambda d: d["name"].isin(countries)
]

df = pd.DataFrame(
    {
        "date": np.tile(pd.date_range("2020-08-01", periods=30), 6),
        "country": np.repeat(countries, 30),
        "num_sold": np.random.randint(1000, 5000, 180),
    }
)

px.choropleth_mapbox(
    df.assign(date_str=lambda d: d["date"].dt.strftime("%Y-%b-%d")),
    geojson=europe.set_index("name")["geometry"].__geo_interface__,
    locations="country",
    color="num_sold",
    color_continuous_scale='OrRd',
    animation_frame="date_str",
).update_layout(
    mapbox={
        "style": "carto-positron",
        "zoom": 2.5,
        "center": {
            "lat": europe.unary_union.centroid.y,
            "lon": europe.unary_union.centroid.x,
        },
    },
    coloraxis={"cmin": df["num_sold"].min(), "cmax": df["num_sold"].max()},
    autosize=False,
    width=600,
)

enter image description here

FYI I generated animated GIF using answer I provided here: animated GIF

Related