Lab Week 07

Overview

There is no lecture this week. This is a two-hour lab run by your two GTAs. Post on Ed Discussion if you have a question.


Lab Objectives

By the end of this lab you will:

  1. Write a short planning document that describes your dashboard’s purpose, audience, and data — this feeds directly into your design report.
  2. Understand how @reactive.calc, @reactive.effect, and filtering fit together in a Shiny app.
  3. Render a working ipyleaflet map inside a Shiny app.

What you need:

  • Your track assignment confirmed (Track A, B, or C).
  • Your own dataset downloaded from the sources listed in 3.6 and saved under data/.
  • The Week 6 repo with a working hello-world Shiny app.
Where to write your plan

Create a file called plan.md (or plan.qmd) in the root of your project repo. You will copy the best parts of this into your design report later.


Section 1: Motivation and Audience

Answer each question below in your plan.md. Copy the template and fill in your own answers.

Template

## 1. Motivation and Audience

### 1.1 What problem does your dashboard address?

<!-- State the specific question your dashboard helps someone answer. -->
<!-- Example: "How has public transport patronage in Auckland recovered since 2020?" -->

[Your answer here]

### 1.2 Who is it for?

<!-- Describe one or two realistic users — their role and the decision this dashboard informs. -->

[Your answer here]

### 1.3 What insight does it enable?

<!-- One sentence: the single most important thing a user should take away. -->
<!-- This sentence might become the title or subtitle of your app. -->

[Your answer here]
Why this matters for the rubric

The “Question” criterion (10%) rewards dashboards that address a clear, well-scoped question. Writing it down now, before you touch the code, prevents the common mistake of building a dashboard that shows data but does not answer anything.


Section 2: Data and Preparation

Fill in the template below in your plan.md (or plan.qmd) using your actual column names and row counts — not guesses.

Do the exploration now, not later

Open your dataset in a notebook. Run .head(), .dtypes, .columns, .shape before filling in the table.

Template

In your .qmd markdown you can add…

## 2. Data and Preparation

### 2.1 Datasets used

| Dataset | Source URL | Format | Rows (approx.) | Key variables |
|---------|-----------|--------|-----------------|---------------|
| e.g. AT patronage | https://at.govt.nz/... | CSV | ~2,400 | month, mode, boardings |
| e.g. Bus routes | AT GIS open data | GeoJSON | ~300 | route_short_name, geometry |

### 2.2 Cleaning and preparation steps

<!-- List the steps needed to get each dataset ready. One line each. -->

1. [e.g. Drop columns X, Y, Z — they are not needed.]
2. [e.g. Reproject from EPSG:2193 (NZTM) to EPSG:4326 for ipyleaflet.]
3. [e.g. Aggregate monthly boardings by mode and year.]
4. ...

### 2.3 Limitations

<!-- Every dataset has gaps. Note at least two. -->

- [e.g. Patronage data is monthly, so weekly patterns are not visible.]
- [e.g. The crash dataset does not distinguish cyclists from e-scooter riders.]

Section 3: Technical Planning

Write a short narrative (three to five paragraphs) in your plan.md describing what a user sees and does when they open your dashboard. A classmate who has never seen your data should be able to picture it after reading.

What a good narrative covers

  • The layout (sidebar, navbar, or something else).
  • Every input by name and type (dropdown, slider, checkbox group, action button).
  • Every output by type (map, chart, text summary) and what it shows.
  • What changes when the user interacts.

Example narrative (generic)

When the user opens the dashboard, they see a sidebar on the left and two cards on the right. The title reads “Auckland Explorer”.

In the sidebar, a dropdown lets the user pick a category. A slider sets a minimum value. An “Apply filters” button sits at the bottom — the user adjusts both controls first, then clicks once to update everything.

The top card shows a chart that updates to reflect the filtered data. The bottom card shows an ipyleaflet map with the filtered records plotted as points or polygons. A one-line text summary between the two cards reads: “Showing 1,240 records matching the current filters.”

Key functions to plan for

When you plan your app, think about where each of these will appear:

  • @reactive.calc — a shared filtered dataset that multiple outputs depend on. Define it once; your chart and map both call it. This avoids filtering the data twice.
  • @reactive.effect — side effects that do not return a value, such as resetting inputs when the user clicks a “Reset” button.
  • Filtering — the logic inside your @reactive.calc that subsets rows based on the current input values (selected category, slider range, checkbox selection, and so on).

You do not need to write code yet. Write the narrative first, then identify which of the functions above you will use and where.


Section 4: ipyleaflet Map inside Shiny

Here we provide a tiny complete, working example using data/NX2.gpkg — the NX2 Northern Express bus route from Hibiscus Coast Station to Auckland Universities. Run it as-is to see a working map, then adapt it to your own dataset.

