How to Build an NYC Taxi Data Dashboard with Claude Dashboards, DuckDB and d3

Long-exposure light trails of cars driving at night

With so much buzz around Claude dashboards , I had to give it a spin with a well known example: the NYC taxi dataset.

This is a 5-minute guide shows that demonstrates the power of dashboards from just a single prompt:

Build a new dashboard (https://support.claude.com/en/articles/17454700-get-started-with-claude-dashboards) with the data that is in the .parquet in this folder.

how a single Claude Code session turned a raw NYC TLC yellow taxi trip Parquet file (January 2026, 3,724,889 rows, 64 MB) into:

  1. An interactive Claude Dashboard hosted on claude.ai, where every number is traceable to its source.
  2. A self-contained HTML embed (24 KB) that you can paste into a WordPress page with the Elementor HTML widget.

The whole build took about 10 minutes of wall-clock time, from the first prompt to the exported file.

NYC yellow taxi trips, January 2026

— trips took in — in fares, fees and tips. — were paid by card and — started at an airport. The busiest day was —, with — trips.

Trips—
Total charged—
Average fare—
Average distance—
Median trip time—
Card tip rate—

Trips per day

Pickups by calendar day. Weekends are grey.

When people ride

Trips by pickup weekday and hour, summed over the month. Darker cells have more trips.

Trip length

Trips per distance band, with the average total charged.

How riders pay

Average total and tip per trip. Tips paid in cash aren't recorded.

Payment Trips Avg total Avg tip

Source: the NYC TLC yellow taxi trip records for January 2026. Only trips picked up in January are counted. Records are left out when the total is zero or negative, the distance is zero or 100 miles or more, or the drop-off time is before the pickup. "Total charged" is the sum of total_amount and includes fares, surcharges, tolls and recorded tips. Card tip rate is tips divided by fares on card trips.

Contents

What the dashboard shows

SectionChart typeKey insight
Takeaway sentenceText with live-filled figures3.5M trips, $104.4M charged, 62.3% paid by card, 6.3% from airports; busiest day Fri Jan 30 (140,853 trips)
Headline KPIsSix number tilesAvg fare $21.08 · avg distance 3.49 mi · median trip 13.5 min · card tip rate 21.2%
Trips per dayBar chart, weekends muted, hover tooltipsWeekly rhythm and one sharp late-January dip
When people rideWeekday × hour heatmapEvening peaks Thu–Sat; late-night demand on weekends
Trip lengthHorizontal bars by distance bandMost trips are 1–5 miles; 20+ mi trips average $112.19
How riders payTableCard riders tip $4.11 on average; cash tips aren’t recorded

Data cleaning rules: the dashboard keeps only pickups in January 2026 and drops records with a zero or negative total, a distance of zero or 100+ miles, or a drop-off before the pickup. That left 3,517,748 trips.

End-to-end workflow

1. Profile the Parquet file with DuckDB

DuckDB read the Parquet schema (20 columns: pickup/drop-off timestamps, distance, fare, tip, payment type, airport fee, the new cbd_congestion_fee) and the date range. A few pickups fell outside January (from 2025-12-31 to 2026-02-01) and were filtered out.

Where does the data come from? The NYC Taxi & Limousine Commission (TLC) yellow taxi trip records, file yellow_tripdata_2026-01.parquet.

2. Aggregate 3.7M rows into five small CSVs

A dashboard can’t attach a 64 MB file, so DuckDB SQL reduced the data to five small summary tables (216 rows in total):

FileRowsContents
summary.csv1Month totals and rates
daily.csv31Trips, revenue, avg fare, avg miles per day
hourly.csv168Trips per ISO weekday × hour
payment.csv5Trips, avg total, avg tip per payment type
distance.csv6Trips and avg total per distance band

3. Create the Claude Dashboard

  • Created a new artifact from the Dashboard artifact type on claude.ai.
  • Uploaded each CSV to the artifact’s asset store.
  • Wrote the dashboard’s documents in one atomic batch: the dash/meta title, five datasets/* source definitions, and the page files/index.html.
  • Built the page in plain HTML with d3 v7. Each figure is tagged with data-source / data-field / data-where, so readers can click any number and see which CSV row it came from.

4. Visual check and fixes

A headless Chrome screenshot found two layout bugs, and both were fixed in the embed and in the live dashboard:

  • The trip-length bars weren’t on one scale because the value column changed width from row to row. The value column now has a fixed width.
  • The bottom card row left an empty third grid column. The grid is now an explicit two-column layout that drops to one column below 760px.

Commands used

Inspect the Parquet file

duckdb -c "DESCRIBE SELECT * FROM 'yellow_tripdata_2026-01.parquet';
SELECT count(*), min(tpep_pickup_datetime), max(tpep_pickup_datetime)
FROM 'yellow_tripdata_2026-01.parquet';"

Clean and aggregate to CSV (DuckDB SQL)

CREATE VIEW t AS SELECT * FROM 'yellow_tripdata_2026-01.parquet'
 WHERE tpep_pickup_datetime >= '2026-01-01' AND tpep_pickup_datetime < '2026-02-01'
   AND total_amount > 0 AND trip_distance > 0 AND trip_distance < 100
   AND tpep_dropoff_datetime > tpep_pickup_datetime;

COPY (SELECT count(*) trips, round(sum(total_amount),2) revenue, round(avg(fare_amount),2) avg_fare,
  round(avg(trip_distance),2) avg_miles,
  round(median(date_diff('second',tpep_pickup_datetime,tpep_dropoff_datetime))/60.0,1) median_minutes,
  round(sum(tip_amount) FILTER (WHERE payment_type=1)/sum(fare_amount) FILTER (WHERE payment_type=1),4) card_tip_rate,
  round(count(*) FILTER (WHERE payment_type=1)::double/count(*),4) card_share,
  round(count(*) FILTER (WHERE Airport_fee>0)::double/count(*),4) airport_share
  FROM t) TO 'summary.csv';

COPY (SELECT strftime(tpep_pickup_datetime,'%Y-%m-%d') AS "day", count(*) trips,
  round(sum(total_amount),2) revenue, round(avg(fare_amount),2) avg_fare,
  round(avg(trip_distance),2) avg_miles FROM t GROUP BY 1 ORDER BY 1) TO 'daily.csv';

COPY (SELECT isodow(tpep_pickup_datetime) dow, hour(tpep_pickup_datetime) AS "hour", count(*) trips
  FROM t GROUP BY 1,2 ORDER BY 1,2) TO 'hourly.csv';

COPY (SELECT CASE payment_type WHEN 0 THEN 'Flex fare' WHEN 1 THEN 'Credit card' WHEN 2 THEN 'Cash'
  WHEN 3 THEN 'No charge' WHEN 4 THEN 'Dispute' ELSE 'Other' END payment,
  count(*) trips, round(avg(total_amount),2) avg_total, round(avg(tip_amount),2) avg_tip
  FROM t GROUP BY 1 ORDER BY trips DESC) TO 'payment.csv';

COPY (SELECT CASE WHEN trip_distance<1 THEN 'Under 1 mi' WHEN trip_distance<2 THEN '1–2 mi'
  WHEN trip_distance<5 THEN '2–5 mi' WHEN trip_distance<10 THEN '5–10 mi'
  WHEN trip_distance<20 THEN '10–20 mi' ELSE '20+ mi' END band,
  min(trip_distance) lo, count(*) trips, round(avg(total_amount),2) avg_total
  FROM t GROUP BY 1 ORDER BY lo) TO 'distance.csv';

Claude Code tool calls (Artifact / ArtifactData)

StepTool call
Pick the artifact typeArtifact action: quickstart, intent: other
Create the dashboardArtifact publish with the Dashboard type_url, title, auto_open: after_first_write
Read the type’s docsArtifact action: read, path: artifact-type/reference/design-defaults.md (later exporting.md)
Upload dataArtifact publish with asset: true, one call per CSV, which returns /_blob/<id> URLs
Write the dashboardArtifactData action: batch (7 writes: dash/meta, 5 × datasets/*, files/index.html)
Fix the layoutArtifactData action: update on files/index.html with if_version: 1

Claude API usage metrics

Usage summary (/usage equivalent)

MetricValue
Modelclaude-opus-5-5 (Opus 5.5), every request
Wall-clock time16:14:43 → 16:25:12 UTC (≈ 10.5 min)
API requests22
Input tokens (uncached)44
Cache-write tokens88,966
Cache-read tokens1,786,426
Output tokens (incl. thinking)29,567
Cache hit rate on input≈ 95% (1.79M read vs 89K written)

Tool calls

ToolCalls
Bash10
Artifact9
AskUserQuestion4
ArtifactData2
ToolSearch1
Write1
Read1

Context window

MetricValue
Context in use at the last request≈ 112.9K tokens (112,095 cached + 825 new + 2 uncached)
Largest contributorsSystem prompt and tool schemas, the Dashboard type’s instructions and design references, the page HTML (~17 KB), and one rendered screenshot
CompactionNone needed

Conclusions

Claude dashboards can turn into a very powerful tool for fast creation of dashboards and data analysis.

However, it should not be unattended - the prompter must know what is the general view of the data being analyzed. Claude dashboards can turn into a very powerful tool

It was also very positive to see that there is traceability of what Claude did for analysing the data - useful for reproducibility

Picture of Nelson Eiró

Nelson Eiró

Senior Data Engineer

LinkedIn

Add comment: