Madalali · Issue 02 ← 01

What predicts the
price of a home
in Dar es Salaam.

13,245
rentals modelled (deduped)
4
models, in order of ambition
8
structured features
364k
best MAE (TSH) · XGBoost

In Issue 01 we said the model was the next post. This is that post. We taught four machines to read 30,220 broker listings and predict the rent. A caveat that is not fine print: these are asking prices, not sale prices. The model predicts what a dalali will advertise, not what a home is worth.

70%

of the model's predictive power comes from just two features: location and the number of bedrooms, in almost equal measure (≈35% each, by SHAP). Everything else combined is the other 30%.

This revises Issue 01's hunch. The address is not the whole story: where a home is and how big it is matter about equally. Furnishing, amenities, property type and payment terms barely move the needle.

Try it on your own budget

Tell the calculator what you can pay a month. It shows which wards are in reach, the dalalis who post there, and what to search on Instagram.

Open the rent calculator →

Four models, in order of ambition.

METHOD · 01–04
01Baseline

Predict the Median

The bar every model must clear.

Guess the neighbourhood's median rent for every listing. A model that can't beat this has learned nothing. It is also already a strong hint that location dominates.

02Linear · Ridge

Draw a Straight Line

Interpretable coefficients, regularised.

Each feature gets a number you can read aloud. Ridge shrinks the noise; Lasso prunes it. It cannot bend, though. A pool matters more in Masaki than Kimara, and a line can't say so.

03Random Forest

Let a Forest Vote

Hundreds of trees, bagged.

Non-linearity and interactions for free, no scaling required. It captures "amenity X matters only in tier Y" without being told. Importances tilt toward high-cardinality features, so with 77 neighbourhoods, read them with care.

04XGBoost
val loss ↓

Boost the Errors Away

The usual winner on tabular data.

Each tree fixes the last one's mistakes. Tuned by cross-validation with early stopping, and never on the test set, which we touch exactly once, at the end.

What the model learned, ranked.

SHAP · XGBOOST
05Importance · Bars
bedrooms 36%neighbourhood 34%everything else 30%

Address and Size, Tied

SHAP importance share.

Bedrooms (36%) and neighbourhood (34%) are co-dominant, together 70%. Everything else combined is the other 30%.

06SHAP · Beeswarm

Direction, Not Just Rank

SHAP values per listing.

Beyond which feature matters: which way it pushes, and where it interacts. The honest view of a black box.

07Bedrooms · PDP
0bd8bd+ plateau

The Price of a Bedroom Bends

Partial dependence on bedroom count.

1→2 bedrooms is not 3→4. The curve shows the diminishing return a straight line couldn't.

The scoreboard.

ModelMAE (TSH)MAPER² (log)
Baseline: neighbourhood median456,69670.6%0.29
Ridge / Lasso regression375,74450.9%0.527
Random Forest365,62849.5%0.535
XGBoost364,44648.5%0.550

Scored on a held-out test set split by property, not by row, because brokers repost the same flat, and a careless split would let the answer leak from train into test. The ceiling is not 100%: the target itself is an LLM extraction that is ~73% consistent on re-run, so some error is the label, not the model. We will say which is which.

Why predicting an informal rent is hard.

A formal portal hands you clean fields. A dalali hands you a caption. Before a model sees anything, the price has to be parsed out of “2.5 Laki” and the bedrooms out of “vyumba vitatu”, and roughly one listing in four disagrees with itself on re-extraction. You are modelling a noisy target before you start.

Then there are the traps that make a first model look brilliant and be wrong. We dropped affordability_tier, because it is assigned from price, so feeding it back in is predicting price from price. We dropped price_per_bedroom for the same reason. We masked the price out of the caption before reading any text from it. And we split by property, not by row.

None of this makes the model more accurate. It makes the accuracy honest, which, for a post whose whole point is what the model learned, is the only number worth printing.

Method.

target      = log(price_tsh)            # rentals only, to start
features    = bedrooms, property_type, neighbourhood,
              furnished, amenities(count+top-k),
              payment_terms, post_month, log1p(likes)
dropped     = affordability_tier, price_per_bedroom,   # price-derived
              price_original, price_usd, currency       # target leakage
split       = by property / source_account, deduped     # no repost leakage
ladder      = median → ridge/lasso → random forest → xgboost
tuning      = k-fold CV + early stopping; test touched once
stack       = python · pandas · scikit-learn · xgboost · shap
note        = asking prices · one scrape window · one city

A single scrape from one window (Jan 2025 to Apr 2026); the market may have moved since. This is an informal opinion, not advice, and we accept no liability for decisions made from it. For authoritative figures see the Tanzania National Bureau of Statistics.

Share this issue