Dashing through the snow ❄️
(or Sharing your data)
in a Quarto Dashboard

Mine Çetinkaya-Rundel

Duke University + Posit

Hello, Quarto!

Quarto — https://quarto.org

An open-source scientific and technical publishing system that builds on standard markdown with features essential for scientific communication.

  • Pandoc Markdown

  • Jupyter Kernels

  • Dozens of Output Formats

  • Specialized Project Types

With Quarto …

you can weave together narrative and code to produce elegantly formatted output as documents, web pages, blog posts, books and more, with…

  • consistent implementation of attractive and handy features across outputs: tabsets, code-folding, syntax highlighting, etc.
  • multi-lingual support for Python, Julia, Observable, and more via the Jupyter engine for executable code chunks as well as R via the knitr engine
  • defaults that meet accessibility guidelines as well as features that enable good practices for producing accessible documents

How does Quarto work?

Computations: Jupyter (and Knitr and ObservableJS)

Markdown: Pandoc
with many enhancements

Output: Documents, presentations, websites, books, blogs

Render Notebook to HTML (default options)

Render Notebook to HTML (document level options)

Render Notebook to HTML (document and cell level options)

Render Notebook to Revealjs https://quarto.org/docs/presentations/revealjs/

Workflow

Render and Preview

Render to output formats:

# ipynb notebook
quarto render notebook.ipynb
quarto render notebook.ipynb --to docx

# plain text qmd
quarto render notebook.qmd 
quarto render notebook.qmd --to pdf

Live preview server (re-render on save):

# ipynb notebook
quarto preview notebook.ipynb
quarto preview notebook.ipynb --to docx

# plain text qmd
quarto preview notebook.qmd
quarto preview notebook.qmd --to pdf

Plain Text Notebooks w/.qmd Files

snowdash.ipynb
---
title: Dashing through the snow ❄️
format: revealjs
jupyter: python3
---

```{python}
import pandas as pd
from datetime import datetime
import itables as itables
import plotly.express as px
import plotly.graph_objects as go
```

```{python}
#| tags: [parameters]
today_string = "2023-12-08"
```

```{python}
#| message: false
meribel = pd.read_csv("data/Meribel.csv")
meribel['datetime'] = pd.to_datetime(meribel['datetime'])

stations = pd.read_csv("data/stations.csv")
```

```{python}
today_date = pd.to_datetime(today_string)
```

```{python}
n_snow = meribel[meribel['snow'] > 0].shape[0]

n_below_freezing = meribel[meribel['temp'] < 32].shape[0]

def below_freezing_color(n):
    if n > 5:
        return "danger"
    elif 3 < n <= 5:
        return "warning"
    else:
        return "light"

n_below_freezing_color = below_freezing_color(n_below_freezing)
```

## Snow fall

```{python}
#| label: fig-snow-fall
#| fig-cap: Snow fall in Meribel
#| scrolled: true

# Create figure
fig = go.Figure()

# Add lines for temp, tempmin, tempmax
fig = fig.add_trace(go.Scatter(x=meribel['datetime'], y=meribel['snow'], mode='lines', name='temp', line=dict(color='black')))

# Add vertical dashed line for today's date
fig = fig.add_shape(
    go.layout.Shape(
        type="line",
        x0=today_date,
        x1=today_date,
        y0=min(meribel['snow']),
        y1=max(meribel['snow']),
        line=dict(
            color="#ae8b2d",
            width=1.5,
            dash="dash"
        )
    )
)

# Set layout including axis labels and y-axis range
fig = fig.update_layout(
    xaxis_title="Date",
    yaxis_title="Snow fall",
)

# Show the plot
fig.show()
```

# Data

```{python}
#| title: Data
# Selecting all columns except 'name'
meribel = meribel.drop(columns=['name'])

# Displaying the DataFrame as an interactive table with pagination using itables
itables.options.classes = ["display", "table", "table-bordered", "table-striped"]
itables.show(meribel)
```

Plain Text Notebooks w/.qmd Files

  • Editable with any text editor (extensions for VS Code, Neovim, and Emacs)

  • Cells always run in the same order

  • Integrates well with version control

  • Cache output with Jupyter Cache or Quarto freezer

  • Lots of pros and cons visa-vi traditional .ipynb format/editors, use the right tool for each job

Rendering Pipeline

Notebook workflow (no execution occurs by default):

Plain text workflow (.qmd => .ipynb then execute cells):

Quarto Dashboards

A new output format for easily creating
dashboards from notebooks

Demo: Dashing through the snow

