In this exercise we will explore hvplot some more which we will build on in Exercise 4 to create a custom linked visualization.
We will be building a new visualization based on the same data we have cleaned and filtered in the rest of the tutorial. First we load the DataFrame
of the >=7
earthquakes:
import xarray as xr
import dask.dataframe as dd
import holoviews as hv
from holoviews import streams # noqa
import hvplot.dask # noqa
import hvplot.pandas # noqa
import hvplot.xarray # noqa: adds hvplot method to xarray objects
df = dd.read_parquet('../../data/earthquakes.parq')
cleaned_df = df.copy()
cleaned_df['mag'] = df.mag.where(df.mag > 0)
cleaned_reindexed_df = cleaned_df.set_index(cleaned_df.time)
cleaned_reindexed_df = cleaned_reindexed_df.persist()
most_severe = cleaned_reindexed_df[cleaned_reindexed_df.mag >= 7].compute()
And next we load the population density raster data:
ds = xr.open_dataarray('../../data/gpw_v4_population_density_rev11_2010_2pt5_min.nc')
cleaned_ds = ds.where(ds.values != ds.nodatavals).sel(band=1)
cleaned_ds.name = 'population'
Start by using hvplot.image
to visualize the whole of the population density data using the Datashader support in hvPlot. Grab a handle on this Image
HoloViews object called pop_density
and customize it using the .opts
method, enabling a logarithmic color scale and a blue colormap (specifically the 'Blues'
colormap). At the end of the cell, display this object.
Hint
Don't forget to include rasterize=True
in the hvplot.image
call. You can use logz=True
and cmap='Blues'
in the .opts
method call
pop_density = ... # Use hvplot here to visualize the data in cleaned_ds and customize it with .opts
pop_density # Display it
pop_density = cleaned_ds.hvplot.image(rasterize=True).opts(logz=True, cmap='Blues')
pop_density
Now visualize the tabular data in most_severe
by building a hv.Points
object directly. This will be very similar to the approach shown in the tutorial but this time we want all the earthquakes to be marked with red crosses of size 100 (no need to use the .opts
method this time). As above, get a handle on this object and display it where the handle is now called quake_points
:
Hint
Don't forget to map the longitude and latitude dimensions to x
and y
in the call to hvplot.points
.
quake_points = ... # Use hvplot here to visualize the data in most_severe and customize it with .opts
quake_points
quake_points = most_severe.hvplot.points(x='longitude', y='latitude', marker='+', size=100, color='red')
quake_points
box_select
tool¶Now use .opts
method to enable the Bokeh box_select
tool on quake points.
Hint
The option is called tools
and takes a list of tool names, in this case 'box_select'
.
quake_points.opts(tools=['tap'])
Now overlay quake_points
over pop_density
to check everything looks correct. Make sure the box select tool is working as expected.
pop_density * quake_points
Using Selection1D
, define a HoloViews stream called selection_stream
using quake_points
as a source.
selection_stream = ...
selection_stream = streams.Selection1D(source=quake_points)
Now we want to create a circle around all the selected points chosen by the box select tool where each circle is centered at the latitude and longitude of a selected earthquake (10^o
in diameter). A hv.Ellipse
object can create a circle using the format hv.Ellipse(x, y, diameter)
and we can build an overlay of circles from a list of circles
using hv.Overlay(circles)
. Using this information, complete the following callback for the circle_marker
DynamicMap
.
Hint
Each circle
needs to be a hv.Ellipse(longitude, latitude, 10)
where longitude and latitude correspond to the current earthquake row.
def circles_callback(indices):
circles = []
if len(indices) == 0:
return hv.Overlay([])
for index in indices:
row = most_severe.iloc[index] # noqa
circle = ... # Define the appropriate Ellipse element here
circles.append(circle)
return hv.Overlay(circles)
# Uncomment when the above function is complete
# circle_marker = hv.DynamicMap(circles_callback, streams=[selection_stream])
def circles_callback(indices):
circles = []
if len(indices) == 0:
return hv.Overlay([])
for index in indices:
row = most_severe.iloc[index]
circle = hv.Ellipse(row.longitude, row.latitude, 10)
circles.append(circle)
return hv.Overlay(circles)
circle_marker = hv.DynamicMap(circles_callback, streams=[selection_stream])
Now test this works by overlaying pop_density
, quake_points
, and circle_marker
together.
pop_density * quake_points * circle_marker
Now let us generate a scatter plot of depth against magnitude for the selected earthquakes. Define a DynamicMap
called depth_magnitude_scatter
that uses a callback called depth_magnitude_callback
. This is a two-line function that returns a Scatter
element generated by .hvplot.scatter
.
Hint
The index
argument of the callback can be passed straight to most_severe.iloc
to get a filtered dataframe corresponding to the selected earthquakes.
def depth_magnitude_callback(index):
selected = most_severe.iloc[index]
return selected.hvplot.scatter(x='mag', y='depth')
depth_magnitude_scatter = hv.DynamicMap(depth_magnitude_callback, streams=[selection_stream])
Now overlay pop_density
, quake_points
and circle_marker
and put this in a one column layout together with the linked plot, depth_magnitude_scatter
.
((pop_density * quake_points * circle_marker) + depth_magnitude_scatter).cols(1)
After loading the data, the following (slightly cleaned up) version generates the whole visualization:
pop_density = cleaned_ds.hvplot.image(rasterize=True).opts(logz=True, cmap='Blues')
quake_points = most_severe.hvplot.points(x='longitude', y='latitude', marker='+', size=100, color='red')
quake_points.opts(tools=['box_select'])
selection_stream = streams.Selection1D(source=quake_points)
def circles_callback(index):
circles = []
if len(index) == 0:
return hv.Overlay([])
for ind in index:
row = most_severe.iloc[ind]
circle = hv.Ellipse(row.longitude, row.latitude, 10) # Define the appropriate Ellipse element here
circles.append(circle)
return hv.Overlay(circles)
circle_marker = hv.DynamicMap(circles_callback, streams=[selection_stream])
def depth_magnitude_callback(index):
selected = most_severe.iloc[index]
return selected.hvplot.scatter(x='mag', y='depth')
depth_magnitude_scatter = hv.DynamicMap(depth_magnitude_callback, streams=[selection_stream])
((pop_density * quake_points * circle_marker) + depth_magnitude_scatter).cols(1)