Data Dashboards


Data Dashboards

A set of marimo notebooks, each loading a public dataset and walking through the same pipeline — load, explore, check data quality, analyze — practiced against four different domains: e-commerce, movies, energy consumption, and Pokémon.

Overview

The goal isn’t the datasets themselves, it’s the repeatable skill set: reading messy real-world files, sanity-checking them before trusting them, reshaping them into something queryable, and presenting the result. Each notebook applies that same discipline to a dataset with its own quirks — duplicate keys, inconsistent column naming, month names instead of numbers, mostly-blank fields — so the data quality step actually earns its keep rather than being a formality.

What’s in the repo

  • ecommerce.py, movies.py, global_energy.py, pokemon.py — the four notebooks
  • loader/loader.py — Loader.load_directory(), walks a folder and dispatches by file extension
  • loader/reader.py — thin wrappers around pd.read_csv / read_parquet / read_json with validation
  • data/ — the datasets themselves, checked into the repo for reproducibility (Olist e-commerce, a recent-movies set, global energy consumption, hourly solar output, Pokémon)
  • layouts/ — marimo grid/slides layout presets
  • scripts/download_data.sh — pulls a Kaggle dataset into data/<path>/ and unzips it

The four notebooks

ecommerce.py — the biggest dataset (~1.6M rows across 9 files: customers, orders, order items, payments, reviews, products, sellers). Joins order items → products → orders → customers into one denormalized table, using pandas’ merge indicator=True as a running check that nothing drops rows unexpectedly. The payoff is an interactive filter shown two ways side by side: a live DuckDB query via mo.sql and the equivalent pandas groupby.

movies.py — takes one flat CSV and builds a small star schema by hand: a movie_fct fact table, a genre_dim with bridge table, a people_dim / role_dim with bridge table for cast and crew, and a movie_stats_dim, visualized with a mermaid ER diagram. The data quality check has real teeth here — release_month is stored as a name ("October") and release_day is blank for most rows, both of which have to be handled explicitly before a release_date column is buildable.

global_energy.py — six small energy-consumption CSVs with inconsistently cased, punctuated headers (Solar (PV) (TWh)), normalized with one column-cleaning helper, plus a separate hourly solar-plant dataset you can zoom into by date range.

pokemon.py — the dashboard: one filter row (generation, primary type, legendary status, name search) scopes KPI tiles, a type-count chart, an average-stats-by-type chart, an attack-vs-defense scatter, a legendary-vs-non-legendary box plot, a per-Pokémon lookup, and a full sortable table — all built with Altair via vegafusion.

Tech stack

LayerTool
Package managementuv
Notebooksmarimo
Datapandas
ChartsAltair / vegafusion
SQL-in-notebookDuckDB (via mo.sql) / sqlglot

Quickstart

uv sync
uv run marimo edit pokemon.py   # or ecommerce.py / movies.py / global_energy.py

Why this project matters

It’s deliberately practiced across four unrelated domains rather than one, so the transferable part — trust nothing until you’ve checked it, then reshape and present — stays separate from any one dataset’s specifics. That’s the same habit I want to carry into production ingestion and analytics work.

View the repository