Interactive visualizations I#

Interactive maps and visualizations are powerful tools that allow us to understand and interpret complex datasets visually. They enable users to explore and interact with data in a more intuitive and insightful way, making it easier to identify patterns, trends, and outliers that might not be immediately apparent in raw datasets.

Interactive maps and visualizations are essential tools in a cartographer’s toolkit, providing a bridge between complex datasets and users. By using the many available Python libraries for interactive visualization, we can create compelling, interactive visual narratives that communicate their findings effectively and engage their audience. This is an essential skill for a cartographer creating maps for the web or for various interactive platforms and devices.

Why Use Interactive Maps and Visualizations?#

  • Enhanced Engagement: Interactive elements encourage users to explore data further, fostering a deeper understanding and engagement with the content.

  • Improved Accessibility: They make complex data more accessible to a broader audience by translating numbers and figures into visual formats.

  • Dynamic Exploration: Users can filter, zoom, and manipulate the data dynamically, allowing for personalized data exploration experiences.

  • Real-time Data Representation: Interactive visualizations can be updated in real-time, providing the most current insights into evolving datasets.

Interactive maps and temporal data#

Interactive maps are also useful for presenting temporal data. When datasets contain time-related attributes—such as dates or timestamps—interactive maps can bring this dimension to life. They enable users to observe how spatial patterns evolve over time, offering insights into trends, seasonal variations, and temporal anomalies within the geographical context. Here are a few examples of how the temporal dimension can be used in an interactive visualization:

  • Animation Over Time: Interactive maps can incorporate time as an animation axis, allowing viewers to play through changes in the data over days, months, years, or any relevant time period. This dynamic representation makes it easier to understand the progression and regression of phenomena, such as the spread of wildfires, changes in air quality, or urban development patterns.

  • Time-based Filtering: They provide mechanisms to filter data by specific time frames, such as sliders or date pickers, enabling users to focus on particular periods of interest and observe how spatial distributions align with historical events or policies.

  • Layering Temporal Data: Interactive maps can layer data from different time periods, allowing for direct comparison and deeper analysis of how areas have changed over time. This can be crucial for understanding long-term environmental changes, demographic shifts, or the impact of infrastructure projects.

Libraries for Creating Interactive Maps and Visualizations#

Several libraries are available for creating interactive maps and visualizations, each with its unique features and capabilities. Here are a few commonly used libraries which we also use today in this tutorial:

  1. Plotly: Plotly is an advanced visualization library that supports a wide range of interactive chart types, such as scatter plots, line charts, bar charts, box plots, histograms, 3D charts, and geographical maps, among others. Plotly excels in creating interactive and web-ready plots that can be easily embedded in web applications or Jupyter notebooks. It offers a high level of interactivity, including zooming, panning, and updating data in real-time. Plotly’s API is well-designed, making it accessible to beginners while powerful enough to meet the needs of advanced users for custom and dynamic visualizations. Additionally, Plotly can be used in combination with Dash, a framework for building analytical web applications, to create highly interactive, data-driven applications.

  2. Folium: Folium is a powerful Python library that helps bridge the gap between Python and the Leaflet.js library to create interactive and attractive maps. It allows data scientists and developers to visualize geospatial data generated in Python using the intuitive capabilities of Leaflet.js. Folium supports various map styles, overlays, and features, including markers, polygons, popups, and more, enabling users to build sophisticated map-based visualizations directly from their Python environment. The library integrates seamlessly with pandas for data manipulation, making it an excellent tool for spatial data analysis and visualization. This library is more extensively described on AutoGIS course pages.

Mapping wild fires in Finland#

In this tutorial we will work with Wild dire data from NASA FIRMS. Using multiple libraries and visualization methods, we will create interactive maps to explore various aspects of data including the temporal dimension.

Let’s have a quick look at our data, we will work with a subset of data from Finland.

[1]:
from plotly.offline import init_notebook_mode
init_notebook_mode(connected=True)
[2]:
import pandas as pd
import plotly.express as px

# Load the data from the CSV file
data_path_csv = 'data/fire_data_for_map.csv'  # Update this path to your file's location
fire_data_csv = pd.read_csv(data_path_csv)

fire_data_csv.head()
[2]:
LATITUDE LONGITUDE ACQ_DATE
0 64.644554 24.424450 2012-01-27
1 64.646645 24.425182 2012-02-01
2 64.644073 24.425926 2012-02-07
3 64.645348 24.427469 2012-02-18
4 64.643326 24.428034 2012-02-18

Interactive plots with plotly#

As discussed previously during this course, maps are not the only way how geographic data can be visualized and explored. The Python library Plotly which we will later use to create interactive maps, can also create nice interactive plots for us. This can be enough depending on what you want to show from your data. But in our case, we will just try a few useful plots to explore our data prior to making our maps.

Let’s first begin by creating an interactive Histogram.

[3]:
# Create an interactive histogram to show the distribution of fire occurrences over time
fig = px.histogram(fire_data_csv,
                   x='ACQ_DATE',
                   title='Distribution of Fire Occurrences Over Time',
                   labels={'ACQ_DATE': 'Acquisition Date'},
                   nbins=120,  # Adjust the number of bins for better visualization
                   color_discrete_sequence=['indianred'])  # Color for the histogram bars

# Update layout for better visualization
fig.update_layout(
    xaxis_title='Date',
    yaxis_title='Fire Occurrences',
    bargap=0.2,  # Gap between bars
)

# Show the figure
fig.show()

Box plots, also known as box-and-whisker plots, are another excellent tool for statistical analysis and data visulization, particularly when you want to understand the distribution, central tendency, and variability of a dataset. They are useful in identifying outliers, comparing distributions across different categories, and visualizing the spread and skewness of the data. In our case with wild fires, the can be for example used to explore regional or temporal variations.

Let’s make a plot of how number of fires have histrically varied for each month across our dataset.

[4]:
# Convert the 'ACQ_DATE' column to datetime format for easier manipulation
fire_data_csv['ACQ_DATE'] = pd.to_datetime(fire_data_csv['ACQ_DATE'])

# Extract the year and month from the ACQ_DATE for grouping purposes
fire_data_csv['Year'] = fire_data_csv['ACQ_DATE'].dt.year
fire_data_csv['Month'] = fire_data_csv['ACQ_DATE'].dt.month

# Group by year and month to count occurrences
fire_data_grouped = fire_data_csv.groupby(['Year', 'Month']).size().reset_index(name='Occurrences')

# Create a combined Year-Month column for plotting
fire_data_grouped['Year-Month'] = fire_data_grouped['Year'].astype(str) + '-' + fire_data_grouped['Month'].astype(str).str.zfill(2)

# Create a box plot to visualize the distribution of fire occurrences by month across all years
fig = px.box(fire_data_grouped,
             x='Month',
             y='Occurrences',
             labels={'Month': 'Month', 'Occurrences': 'Number of Occurrences'},
             color_discrete_sequence=['brown'])  # Color for the box plot

# Update layout for better visualization
fig.update_layout(
    xaxis_title='Month',
    yaxis_title='Number of Fire Occurrences',
    xaxis=dict(tickmode='array', tickvals=list(range(1, 13)), ticktext=['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']),
    title={
        'text': "Distribution of Fire Occurrences by Month",
        'y':0.9,
        'x':0.5,
        'xanchor': 'center',
        'yanchor': 'top'
    }

)

# Show the figure
fig.show()

A time series line chart is a graphical representation that shows how a particular variable changes over time. It’s a powerful tool for visualizing trends, patterns, or fluctuations in data across a chronological sequence.

[5]:
import pandas as pd
import plotly.express as px

# Assuming you've already loaded your data into fire_data_csv
# Convert 'ACQ_DATE' to datetime if not already done
fire_data_csv['ACQ_DATE'] = pd.to_datetime(fire_data_csv['ACQ_DATE'])

# Aggregate fire occurrences by date
fire_data_daily = fire_data_csv.groupby('ACQ_DATE').size().reset_index(name='Occurrences')

# Create a time series line chart to visualize the trend of fire occurrences over time
fig = px.line(fire_data_daily,
              x='ACQ_DATE',
              y='Occurrences',
              title='Trend of Fire Occurrences Over Time',
              labels={'ACQ_DATE': 'Date', 'Occurrences': 'Number of Fire Occurrences'},
              color_discrete_sequence=['navy'])  # Color for the line

# Update layout for better visualization
fig.update_layout(
    xaxis_title='Date',
    yaxis_title='Number of Fire Occurrences'
)

# Show the figure
fig.show()

Mapping time: an interactive animated map#

In our dataset, we have columns with latitudes and longitudes for the locations where the fire has occurred. Plotly can directly work with these columns, so there is no need for any conversion or use of GeoPandas for a quick visualization of our data using Plotly.

In following code cell, we’re loading wildfire occurrence data using pandas, and then creating an interactive geographical map with Plotly Express. The map visualizes fire occurrences, marked in red, based on their latitude and longitude. Each point’s hover text shows the acquisition date. We adjust the map’s focus to center on Finland, providing a targeted view of fire occurrences within that region. The map’s title is also centered for a nicer presentation.

[6]:
import pandas as pd
import plotly.express as px

# Load the data
data_path = 'data/fire_data_for_map.csv'
fire_data = pd.read_csv(data_path)

# Create an interactive map using Plotly Express
fig = px.scatter_geo(fire_data,
                     lat='LATITUDE',
                     lon='LONGITUDE',
                     color_discrete_sequence=['red'],  # Fire occurrences in red
                     hover_name='ACQ_DATE',  # Show acquisition date on hover
                     projection="orthographic",  # Map projection
                     )

# Adjust the map to focus on Finland
fig.update_geos(
    center={"lat": 65, "lon": 26},  # Center the map over Finland
    lataxis_range=[59, 71],  # Latitude range for Finland
    lonaxis_range=[19, 32]   # Longitude range for Finland
)

# Center the title
fig.update_layout(
    title={
        'text': "Fire Occurrences Visualization",
        'y':0.9,
        'x':0.5,
        'xanchor': 'center',
        'yanchor': 'top'
    }
)

# Show the figure
fig.show()

This initial visualization offers a quick overview of the data, pinpointing fire occurrences with latitude and longitude coordinates. However, to fully grasp the temporal aspect of the data, a simple dot map falls short. Fortunately, Plotly enables us to create an interactive animated map to visualize the timing of occurrences.

In the following code cell, we: - make sure the acquisition date (ACQ_DATE) is in a readable date format. - Use Plotly Express to create an interactive map, highlighting fire occurrences in red. Each point’s hover text displays its acquisition date, providing immediate temporal context. - Specifically adjust the map’s focus and zoom to visualize Finland more closely, enhancing the geographical context of the data. - Incorporate an animation based on ACQ_DATE, allowing us to observe how fire occurrences have evolved over time. - Make layout adjustments to optimize the visualization’s interactivity, particularly the slider’s appearance and functionality, which controls the animation’s temporal navigation.

This approach enhances our spatial understanding of wildfire occurrences and also allows us to dynamically explore these events over time, offering a more comprehensive view of both where and when fires have occurred.

[7]:
fire_data['ACQ_DATE'] = pd.to_datetime(fire_data['ACQ_DATE']).dt.strftime('%Y-%m-%d')

# Specify coordinates for focusing on Finland (roughly centered)
finland_center = {"lat": 64.5, "lon": 25.7}

# Create an interactive map with adjustments for Finland
fig = px.scatter_geo(fire_data,
                     lat='LATITUDE',
                     lon='LONGITUDE',
                     color_discrete_sequence=['red'],  # Fire occurrences in red
                     hover_name='ACQ_DATE',  # Show acquisition date on hover
                     projection="natural earth",  # Map projection
                     title="Fire Occurrences Visualization Over Time",
                     animation_frame='ACQ_DATE',
                     width=800,  # Adjust width for a larger frame
                     height=600)  # Adjust height for a larger frame

# Adjust geo properties to zoom in on Finland
fig.update_geos(center=finland_center,
                projection_scale=5)  # Adjust scale to zoom

# More properties to adjust
#fig.update_geos(center=finland_center,
#                projection_scale=5,  # Adjust scale to zoom
#                showcoastlines=True, coastlinecolor="Black",
#                showland=True, landcolor="lightgray",
#                showocean=True, oceancolor="LightBlue",
#                showlakes=True, lakecolor="Blue",
#                showrivers=True, rivercolor="Blue")

# Adjust layout properties to modify slider appearance
fig.update_layout(
    sliders=[dict(
        currentvalue={"prefix": "Date: "},
        len=0.4,  # Adjust slider length (percentage of plot width)
        x=0.3,  # Adjust slider position (0 is left, 1 is right)
        y=-0.2,  # Adjust vertical position of the slider
    )],
        title={
        'text': "Fire Occurrences",
        'y':0.9,
        'x':0.5,
        'xanchor': 'center',
        'yanchor': 'top'
    }
)


# Show the figure
fig.show()

# Optionally, save the figure to an HTML file:
# fig.write_html("path_to_save/fire_occurrences_map_over_time_focused_on_finland.html")

Aggreagting our data per regions

There are numerous dots on this map, each signifying a fire occurrence. While this level of detail might be exactly what we need, depending on the map’s intended use, for many purposes, the map could become more understandable if we use aggregated data, such as by regions. This means instead of individual dots for each fire, we could show the number of fires per region within a certain timeframe. Let’s refine our data a bit for this. We’ll use a shapefile of Finland’s regions to perform a spatial join, which will assign a region to each fire occurrence point. Then, we’ll aggregate these points to prepare our data for a more region-focused visualization.

[8]:
import geopandas as gpd

# Load the shapefile
shapefile_path = 'data/maakunnat_2024_milj.shp'
finland_regions = gpd.read_file(shapefile_path)

# Let's make a quick plot
finland_regions.plot()
[8]:
<Axes: >
../../_images/notebooks_week4_interactive-vis-Python-I_16_1.png
[9]:
# Take to the right CRS to work with web maps (fire data also comes in this CRS)
finland_regions = finland_regions.to_crs("EPSG:4326")
# Convert to GeoJSON
finland_geojson=finland_regions.__geo_interface__

# Load fire incident data
fire_data = pd.read_csv('data/fire_data_for_map.csv')

# Convert 'ACQ_DATE' to datetime and extract year
fire_data['ACQ_DATE'] = pd.to_datetime(fire_data['ACQ_DATE'])
fire_data['year'] = fire_data['ACQ_DATE'].dt.year

# Convert fire data to GeoDataFrame
fire_data_gdf = gpd.GeoDataFrame(fire_data, geometry=gpd.points_from_xy(fire_data.LONGITUDE, fire_data.LATITUDE), crs="EPSG:4326")

# Spatial join fire data with Finland regions
joined_data = gpd.sjoin(fire_data_gdf, finland_regions, how="inner", op='intersects')

# Aggregate fire incident data by region and year
aggregated_data = joined_data.groupby(['NAMEFIN', 'year']).size().reset_index(name='incidents')
aggregated_data.to_csv("data/aggregate_year.csv")
aggregated_data.head()
/home/docs/checkouts/readthedocs.org/user_builds/cartogis/envs/lih/lib/python3.12/site-packages/IPython/core/interactiveshell.py:3517: FutureWarning:

The `op` parameter is deprecated and will be removed in a future release. Please use the `predicate` parameter instead.

[9]:
NAMEFIN year incidents
0 Etelä-Karjala 2012 39
1 Etelä-Karjala 2013 33
2 Etelä-Karjala 2014 21
3 Etelä-Karjala 2015 26
4 Etelä-Karjala 2016 36

Now let’s create our interactive map of fire incidents per region in Finland. For better data presentation of our data we reclassify value using Natural Breaks classification method. We use Mapclassify library for the classification. This library is extensively described on AutoGIS course pages.

[10]:
import mapclassify
classifier = mapclassify.NaturalBreaks.make(k=9)
aggregated_data["incidents_cls"] = aggregated_data[["incidents"]].apply(classifier)

min, max = aggregated_data["incidents_cls"].min(),  aggregated_data["incidents_cls"].max()
color_scale = [
    [0, 'yellow'],  # Start of the scale
    [1, 'red']      # End of the scale
]
# Generate animated map
fig = px.choropleth(data_frame=aggregated_data,
                    geojson=finland_geojson,
                    locations="NAMEFIN",  # Specify the column containing region names
                    featureidkey="properties.NAMEFIN",
                    color="incidents_cls",
                    animation_frame="year",
                    scope="europe",
                    height=600,
                    title="Fire Incidents in Finland Regions Over Time",
                    labels={'incidents':'Number of Incidents'},
                    color_continuous_scale=color_scale,
                    range_color=(min, max)
                   )

# Update map projection and centering on Finland
fig.update_geos(fitbounds="locations", visible=False)

fig.update_layout(
    margin={"r":0, "t":0, "l":0, "b":0},
    height=800,  # You can adjust the height and width to give more space to the map area
    sliders=[{'pad':{"t": 5}}]  # Adjust the slider's position to be less intrusive
)
fig.show()