{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Imperative vs. declarative visualization\n", "\n", "|Imperative|Declarative|\n", "|----------|-----------|\n", "|Specify how something should be done. | Specify what should be done.|\n", "|Specification and execution intertwined. | Separates specification from execution. |\n", "E.g. \"Put a red circle here. Put a blue circle there.| E.g. \"Map `` to a position and `` to a color.\"|\n", "\n", "Matplotlib is an _imperative_ visualization API with minimal support for **interactivity**. \n", "\n", "_Declarative_ visualization describes plots in terms of **data** and **relationships**, rather than incidental details.\n", "\n", "*Note*: much of this part of the tutorial is adapted from https://www.youtube.com/watch?v=ms29ZPUKxbU" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Getting started... You will need to install **_Altair_**. We have been using `conda` as our package manager so this can be done as follows in your shell:\n", "\n", "```\n", "conda install -c conda-forge altair vega_datasets\n", "```" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from vega_datasets import data\n", "\n", "iris = data.iris()\n", "\n", "iris.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Matplotlib examples\n", "\n", "### First, grouping...\n", "\n", "Let's group by the species column in the dataframe and color the points by their species." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![Grouping in Matplotlib](https://raw.githubusercontent.com/UWDIRECT/UWDIRECT.github.io/master/Wi21_content/SEDS/grouping_in_matplotlib.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Second, faceting...\n", "\n", "Instead of plotting all of the species on the same plot, let's make separate plots for each species." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![Grouping in Matplotlib](https://raw.githubusercontent.com/UWDIRECT/UWDIRECT.github.io/master/Wi21_content/SEDS/faceting_in_matplotlib.png)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Declarative visualization, by example\n", "\n", "Let's show how this would be accomplished in a declarative language like Altair / Vega. First, a single plot showing the species as colors." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import altair as alt\n", "\n", "alt.Chart(iris).mark_point().encode(\n", " x='petalLength',\n", " y='sepalLength',\n", " color='species'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Now faceting...\n", "\n", "Show the species as facets using different plots." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import altair as alt\n", "\n", "alt.Chart(iris).mark_point().encode(\n", " x='petalLength',\n", " y='sepalLength',\n", " color='species',\n", " column='species'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### And interactivity?\n", "\n", "Yup. That's easy with the `.interactive()` method on the Chart object. **Notice how the column charts are linked through their interactivity.**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import altair as alt\n", "\n", "alt.Chart(iris).mark_point().encode(\n", " x='petalLength',\n", " y='sepalLength',\n", " color='species',\n", " column='species'\n", ").interactive()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Introduction to Altair\n", "\n", "Much of this tutorial is adapted from the Altair user guide: https://altair-viz.github.io\n", "\n", "The key idea is that you are declaring links between data columns and visual encoding channels, such as the x-axis, y-axis, color, etc. The rest of the plot details are handled automatically. Building on this declarative plotting idea, a surprising range of simple to sophisticated plots and visualizations can be created using a relatively concise grammar." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Import the necessary libraries to get started..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import altair as alt\n", "import numpy as np\n", "import pandas as pd" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's make sure the visualization is working in jupyter lab by using a canned dataset" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iris = data.iris()\n", "\n", "iris.head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alt.Chart(iris).mark_point().encode(\n", " x='petalLength',\n", " y='petalWidth',\n", " color='species'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's make a super a super simple data frame and create our first visualization." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df = pd.DataFrame({'a': list('CCCDDDEEE'),\n", " 'b': [2, 7, 4, 1, 2, 6, 8, 4, 7]})\n", "df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The Chart object is the basic object of Altair. If we pass it our dataframe ``df`` we can show a point. It doesn't mean anything, but we can do it anyway..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alt.Chart(df).mark_point()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With factors like column A, we can plot them on an axis, e.g. the x axis. Again, doesn't mean much." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alt.Chart(df).mark_point().encode(\n", " x='a',\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_BUT_ with when we encode both axes, we can get some sort of a sensible plot." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alt.Chart(df).mark_point().encode(\n", " x='a',\n", " y='b'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The type of the data in the \"b\" column is again automatically-inferred by altair, and this time is treated as a quantitative type (i.e. real-valued). Additionally, we see that grid lines and appropriate axis titles are automatically added as well." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Aggregation and other transformations\n", "\n", "To allow for more flexibility in how data are visualized, Altair has a built-in syntax for aggregation of data. For example, we can compute the average of all values by specifying this aggregate within the column identifier:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alt.Chart(df).mark_point().encode(\n", " x='a',\n", " y='average(b)'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Typically, aggregated values are not represented by point markings, but by bar markings. We can do this by replacing ``mark_point()`` with ``mark_bar()``:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alt.Chart(df).mark_bar().encode(\n", " x='a',\n", " y='average(b)'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Because the categorical feature is mapped to the x-axis, the result is a vertical bar chart. To get a horizontal bar chart, all we need is to swap the `x` and `y` keywords:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alt.Chart(df).mark_bar().encode(\n", " y='a',\n", " x='average(b)'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "----" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Side note:* Under the hood, the Altair represents the plot as a JSON object this is because it passes the visualization declarations to a tool called Vega. If you want to examine the JSON, you can, e.g." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "chart = alt.Chart(df).mark_bar().encode(\n", " x='a',\n", " y='average(b)',\n", ")\n", "print(chart.to_json())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "----" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Back to customizing your charts...\n", "\n", "Let's change the color of the bars and fix up the titling." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alt.Chart(df).mark_bar(color='firebrick').encode(\n", " alt.Y('a', title='category'),\n", " alt.X('average(b)', title='avg(b) by category')\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One really nice thing about Vega and Altair is that once you have a visualization that you want to persist outside of a notebook, you can save it to a web page (HTML)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "chart = alt.Chart(df).mark_bar(color='firebrick').encode(\n", " alt.Y('a', title='category'),\n", " alt.X('average(b)', title='avg(b) by category')\n", ")\n", "chart.save('chart.html')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Even cooler, you can export the plot to the online Vega editor by using the options on the top right in the three dot button. Then in the editor you can share the visualization using only a URL (if the data are small enough). **Demo**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "-----" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Let's make a time series plot using dates and some random data" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rand = np.random.RandomState(0)\n", "\n", "df = pd.DataFrame({'value': rand.randn(100).cumsum()},\n", " index=pd.date_range('2018', freq='D', periods=100))\n", "df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you would like the index to be available to the chart, you can explicitly turn it into a column using the reset_index() method of Pandas dataframes:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alt.Chart(df.reset_index()).mark_line().encode(\n", " x='index:T',\n", " y='value:Q'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "-----" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Encodings\n", "\n", "An _encoding_ in Altair is how you map properties of the data to visual properties in order to effectively communicate information. Encodings are most often expressed through the ```Chart.encode()``` method.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But first, let's look at a new dataset: cars. Like `iris` it is heavily used for demos in R and Python. The data set we are using here contains information about cars, their mileage, horsepower, acceleration, cylinders, country of origin. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cars = data.cars()\n", "\n", "cars.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A basic visualization that we have already seen as the test of does Altair work." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cars = data.cars()\n", "\n", "alt.Chart(cars).mark_point().encode(\n", " x='Horsepower',\n", " y='Miles_per_Gallon',\n", " color='Origin',\n", " shape='Origin'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### A complete list of the encodings is available here:\n", "\n", "https://altair-viz.github.io/user_guide/encoding.html#encoding-channels\n", "\n", "Like the iris dataset it is Let's plot the horsepower against the miles per gallon and color code using three different approaches the number of cylinders." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "base = alt.Chart(cars).mark_point().encode(\n", " x='Horsepower:Q',\n", " y='Miles_per_Gallon:Q',\n", ").properties(\n", " width=150,\n", " height=150\n", ")\n", "\n", "alt.vconcat(\n", " base.encode(color='Cylinders:Q').properties(title='quantitative'),\n", " base.encode(color='Cylinders:O').properties(title='ordinal'),\n", " base.encode(color='Cylinders:N').properties(title='nominal'),\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### The different types are explained below" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "| Data Type | Shorthand Code | Description |\n", "|-----------|----------------|-------------|\n", "quantitative | Q | a continuous real-valued quantity\n", "ordinal | O | a discrete ordered quantity\n", "nominal | N | a discrete unordered category\n", "temporal | T | a time or date value\n", "\n", "The type used for the data will affect the scales used and the characteristics of the mark. For example, here is the difference between a quantitative and ordinal scale for an column that contains integers specifying a year:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pop = data.population.url\n", "\n", "base = alt.Chart(pop).mark_bar().encode(\n", " alt.Y('mean(people):Q', title='total population')\n", ").properties(\n", " width=200,\n", " height=200\n", ")\n", "\n", "alt.hconcat(\n", " base.encode(x='year:Q').properties(title='year=quantitative'),\n", " base.encode(x='year:O').properties(title='year=ordinal')\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Altair is knowledgeable about database-style grouping and aggregation\n", "\n", "For example, building a histogram from a one-dimensional dataset involves splitting data based on the bin it falls in, aggregating the results within each bin using a count of the data, and then combining the results into a final figure." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alt.Chart(cars).mark_bar().encode(\n", " alt.X('Horsepower', bin=True),\n", " y='count()'\n", " # could also use alt.Y(aggregate='count', type='quantitative')\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Similarly, we can create a two-dimensional histogram using, for example, the size of points to indicate counts within the grid (sometimes called a “Bubble Plot”):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alt.Chart(cars).mark_point().encode(\n", " alt.X('Horsepower', bin=True),\n", " alt.Y('Miles_per_Gallon', bin=True),\n", " size='count()',\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There is no need, however, to limit aggregations to counts alone. For example, we could similarly create a plot where the color of each point represents the mean of a third quantity, such as acceleration:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alt.Chart(cars).mark_circle().encode(\n", " alt.X('Horsepower', bin=True),\n", " alt.Y('Miles_per_Gallon', bin=True),\n", " size='count()',\n", " color='average(Acceleration):Q'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### There are many aggregate functions that you can use\n", "\n", "Here is a table adapted from the Altair documention showing them." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|Aggregate|Description|Example|\n", "|---------|-----------|-------|\n", "|argmin|An input data object containing the minimum field value.|N/A|\n", "|argmax|An input data object containing the maximum field value.|N/A|\n", "|average|The mean (average) field value. Identical to mean.|Line Chart with Layered Aggregates|\n", "|count|The total count of data objects in the group.|Simple Heatmap|\n", "|distinct|The count of distinct field values.|N/A|\n", "|max|The maximum field value.|Box Plot with Min/Max Whiskers|\n", "|mean|The mean (average) field value.|Interactive Scatter Plot and Linked Layered Histogram|\n", "|median|The median field value|Box Plot with Min/Max Whiskers|\n", "|min|The minimum field value.|Box Plot with Min/Max Whiskers|\n", "|missing|The count of null or undefined field values.|N/A|\n", "|q1|The lower quartile boundary of values.|Box Plot with Min/Max Whiskers|\n", "|q3|The upper quartile boundary of values.|Box Plot with Min/Max Whiskers|\n", "|ci0|The lower boundary of the bootstrapped 95% confidence interval of the mean.|Sorted Error Bars showing Confidence Interval|\n", "|ci1|The upper boundary of the bootstrapped 95% confidence interval of the mean.|Sorted Error Bars showing Confidence Interval|\n", "|stderr|The standard error of the field values.|N/A|\n", "|stdev|The sample standard deviation of field values.|N/A|\n", "|stdevp|The population standard deviation of field values.|N/A|\n", "|sum|The sum of field values.|Streamgraph|\n", "|valid|The count of field values that are not null or undefined.|N/A|\n", "|values|??|N/A|\n", "|variance|The sample variance of field values.|N/A|\n", "|variancep|The population variance of field values.|N/A|" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "------\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Ordering marks in plots and legends\n", "\n", "The order option and Order channel can sort how marks are drawn on the chart.\n", "\n", "For stacked marks, this controls the order of components of the stack. Here, the elements of each bar are sorted alphabetically by the name of the nominal data in the color channel.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "barley = data.barley()\n", "\n", "alt.Chart(barley).mark_bar().encode(\n", " x='variety:N',\n", " y='sum(yield):Q',\n", " color='site:N',\n", " order=alt.Order(\"site\", sort=\"ascending\")\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alt.Chart(barley).mark_bar().encode(\n", " x='variety:N',\n", " y='sum(yield):Q',\n", " color='site:N',\n", " order=alt.Order(\"site\", sort=\"descending\")\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This concept applies also to other chart types like stacked area charts:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alt.Chart(barley).mark_area().encode(\n", " x='variety:N',\n", " y='sum(yield):Q',\n", " color='site:N',\n", " order=alt.Order(\"site\", sort=\"ascending\")\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For line marks, the ```order``` channel encodes the order in which data points are connected. This can be useful for creating a scatterplot that draws lines between the dots using a different field than the `x` and `y` axes." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "driving = data.driving()\n", "\n", "alt.Chart(driving).mark_line(point=True).encode(\n", " alt.X('miles', scale=alt.Scale(zero=False)),\n", " alt.Y('gas', scale=alt.Scale(zero=False)),\n", " order='year'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### We can also apply a color to the ordering. How would you do this?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Multiple plots....\n", "\n", "You can have multiple plots on a line by using the `|` operator. Here is an example showing how ordering impacts the readability of plots and uses the `'` operator:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "barley = data.barley()\n", "\n", "base = alt.Chart(barley).mark_bar().encode(\n", " y='mean(yield):Q',\n", " color=alt.Color('mean(yield):Q', legend=None)\n", ").properties(width=100, height=100)\n", "\n", "# Sort x in ascending order\n", "ascending = base.encode(\n", " alt.X(field='site', type='nominal', sort='ascending')\n", ").properties(\n", " title='Ascending'\n", ")\n", "\n", "# Sort x in descending order\n", "descending = base.encode(\n", " alt.X(field='site', type='nominal', sort='descending')\n", ").properties(\n", " title='Descending'\n", ")\n", "\n", "# Sort x in an explicitly-specified order\n", "explicit = base.encode(\n", " alt.X(field='site', type='nominal',\n", " sort=['Duluth', 'Grand Rapids', 'Morris',\n", " 'University Farm', 'Waseca', 'Crookston'])\n", ").properties(\n", " title='Explicit'\n", ")\n", "\n", "# Sort according to another field\n", "sortfield = base.encode(\n", " alt.X(field='site', type='nominal',\n", " sort=alt.EncodingSortField(field='yield', op='mean'))\n", ").properties(\n", " title='By Yield'\n", ")\n", "\n", "ascending | descending | explicit | sortfield" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "-----\n", "\n", "#### Making a box plot\n", "\n", "Take 5 minutes and figure out how to make a box plot out of these data:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df = pd.DataFrame({'a': list('CCCDDDEEE'),\n", " 'b': [2, 7, 4, 1, 2, 6, 8, 4, 7]})\n", "df" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "----\n", "\n", "#### Interactivity!\n", "\n", "Here we get to the bees knees: making your plots interactive easily with Altair.\n", "\n", "First let's start with the uninteresting HP vs MPG plot." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cars = data.cars.url\n", "\n", "alt.Chart(cars).mark_point().encode(\n", " x='Miles_per_Gallon:Q',\n", " y='Horsepower:Q',\n", " color='Origin:N'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice... Not interactive. Let's make it possible to select some points." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "brush = alt.selection_interval() # selection of type \"interval\"\n", "\n", "\n", "alt.Chart(cars).mark_point().encode(\n", " x='Miles_per_Gallon:Q',\n", " y='Horsepower:Q',\n", " color='Origin:N'\n", ").add_selection(\n", " brush\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "WOW. That is completely useless. Let's make the selection do something more interesting.\n", "\n", "In this case, we select an interval and color everything else light grey." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alt.Chart(cars).mark_point().encode(\n", " x='Miles_per_Gallon:Q',\n", " y='Horsepower:Q',\n", " color=alt.condition(brush, 'Origin:N', alt.value('lightgray'))\n", ").add_selection(\n", " brush\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_Better_ but still only highlighting. Let's show how two charts can be used in this way to show interesting relationships between variables." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "chart = alt.Chart(cars).mark_point().encode(\n", " y='Horsepower:Q',\n", " color=alt.condition(brush, 'Origin:N', alt.value('lightgray'))\n", ").properties(\n", " width=250,\n", " height=250\n", ").add_selection(\n", " brush\n", ")\n", "\n", "chart.encode(x='Acceleration:Q') | chart.encode(x='Miles_per_Gallon:Q')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*NEATO!*\n", "\n", "\n", "Instead of selection an `x` and `y` range, let's just enable selecting on the `x` axis." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "brush = alt.selection_interval(encodings=['x'])\n", "\n", "chart = alt.Chart(cars).mark_point().encode(\n", " y='Horsepower:Q',\n", " color=alt.condition(brush, 'Origin:N', alt.value('lightgray'))\n", ").properties(\n", " width=250,\n", " height=250\n", ").add_selection(\n", " brush\n", ")\n", "\n", "chart.encode(x='Acceleration:Q') | chart.encode(x='Miles_per_Gallon:Q')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Cool** but I want to investigate a single point and show a tool tip revealing information about that specific point. Example, find out about outliers.\n", "\n", "For any but the simplest selections, the user needs to think about exactly what is targeted by the selection, and this can be controlled with either the fields or encodings arguments. These control what data properties are used to determine which points are part of the selection.\n", "\n", "For example, here we create a small chart that acts as an interactive legend, by targeting the Origin field using fields=['Origin']. Clicking on points in the upper-left plot (the legend) will propagate a selection for all points with a matching Origin." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "selection = alt.selection_multi(fields=['Origin'])\n", "color = alt.condition(selection,\n", " alt.Color('Origin:N', legend=None),\n", " alt.value('lightgray'))\n", "\n", "scatter = alt.Chart(cars).mark_point().encode(\n", " x='Horsepower:Q',\n", " y='Miles_per_Gallon:Q',\n", " color=color,\n", " tooltip='Name:N'\n", ")\n", "\n", "legend = alt.Chart(cars).mark_point().encode(\n", " y=alt.Y('Origin:N', axis=alt.Axis(orient='right')),\n", " color=color\n", ").add_selection(\n", " selection\n", ")\n", "\n", "scatter | legend" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Now we are getting something useful** \n", "\n", "Let's add a dropdown box to select the country." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "input_dropdown = alt.binding_select(options=['Europe','Japan','USA'])\n", "selection = alt.selection_single(fields=['Origin'], bind=input_dropdown, name='Country of ')\n", "color = alt.condition(selection,\n", " alt.Color('Origin:N', legend=None),\n", " alt.value('lightgray'))\n", "\n", "alt.Chart(cars).mark_point().encode(\n", " x='Horsepower:Q',\n", " y='Miles_per_Gallon:Q',\n", " color=color,\n", " tooltip='Name:N'\n", ").add_selection(\n", " selection\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using Altair in Flask with React\n", "\n", "A group that runs data science hackweeks for the Electrochemical Society has put together a micro example of using the python web framework `flask` with `react` and `Altair` to embed interactive visualizations in your web apps using Python. Flask is a micro web framework written in Python. It is classified as a microframework because it does not require particular tools or libraries. React is a JavaScript library for building user interfaces build and maintained by Facebook. Altair you should know by now.\n", "\n", "The GitHub with the example code is here: https://github.com/ECSHackWeek/flask-altair-react-example along with instructions for setting up the environment and getting started with it." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## In class exercise\n", "\n", "Take 20 minutes and using your data from your class project or the HCEPDB data or some other non-trivial dataset and make an interactive visualization. At the end of this period we will share to the class some of the visualizations you have made with Altair." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.2" } }, "nbformat": 4, "nbformat_minor": 4 }