Dashboard: https://mine.quarto.pub/dashing-through-snow-py

Code: https://github.com/mine-cetinkaya-rundel/dashing-through-snow

Notebook ➝ Dashboard

snowdash.ipynb
---
title: "Dashing through the snow ❄️"
format: dashboard
---

# notebook content goes here...

Dashboard Components

  1. Navigation Bar and Pages — Icon, title, and author along with links to sub-pages (if more than one page is defined).

  2. Sidebars, Rows & Columns, and Tabsets — Rows and columns using markdown heading (with optional attributes to control height, width, etc.). Sidebars for interactive inputs. Tabsets to further divide content.

  3. Cards (Plots, Tables, Value Boxes, Content) — Cards are containers for cell outputs and free form markdown text. The content of cards typically maps to cells in your notebook or source document.

All of these components can be authored and customized within notebook UI or plain text qmd.

Layout: Rows

---
title: "Focal (Top)"
format: dashboard
---
    
## Row {height=70%}

```{python}
```

## Row {height=30%}

```{python}
```

```{python}
```

Layout: Columns

---
title: "Focal (Top)"
format: 
  dashboard:
    orientation: columns
---
    
## Column {width=60%}

```{python}
```

## Column {width=40%}

```{python}
```

```{python}
```

Tabsets

---
title: "Palmer Penguins"
format: dashboard
---
    
## Row

```{python}
```

## Row {.tabset}

```{python}
#| title: Chart 2
```

```{python}
#| title: Chart 3
```

Plots

plotly

```{python}
#| title: GDP and Life Expectancy
import plotly.express as px
df = px.data.gapminder()
px.scatter(
  df, x="gdpPercap", y="lifeExp", 
  animation_frame="year", animation_group="country",
  size="pop", color="continent", hover_name="country", 
  facet_col="continent", log_x=True, size_max=45, 
  range_x=[100,100000], range_y=[25,90]
)
```

ipyleaflet

```{python}
#| title: "World Map"
#| padding: 0px
from ipyleaflet import Map, basemaps, basemap_to_tiles
Map(basemap=basemap_to_tiles(basemaps.OpenStreetMap.Mapnik),
    center=(48.204793, 350.121558), zoom=2)
```

Tables

tabulate

```{python}
from tabulate import tabulate
from IPython.display import Markdown
Markdown(tabulate(penguins, showindex=False)}
```

itables

```{python}
from itables import show
show(penguins)
```

Value Boxes

## Row

```{python}
#| component: valuebox
#| title: "Current Price"
dict(icon = "currency-dollar",
     color = "secondary",
     value = get_price(data))
```

```{python}
#| component: valuebox
#| title: "Change"
change = get_change(data)
dict(value = change['amount'],
     icon = change['icon'],
     color = change['color']) 
```

Text Content

## Column

```{python}
#| title: Population
px.area(df, x="year", y="pop", 
        color="continent", 
        line_group="country")
```

```{python}
#| title: Life Expectancy
px.line(df, x="year", y="lifeExp", 
        color="continent", 
        line_group="country")
```

::: {.card}
Gapminder combines data from multiple sources
into unique coherent time-series that can’t be
found elsewhere. Learn more about the Gampminder
dataset at <https://www.gapminder.org/data/>.
:::

Expanding Cards

Cards provide an Expand button which appears at bottom right on hover:

Dashboard Deployment

Dashboards are typically just static HTML pages so can be deployed to any web server or web host.

Static Rendered a single time (e.g. when underlying data won’t ever change)
Scheduled Rendered on a schedule (e.g. via cron job) to accommodate changing data.
Parameterized Variations of static or scheduled dashboards based on parameters.
Interactive Fully interactive dashboard using Shiny (requires a server for deployment).

Parameterized Dashboards

Add a parameters tag to the first cell (based on papermill) :

```{python}
#| tags: [parameters]
ticker = "BA"
```

Use the -P command line option to vary the parameter:

quarto render dashboard.qmd -P ticker:GOOG

Interactive Dashboards

https://quarto.org/docs/dashboards/interactivity/shiny-python/

  • For interactive exploration, some dashboards can benefit from a live Python backend

  • To do this with Quarto Dashboards, add interactive Shiny components

  • Deploy with or without a server!

Deployment of Interactive Dashboards

Server

On-Prem

Serverless

Using Pyodide

[Example]

Learning More

Quarto v1.4 Pre-Release

https://quarto.org/docs/download/prerelease.html

pip install git+https://github.com/quarto-dev/quarto-cli

Resources

Slides

https://mine.quarto.pub/quarto-dashboards-pydata/