← Terug naar overzicht

Movie Dashboard

Een Streamlit-dashboard dat mijn eigen Letterboxd-kijkgeschiedenis analyseert en visualiseert.

Screen capture van Movie Dashboard
  • Python
  • Streamlit
  • Pandas

Probleem & doel

Films kijken is al jaren een van mijn favoriete bezigheden, en als data-persoon wilde ik weten welke patronen daarin zitten: welke regisseurs, genres of landen domineren mijn kijkgeschiedenis? Platforms zoals Letterboxd zijn geweldig om bij te houden wat je hebt gezien, maar het biedt weinig inzichten in de patronen van mijn kijkgeschiedenis.

Het doel van dit project was om mijn kijkgeschiedenis te verrijken met metadata (regisseurs, cast, genres, taal, land) en daar vervolgens een interactief dashboard van te bouwen, zodat ik vragen kan beantwoorden als: welke regisseur zie ik het vaakst terug? Kijk ik vooral Engelstalige films, of ook veel wereldcinema? Wat zijn mijn favoriete film-eras?

Demo

Movie Dashboard: filters aanpassen (decennium, land), scrollen langs de grafieken en hoveren over de landenkaart

De live demo bovenaan de pagina draait op Streamlit Community Cloud, met een sample van 250 films uit mijn eigen kijkgeschiedenis — zodat je de filters, grafieken en kaart met genoeg spreiding kunt uitproberen, zonder dat mijn volledige Letterboxd-data publiek komt te staan.

Architectuur

Het project bestaat uit drie lagen, elk met een eigen verantwoordelijkheid:

  1. Verrijkensrc/tmdb_api.py bevat één centrale functie, search_movie(), die filmmetadata ophaalt bij TMDB (op basis van een TMDB-ID of via title/year-search). scripts/update_cache.py loopt de kijkgeschiedenis langs en bouwt een lokale cache (tmdb_cache.csv) op, zodat metadata maar één keer per film opgehaald hoeft te worden.
  2. Voorbereidenscripts/prepare_df.py transformeert de ruwe TMDB-cache naar een dashboard-klare dataset: kleine inconsistenties opschonen en afgeleide kolommen toevoegen (decennium, hoofdproductieland, hoofdtaal) die anders steeds opnieuw berekend zouden moeten worden in het dashboard zelf.
  3. Visualiserensrc/graphs.py bevat herbruikbare Plotly-hulpfuncties (plot_bar(), plot_map()) met een eigen kleursysteem. src/dashboard.py bevat de volledige Streamlit-app in één run_dashboard()-functie (styling, filters, grafieken). main.py is de dunne entrypoint die de app start.
movie-dashboard/

├── src/
│   ├── tmdb_api.py       # TMDB API-functie (search_movie)
│   ├── graphs.py         # Plotly-visualisatiehulpfuncties
│   └── dashboard.py       # Streamlit-app (run_dashboard)

├── scripts/
│   ├── update_cache.py   # Cache opbouwen/verversen
│   └── prepare_df.py     # Dataset voorbereiden voor het dashboard

├── data/
│   ├── raw/sample_movies.csv        # Kijkgeschiedenis (input)
│   ├── cache/tmdb_data.csv          # TMDB-cache (gegenereerd)
│   └── processed/final_movies.csv   # Dashboard-klare dataset (gegenereerd)

└── main.py                # Entrypoint: streamlit run main.py

Tech stack

  • Python — de volledige pipeline, van data-verrijking tot dashboard, in één taal
  • pandas — data-transformatie, caching-logica, filtering
  • requests — TMDB API-calls, met expliciete afhandeling van connection errors, HTTP-errors en timeouts
  • tqdm — voortgangsindicatie tijdens het (trage) verrijkingsproces
  • TMDB API — bron van filmmetadata (cast, crew, genres, taal, land, runtime)
  • Plotly (Express) — interactieve bar charts en choropleth-kaarten
  • Streamlit — de dashboard-app zelf, inclusief custom dark theme via .streamlit/config.toml
  • Lokale CSV-caching in plaats van een database — past bij de schaal van een persoonlijk project

