Buenos Aires Real Estate
How the city prices a square meter.
Built from every for-sale apartment listing in the city — scraped, deduplicated, and cleaned into one dataset, then mapped to median price per m² across 41 barrios. A fault-tolerant scraper, layered neighborhood normalization, and a median-based analysis surface where price concentrates, and why one barrio doesn't play by the same rules.
How it works
The price gap from the cheapest barrio to Puerto Madero — a tier no other neighborhood comes near.
The price map
Every barrio with 10+ listings, by median price per m². The northern and residential barrios lead; the older central ones trail. Puerto Madero is left stretching the axis — being an outlier is the point, not a problem to hide.
Location, not size
In many cities, small units carry a price-per-m² premium. If that held here, this cloud would slope down to the right. It doesn’t. Price per m² scatters freely at every size, and Puerto Madero sits top-right — the largest apartments and the most expensive per m², the exact opposite of the premium.
Knowing a barrio’s typical size tells you almost nothing about its price per m².
Two kinds of expensive
Plot total price against price per m² and most barrios drift up together. Recoleta doesn’t: high total price, only mid-pack per m². It’s expensive because the apartments are big and old, not because each square meter commands a premium. Belgrano echoes it more gently.
The query behind the map
No need to open the repo. This is the heart of the analysis: one snapshot per listing, filtered to a clean USD scope, aggregated to a true median per barrio.
WITH latest AS ( SELECT DISTINCT ON (listing_id) listing_id, price, currency FROM listing_snapshots ORDER BY listing_id, captured_at DESC -- newest snapshot per listing ) SELECT l.neighborhood, COUNT(*) AS n, ROUND(percentile_cont(0.5) WITHIN GROUP ( ORDER BY s.price / l.covered_m2)) AS median_usd_m2 FROM latest s JOIN listings l ON l.id = s.listing_id WHERE s.currency = 'USD' AND l.covered_m2 > 0 AND l.neighborhood IS NOT NULL GROUP BY l.neighborhood HAVING COUNT(*) >= 10 ORDER BY median_usd_m2 DESC;