Understanding The Visual Grammar of Data
Reference Implementation and Data Query Schema Specification for ML-Dash
We are typically specifying two things when we make a chart: the data, and the presentation. Figure 1 shows what this would look like as UI components, where the outer layer handles the data query, and the inner, stateless presentation layer specifies the type of visualization we want, and how it is presented stylistically. Take a simple plot of the training and validation loss -- there are two different metrics, and they are presented in the same XY time-series plot with two different colors. The former specifies what the data is and how we get it, and the latter specifies how they are visually presented.
The vuer-viz library is most concerned with the inner presentation layer. The data query layer usually couples strongly with and depends upon your choice of data transform and statistical backends. These design choices are best left to the end developer to make.
The purpose of this section is to provide a grammar that specifies both the data and the presentation. We do so by providing the specification itself, a collection of reference implementations, and a best practice guide.
Data Query Examples
We will focus on time-series data. This section is organized as below:
- Part 1 - Multiple Time Series from A Single Experiment
- Part 2 - Aggregation Across A Set of Experiments
- Part 3 - Comparison Across Multiple Set of Experiments
- Other Chart Types - Bar Charts, Image/Video Display, and More
For data hooks, refer to the next page in the guide: Data Hooks & Providers
Part 1: Presenting Time Series Data from A Single Experiment
interface SeriesDataProps { /* A specific experiment prefix */ prefix?: string; /* key for the x-axis, dot-separated path local to the experiment. */ xKey?: string; /* a single y key */ yKey?:string ; /* used to specify a list of y keys */ yKeys?: string[]; }
Reference Component:
import {getSeries} from "@vuer-ai/ml-dash"; import {useZoomPan, LineChart, VerticalMarker, SeriesTooltip} from "@vuer-ai/vuer-viz"; export default function Lines({ prefix, xMin, xMax, xKey, yKey, yMin, yMax, yKeys, xFormat, yFormat, markerX, /** interaction setters for the cursor position, and display range. */ setXMin, setXMax, setMarker, }) { // these are the data min and data max const series = getSeries({ prefix, xMin, xMax, xKey, yKey, yKeys }); const minLimit = series[0][xKey][0]; const maxLimit = series[0][xKey].slice(-1)[0]; const { plotRef } = useZoomPan(xMin, xMax, setXMin, setXMax, minLimit, maxLimit); return ( <LineChart plotRef={plotRef} series={series} width={770} canvas xMin={xMin} xMax={xMax} xFormat={xFormat} yFormat={yFormat} onChange={setMarker} > <VerticalMarker x={markerX} /> <SeriesTooltip x={markerX} series={series} xFormat={xFormat} yFormat={yFormat}/> </LineChart> ); }
Part 2: Aggregation Across A Set of Experiments
We typically average across multiple random seeds in deep reinforcement learning
experiments because the first generation of RL algorithms had high variance. We can
do so by adding a glob pattern in place of the experimental prefix, and an agg field
to specify how we aggregate statistics across multiple runs.
type Aggregation = | "mean" | "median" | "std" | "var" | "min" | "max" | "sum" | "count" | "Q05" | "Q10" | "Q25" | "Q50" | "Q75" | "Q90" | "Q95" | null
| Aggregation | Formula | Description |
|---|---|---|
"mean" | x̄ = (1/n)Σxᵢ | Arithmetic mean |
"median" | median(x₁, …, xₙ) | Middle value (50th percentile) |
"std" | σ = √[(1/n)Σ(xᵢ − x̄)²] | Standard deviation |
"var" | σ² = (1/n)Σ(xᵢ − x̄)² | Variance |
"min" | min(x₁, …, xₙ) | Minimum value |
"max" | max(x₁, …, xₙ) | Maximum value |
"sum" | Σxᵢ | Sum of all values |
"count" | n | Number of values |
"Q05" | Q₀.₀₅ | 5th percentile |
"Q10" | Q₀.₁₀ | 10th percentile |
"Q25" | Q₀.₂₅ | 25th percentile (lower quartile) |
"Q50" | Q₀.₅₀ | 50th percentile (median) |
"Q75" | Q₀.₇₅ | 75th percentile (upper quartile) |
"Q90" | Q₀.₉₀ | 90th percentile |
"Q95" | Q₀.₉₅ | 95th percentile |
null | — | No aggregation (use raw data) |
The rest of the query keys remain the same as the single experiment case.
Note: We treat
prefixas the current work directory (cwd), and run glob matching inside this directory context.
type Aggregation = "mean" | null; interface SeriesDataProps { /* can consider this as the current work directory (cwd) when running glob */ prefix?: string /* experiment prefix pattern containing glob patterns */ glob?: string; /* how to aggregate the time series found: average over them? */ agg?: Aggregation; /* key for the x-axis, dot-separated path local to the experiment. */ xKey?: string; /* a single y key */ yKey?:string ; /* used to specify a list of y keys */ yKeys?: string[]; }
Reference Component:
import {getSeries} from "@vuer-ai/ml-dash"; import {useZoomPan, LineChart, VerticalMarker, SeriesTooltip} from "@vuer-ai/vuer-viz"; export default function Lines({ prefix, glob, xMin, xMax, xKey, yKey, yMin, yMax, yKeys, xFormat, yFormat, markerX, /** interaction setters for the cursor position, and display range. */ setXMin, setXMax, setMarker, }) { // these are the data min and data max const series = getSeries({ prefix, glob, xMin, xMax, xKey, yKey, yKeys }); const minLimit = series[0][xKey][0]; const maxLimit = series[0][xKey].slice(-1)[0]; const { plotRef } = useZoomPan(xMin, xMax, setXMin, setXMax, minLimit, maxLimit); return ( <LineChart plotRef={plotRef} series={series} width={770} canvas xMin={xMin} xMax={xMax} xFormat={xFormat} yFormat={yFormat} onChange={setMarker} > <VerticalMarker x={markerX} /> <SeriesTooltip x={markerX} series={series} xFormat={xFormat} yFormat={yFormat}/> </LineChart> ); }
Part 3: Comparison Across Multiple Set of Experiments
This is best illustrated via the following simple example: Suppose we want to reproduce the no-target network experiment from my random fourier feature paper: "Overcoming the Spectral Bias of Neural Value Approximation".
We will specify the title, x-axis format, and the range as global attributes to the chart. Then, we specify the data query parameters for two separate set of experiments, averaged and summarized across multiple random seeds.
# Schema for a Comparison Plot title: string = "With vs w/o Target Network" xFormat: Formatter = 'auto' yMin: number = -10 yMax: number = 1_000 series: - prefix: string = "model-free/iclr-2021/analysis" glob: string = "ffn/3_layer/no-tgt/seed-*" xKey: string = "step" yKey: string = "reward.mean" label: string = "No Tgt" color: Color = "#23aaff" - prefix: string = "model-free/iclr-2021/analysis" glob: string = "mlp/3_layer/seed-*" xKey: string = "step" yKey: string = "reward.mean" label: string = "With Tgt" color: Color = "#fff" dashArray: string = "4,4"
And this should output two curves: a dashed black learning curve, indicating the vanilla Soft-Actor Critic (SAC) baseline, that uses a delayed Q-network; and an escher-blue curve showing the performance of the Learned Fourier Feature Q-network (LFF), without relying on a target network.
- todo: add a live example with learning-curve data.
Related Documentation
- Data Query Examples - Complete type definitions for the data query layer, including
Aggregation,SeriesQuery,AggregatedSeriesQuery, andChartQueryinterfaces - Data Hooks and Providers - React hooks for fetching and transforming experiment data, including
useSeries,useAggregatedSeries, anduseComparisonChart - Other Chart Types - Additional visualization components including Bar Charts, Image Display, Video Display, and more