An analysis of Antwerp's short-let market built from Inside Airbnb's public exports: 2,654 listings, 122,622 reviews, and a nightly availability calendar. It started as a timed team hackathon and has been carried further into a standalone piece of analysis.
The finding: the neighbourhoods that charge the most earn the least.
Across the 18 neighbourhoods with enough listings to be meaningful, nightly price and occupancy are negatively correlated (r = −0.38). Charging more is not the same as earning more, and in Antwerp it is close to the opposite:
| Median price/night | Occupancy | Est. revenue/year | |
|---|---|---|---|
| Harmonie — best earner | €88 | 67% | €20,082 |
| Stadspark — most expensive | €112 | 38% | €4,144 |
Stadspark commands a 27% price premium over Harmonie and earns roughly a fifth as much. The empty nights cost far more than the higher rate recovers.
The same trap catches the biggest market in the city: Historisch Centrum holds 405 listings — more than any other neighbourhood — charges an above-median €94, and lands near the bottom on revenue at €7,410. It is the most crowded, most touristic, and one of the least productive places in Antwerp to run a listing.
The top of the table is not the postcard city. Harmonie, the three Borgerhout districts, and Oud-Berchem are residential neighbourhoods in the belt outside the historic core, all running 56–67% occupancy. The tourist-centre names — Historisch Centrum, Theaterbuurt-Meir, Centraal Station — cluster near 37%.
The plausible reading is that the centre is saturated: enough supply chasing the same weekend visitors that individual listings sit empty midweek, while the residential belt picks up steadier, longer, less seasonal demand.
Reviews are the only public signal for completed stays. Antwerp's grew steadily to 2019, collapsed in 2020, and recovered past the old peak — 2024 finished around 4× its 2019 level. Whatever the centre's occupancy problem is, it is not a shrinking market.
- Occupancy is a proxy. Inside Airbnb publishes
availability_365(nights still bookable), not nights actually sold.(365 − availability_365) / 365treats every unavailable night as a booked one, which also sweeps in host blackouts and maintenance. It overstates occupancy — but consistently across neighbourhoods, so the ranking holds up better than the absolute numbers. - Revenue is
median price × occupancy × 365, so it inherits that same bias. Read it as a comparative index, not a euro forecast. - Medians, not means. Price runs from €13 to €50,000; a handful of extreme listings would drag any mean somewhere useless.
- Neighbourhoods under 30 listings are excluded — 55 exist in the data, 18 clear the bar. Small-sample medians swing wildly.
- One snapshot. Listings reflect a single scrape; a seasonal pull would show a different picture.
pip install -r requirements.txt # or: conda env create -f environment.yml
python analysis/build_charts.py # regenerates every figure in docs/
jupyter notebook H1.ipynb # the original hackathon notebookanalysis/build_charts.py prints the headline numbers to stdout and writes a light and
a dark variant of each chart. Both raw exports are committed gzipped, so nothing needs
downloading.
Inside Airbnb, Antwerp snapshot (insideairbnb.com):
| File | Rows | Contents |
|---|---|---|
listings.csv.gz |
2,654 | One row per listing, 79 columns |
calendar.csv.gz |
— | Nightly availability and price |
reviews.csv.gz |
122,622 | Review dates, back to 2012 |
The repo began as a timed team hackathon for Python for Data Science at ESADE MiBA —
take raw Airbnb exports and reach an ML-ready dataset in one sitting (H1.ipynb, with
TF-IDF text features and temporal engineering).
Hackathon team: Pau Gratacós Fusté · Sean Hoet · Florian Nix · Caroline Wheeler · Riwad Irshied. My contribution there was sourcing the Antwerp datasets and the load/merge step that joined the three exports for the team to build on.
The analysis and charts above (analysis/, this README) are my own work afterwards.