Buenos Aires Real Estate
How the city prices a square meter.
Built from 59,069 for-sale apartment listings across two sources — scraped, deduplicated, and cleaned into one dataset, then mapped to median price per m² across all 48 barrios. Two sources — Argenprop, until its WAF blocked the scraper, and Mercado Libre, which carries the bulk of the dataset. The findings: location explains 46% of the variance in price per m², unit size almost none, and Puerto Madero is its own tier without being an island.
How it works
of the variance in price per m² is explained by location alone. Unit size explains 0.7%.
The price map
All 48 barrios, by median price per m². The gradient runs north to south: the corridor along the river — Núñez, Belgrano, Palermo — reaches $3,900, the center sits around $2,300, and the southern edge — Villa Lugano, Villa Soldati — drops near $1,000. Puerto Madero stretches the axis, but look below it: its floor reaches down into the premium end of the next three barrios.
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. A variance decomposition over all 59,069 listings puts it in numbers: location explains 46% of the variance in price per m²; unit size explains 0.7% — practically nothing. The one exception is at the top: above 100 m², price per m² does climb, and Puerto Madero sits top-right — the largest apartments and the most expensive per m², the exact opposite of the premium.
Knowing a unit’s size tells you almost nothing about its price per m². Knowing its barrio tells you nearly half.
Two kinds of expensive
Plot total price against price per m² and most barrios drift up together — the two rankings correlate at 0.93. Recoleta doesn’t: 3rd in the city by total price, only 11th per m². It’s expensive because the apartments are big and old, not because each square meter commands a premium — a real exception, but a narrow one.
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;