Follow the link to Canvas for Week 7.

Unzip the file.

Now start building.

The dataset has one row (the full route geometry as a MultiLineString) and average daily boardings for each day of the week (Mon_av through Sun_av). The app lets the user pick a day and shows the route coloured by weekday vs weekend, with a summary of average boardings.

In a separate folder, launch uv and download the relevant packages shiny, shinywidgets, ipyleaflet, and geopandas. It is always good to run a fresh new uv environment!

uv venv

source .venv/bin/activate # MacOSX
.venv\Scripts\Activate.ps1 # Windows PowerShell

uv pip install shiny shinywidgets ipyleaflet geopandas

Once you see that the uv is activated, go and check your app.py. It may look like below:

from shiny import App, ui, render, reactive
from shinywidgets import output_widget, render_widget
from ipyleaflet import Map, GeoData
import geopandas as gpd

# Load once at module level — always reproject to EPSG:4326 for ipyleaflet
route = gpd.read_file("data/NX2.gpkg", layer="bus_route").to_crs(4326)

day_labels = {
    "Mon_av": "Monday",
    "Tue_av": "Tuesday",
    "Wed_av": "Wednesday",
    "Thu_av": "Thursday",
    "Fri_av": "Friday",
    "Sat_av": "Saturday",
    "Sun_av": "Sunday",
}

app_ui = ui.page_sidebar(
    ui.sidebar(
        ui.input_select("day", "Day of week", choices=day_labels),
        ui.input_action_button("apply", "Apply"),
        width=250,
    ),
    ui.output_text("summary"),
    output_widget("map"),
    title="NX2 Bus Route Explorer",
)

def server(input, output, session):

    @reactive.calc
    @reactive.event(input.apply, ignore_none=False)
    def selected_day():
        # Returns the chosen column name; outputs read from it
        return input.day()

    @reactive.effect
    @reactive.event(input.apply)
    def _():
        # Side effect: log to console on each apply click
        col = selected_day()
        boardings = int(route[col].iloc[0])
        print(f"Day: {day_labels[col]}, avg boardings: {boardings:,}")

    @render.text
    def summary():
        col = selected_day()
        boardings = int(route[col].iloc[0])
        return f"NX2 average boardings on {day_labels[col]}: {boardings:,}"

    @render_widget
    def map():
        col = selected_day()
        weekdays = {"Mon_av", "Tue_av", "Wed_av", "Thu_av", "Fri_av"}
        colour = "#d73027" if col in weekdays else "#1f78b4"
        m = Map(center=(-36.74, 174.72), zoom=11)
        m.add_layer(GeoData(
            geo_dataframe=route,
            style={"color": colour, "weight": 4, "opacity": 0.8},
            name="NX2 route",
        ))
        return m

app = App(app_ui, server)

Now, run the app using the play button on the top left of the app.py file. Alternatively, you can run the app in your uv terminal using shiny run --reload app.py and follow the instructions.

Two rules that prevent silent failures:

  • Always .to_crs(4326) when loading a GeoDataFrame. ipyleaflet expects WGS84; NZTM data (EPSG:2193) will render as an empty map with no error message.
  • Return a new Map() inside @render_widget every time. Do not create the map at module level and try to mutate it; the widget will not update.

Your task

Adapt the example above to your own dataset and track. For more working code patterns, browse the Python Shiny gallery — every example includes full source code you can copy and modify.


Section 5: Commit and Push

git add plan.md app.py data/
git commit -m "Week 7: plan and skeleton app"
git push origin main
  1. In the Changes tab you will see plan.md, app.py, and your data files.
  2. Summary: Week 7: plan and skeleton app.
  3. Click Commit to main, then Push origin.

Homework before Week 8

Before the next lab:

  1. Finish the plan if you did not complete all three sections in class.
  2. Load your real data into app.py and get at least one output (chart or map) rendering from the filtered dataset.
  3. Wire all your inputs so that each one meaningfully changes at least one output.
  4. Rebuild and push: shinylive export . docs, commit, push. Check your GitHub Pages URL.
The plan is not assessed separately, but the design report is

Your plan feeds directly into the design report (10% of A2). Sections 1, 2, and 3 of this lab map onto the required report sections. Students who do a thorough job this week typically finish the report in under an hour during Week 8.


Further Reading

  • Textbook 3.5 (Mapping Auckland Open Data) for data-loading patterns.
  • Textbook 3.3 (Decorators and UI Polish) for layout options, cards, and themes.
  • Python Shiny gallery for working code patterns to adapt.
  • ipyleaflet documentation for layer types beyond GeoData.