Belangrijkste beslissingen & afwegingen

  • TMDB-ID als sleutel, niet fuzzy title-matching. Door te zoeken op een vaste TMDB-ID (in plaats van titel + jaar te matchen) wordt de koppeling tussen kijkgeschiedenis en metadata betrouwbaar, ook bij films met dezelfde titels.
  • Incrementele, onderbreekbare caching. Metadata wordt per batch van 25 films weggeschreven, met een willekeurige vertraging (2,5–5s) tussen requests om TMDB’s rate limits te respecteren. Het script is idempotent: opnieuw draaien overschrijft of dupliceert geen bestaande data, en een onderbroken run kan gewoon hervat worden.
  • Scheiding van verantwoordelijkheden. Verrijken (TMDB ophalen), voorbereiden (kolommen afleiden) en visualiseren (dashboard) zijn bewust drie losse stappen in plaats van één groot script. Dat houdt elk onderdeel simpel te testen en te hergebruiken.
  • Herbruikbare, generieke plot-functies. In plaats van losse grafiekcode per chart is er één plot_bar() en één plot_map(), met ondersteuning voor zowel losse kleuren als gradients. Een kleine kleur-hulpfunctie (adjust_color()) genereert automatisch een lichte/donkere variant van een basiskleur, zodat alle grafieken een consistente, op het donkere thema afgestemde look hebben.
  • Binaire shortcut-filters. Naast multiselect-filters (land, genre, taal, decennium) zijn er simpele VS/niet-VS- en Engelstalige/niet-Engelstalige-filters. Dat is een bewuste afweging: minder granulariteit, maar snel antwoord op vragen (hoeveel wereldcinema kijk ik eigenlijk?).
  • explode() voor meerwaardige velden. Cast en crew staan in TMDB als komma-gescheiden string in één cel (bv. drie acteurs in één veld). Door deze te exploderen naar losse rijen kan er correct per persoon geteld worden, in plaats van per film-string.
  • Sample-data voor de publieke versie. De live demo en de publieke repo draaien bewust op een sample van 250 films uit mijn eigen kijkgeschiedenis, in plaats van de volledige dataset (1231 films). Zo kan iedereen de filters, grafieken en kaart met genoeg spreiding uitproberen, zonder dat mijn complete, persoonlijke Letterboxd-geschiedenis publiek komt te staan.

Lessen & vervolg

Geen live databron. Letterboxd heeft geen publieke API. De enige manier om aan mijn eigen kijkgeschiedenis te komen is een CSV-export vanuit de app, met een paar velden: titel, jaar, rating, kijkdatum. Het dashboard draait dus op een snapshot, niet op een live-gekoppelde bron. Om het dashboard bij te werken is dus eerst een nieuwe export en een nieuwe pipeline-run nodig.

Preprocessen versus live berekenen. prepare_df.py doet de bewerkingen die voor elke filtercombinatie hetzelfde blijven (opschonen, decennium/hoofdland/hoofdtaal afleiden) één keer. Het exploderen van cast en crew naar losse rijen gebeurt juist in het dashboard zelf, na filtering, zodat de telling klopt met de actuele selectie in plaats van de hele dataset.

Wat ik anders zou doen:

  • De export/import-stap loopt nu volledig handmatig: nieuwe Letterboxd-export downloaden, dan de pipeline opnieuw draaien. Ik zou onderzoeken of er meer automatisering van de export en pipeline mogelijk is.
  • Filters werken nu onafhankelijk van elkaar: een genre-filter toont altijd alle genres, ook als een andere filter (bv. decennium) de resultaten al heeft ingeperkt tot een paar films. Bij een veel grotere dataset zou ik de filter-opties dynamisch willen aanpassen aan wat al geselecteerd is.

Repository

Bekijk de broncode via de repo-link hierboven. De 3-delige blogserie hierboven geeft een uitgebreide, stap-voor-stap walkthrough van de implementatie: van TMDB-verrijking, via herbruikbare Plotly-visualisaties, tot de complete Streamlit-app.