{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\"HoloViz\n", "

Exercises 3-4: Plotting

" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercise 3\n", "\n", "In this exercise we will explore hvplot some more which we will build on in Exercise 4 to create a custom linked visualization.\n", "\n", "#### Loading the data as before\n", "\n", "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:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np # noqa\n", "import xarray as xr\n", "import dask.dataframe as dd\n", "import holoviews as hv\n", "\n", "from holoviews import streams # noqa\n", "\n", "import hvplot.dask # noqa\n", "import hvplot.pandas # noqa\n", "import hvplot.xarray # noqa: adds hvplot method to xarray objects\n", "\n", "df = dd.read_parquet('../../data/earthquakes.parq')\n", "df.time = df.time.astype('datetime64[ns]')\n", "cleaned_df = df.copy()\n", "cleaned_df['mag'] = df.mag.where(df.mag > 0)\n", "cleaned_reindexed_df = cleaned_df.set_index(cleaned_df.time)\n", "cleaned_reindexed_df = cleaned_reindexed_df.persist()\n", "most_severe = cleaned_reindexed_df[cleaned_reindexed_df.mag >= 7].compute()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And next we load the population density raster data:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ds = xr.open_dataarray('../../data/gpw_v4_population_density_rev11_2010_2pt5_min.nc')\n", "cleaned_ds = ds.where(ds.values != ds.nodatavals).sel(band=1)\n", "cleaned_ds.name = 'population'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Visualizing the raster data\n", "\n", "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.\n", "\n", "
Hint
\n", "\n", "Don't forget to include `rasterize=True` in the `hvplot.image` call. You can use `logz=True`, `clim=(1, np.nan)` and `cmap='Blues'` in the `.opts` method call\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pop_density = ... # Use hvplot here to visualize the data in cleaned_ds and customize it with .opts\n", "pop_density # Display it" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
Solution
\n", "\n", "```python\n", "pop_density = cleaned_ds.hvplot.image(rasterize=True, logz=True, clim=(1, np.nan), cmap='Blues') \n", "pop_density\n", "```\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Visualizing the earthquake positions\n", "\n", "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`:\n", "\n", "
Hint
\n", "\n", "Don't forget to map the longitude and latitude dimensions to `x` and `y` in the call to `hvplot.points`.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "quake_points = ... # Use hvplot here to visualize the data in most_severe and customize it with .opts\n", "quake_points" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
Solution
\n", "\n", "```python\n", "quake_points = most_severe.hvplot.points(x='longitude', y='latitude', marker='+', size=100, color='red')\n", "quake_points \n", "```\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Enabling the `box_select` tool\n", "\n", "Now use `.opts` method to enable the Bokeh `box_select` tool on quake points.\n", "\n", "
Hint
\n", "\n", "The option is called `tools` and takes a list of tool names, in this case `'box_select'`.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
Solution
\n", "\n", "```python\n", "quake_points.opts(tools=['tap'])\n", "```\n", "\n", "
\n", "\n", "\n", "\n", "\n", "#### Composing these visualizations together\n", "\n", "Now overlay `quake_points` over `pop_density` to check everything looks correct. Make sure the box select tool is working as expected." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
Solution
\n", " \n", "```python\n", "pop_density * quake_points\n", "```\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercise 4\n", "\n", "Using `Selection1D`, define a HoloViews stream called `selection_stream` using `quake_points` as a source." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "selection_stream = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
Solution
\n", " \n", "```python\n", "selection_stream = streams.Selection1D(source=quake_points)\n", "```\n", "\n", "
\n", "\n", "#### Highlighting earthquakes with circles\n", "\n", "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`.\n", "\n", "
Hint
\n", "\n", "Each `circle` needs to be a `hv.Ellipse(longitude, latitude, 10)` where longitude and latitude correspond to the current earthquake row.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def circles_callback(index):\n", " circles = []\n", " if len(index) == 0:\n", " return hv.Overlay([])\n", " \n", " for i in index:\n", " row = most_severe.iloc[i] # noqa\n", " circle = ... # Define the appropriate Ellipse element here\n", " circles.append(circle)\n", " return hv.Overlay(circles)\n", "\n", "# Uncomment when the above function is complete\n", "# circle_marker = hv.DynamicMap(circles_callback, streams=[selection_stream])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
Solution
\n", "\n", "```python\n", "def circles_callback(index):\n", " circles = []\n", " if len(index) == 0:\n", " return hv.Overlay([])\n", " \n", " for i in index:\n", " row = most_severe.iloc[i]\n", " circle = hv.Ellipse(row.longitude, row.latitude, 10) \n", " circles.append(circle)\n", " \n", " return hv.Overlay(circles)\n", "\n", "circle_marker = hv.DynamicMap(circles_callback, streams=[selection_stream])\n", "```\n", "\n", "
\n", "\n", "\n", "Now test this works by overlaying `pop_density`, `quake_points`, and `circle_marker` together." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
Solution
\n", " \n", "```python\n", "pop_density * quake_points * circle_marker\n", "```\n", "
\n", "\n", "\n", "#### Depth and magnitude scatter of selected earthquakes\n", "\n", "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`.\n", "\n", "
Hint
\n", "\n", "The `index` argument of the callback can be passed straight to `most_severe.iloc` to get a filtered dataframe corresponding to the selected earthquakes.\n", "\n", "
\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
Solution
\n", "\n", "```python\n", "def depth_magnitude_callback(index):\n", " selected = most_severe.iloc[index]\n", " return selected.hvplot.scatter(x='mag', y='depth')\n", "\n", "depth_magnitude_scatter = hv.DynamicMap(depth_magnitude_callback, streams=[selection_stream])\n", "```\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Final visualization\n", "\n", "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`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
Solution
\n", "\n", "```python\n", "((pop_density * quake_points * circle_marker) + depth_magnitude_scatter).cols(1)\n", "```\n", "\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
Overall Solution
\n", "\n", "After loading the data, the following (slightly cleaned up) version generates the whole visualization:\n", "\n", "```python\n", "pop_density = cleaned_ds.hvplot.image(rasterize=True, logz=True, clim=(1, np.nan), cmap='Blues') \n", "quake_points = most_severe.hvplot.points(x='longitude', y='latitude', marker='+', size=100, color='red')\n", "quake_points.opts(tools=['box_select'])\n", "\n", "selection_stream = streams.Selection1D(source=quake_points)\n", "\n", "def circles_callback(index):\n", " circles = []\n", " if len(index) == 0:\n", " return hv.Overlay([])\n", " for i in index:\n", " row = most_severe.iloc[i]\n", " circle = hv.Ellipse(row.longitude, row.latitude, 10) # Define the appropriate Ellipse element here\n", " circles.append(circle)\n", " \n", " return hv.Overlay(circles)\n", "\n", "circle_marker = hv.DynamicMap(circles_callback, streams=[selection_stream])\n", "\n", "def depth_magnitude_callback(index):\n", " selected = most_severe.iloc[index]\n", " return selected.hvplot.scatter(x='mag', y='depth')\n", "\n", "depth_magnitude_scatter = hv.DynamicMap(depth_magnitude_callback, streams=[selection_stream])\n", "\n", "((pop_density * quake_points * circle_marker) + depth_magnitude_scatter).cols(1)\n", "```\n", "\n", "
" ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 4 }