Predict a fair rent for any Indian flat, and instantly tell whether a listing is a good deal or overpriced.
RentRadar is an end-to-end data product built on ~7,500 real rental listings across five Indian cities. It cleans messy listing data, uncovers what actually drives rent through statistical testing, trains a machine-learning model to predict fair prices, and serves it all through an interactive web app.
π Try the live app Β Β·Β π See the findings Β Β·Β π§ How it works
A renter looking at a flat asks two questions: "What should this cost?" and "Am I getting ripped off?" RentRadar answers both.
- Estimates fair rent for any flat from its city, locality, size, bedrooms, bathrooms, and furnishing.
- Flags the deal β enter what a listing is asking, and RentRadar labels it Good Deal, Fairly Priced, or Overpriced, with the percentage difference from fair value.
Before modeling, I explored the data and statistically tested what drives rent. All three findings are significant at p < 0.05.
1. Furnished flats command a real premium. Furnished flats rent for roughly βΉ19,700 more per month than unfurnished ones. A Welch's t-test confirmed the gap is significant (t = 12.56, p β 3Γ10β»Β³β΅) β not random noise.
2. Rent rises sharply with bedroom count. Average rent roughly doubles with each bedroom from 1 to 4 BHK. One-way ANOVA confirmed the differences are significant (F = 1341, p β 0). Higher BHK categories (5+) were excluded due to too few samples.
3. Cities differ dramatically. Mumbai is the most expensive (~βΉ73,000 average), Nagpur the cheapest (~βΉ18,000) β a ~4Γ spread. ANOVA confirmed the city effect is significant (F = 285, p β 6Γ10β»Β²Β²βΉ).
Which groups differ? A one-way ANOVA only says at least one group stands out. Post-hoc Tukey HSD (which corrects for multiple comparisons) shows the differences are pervasive, not driven by a single outlier: all 10 city pairs differ significantly β even the closest, New Delhi vs Pune β and all 6 bedroom pairs (1β4 BHK) differ significantly. No two cities, and no two adjacent BHK levels, are statistically interchangeable.
Note on interpretation: these tests establish statistical association, not proven causation. The bedroom and city effects partly reflect differences in flat size and location.
I framed fair-rent prediction as a regression problem and benchmarked every model against a deliberately simple baseline.
| Model | RMSE (avg error) | vs. baseline |
|---|---|---|
| Baseline (locality average) | βΉ41,434 | β |
| Linear Regression | βΉ30,407 | better |
| Random Forest | βΉ29,180 | 30% better |
The Random Forest beat the naive baseline by 30%, capturing nonlinear relationships and feature interactions that a locality average alone misses.
Honest limitations: the model's error is largest on ultra-luxury rentals (βΉ1 lakh+), which are sparse in the data β so very high-end flats are predicted less reliably. Locality sparsity was the other big issue: over 1,000 localities appeared only once. I bucketed every locality with fewer than 10 listings β counted on the training split only, to avoid leakage β into a single "Other" group, cutting the feature space from 1,762 to 145 columns. This trades a little raw accuracy (an earlier 1,762-column model scored ~βΉ27,800 RMSE by memorizing one-off localities) for a far more defensible model that generalizes to unseen areas.
Raw listings β Clean & validate β Explore & test β Train model β Serve app
(CSV, 7.7k) (pandas) (scipy stats) (scikit-learn) (Streamlit)
- Data cleaning β parsed messy fields, removed sale-price contamination and outliers, handled missing values. (See
DATA_QUALITY.mdfor every decision.) - Statistical analysis β t-tests and ANOVA to identify significant rent drivers.
- Modeling β train/test split, locality-average baseline, then Linear Regression and Random Forest, compared on held-out data via RMSE.
- Deployment β a Streamlit app that loads the trained model and returns a fair-rent estimate plus a deal verdict.
Python Β· pandas Β· scikit-learn Β· scipy Β· matplotlib / seaborn Β· Streamlit Β· joblib
A core part of this project was handling real, messy data honestly. The original price column was contaminated with sale prices and junk values; I evaluated the data, switched to a cleaner source with a labeled rent field, and documented every cleaning decision. Full details in DATA_QUALITY.md.
# 1. Clone and enter the repo
git clone https://github.com/Sayan7anDa5/rentradar.git
cd rentradar
# 2. Install dependencies
pip install -r requirements.txt
# 3. Launch the app
streamlit run app.pyThe app opens in your browser. Pick a city and locality, set the flat's details, enter an asking rent, and click Check This Deal.
Beyond the CSV that powers the app, the dataset is also modeled in a normalized PostgreSQL schema β a demonstration of SQL data modeling and querying.
- Schema (
sql/schema.sql): three tables βcitiesβlocalitiesβlistingsβ with foreign keys, aUNIQUE(name, city_id)constraint (a locality name can recur across cities), and aCHECKon furnishing values. - Loader (
db/load_to_postgres.py): readsdata/rentals_clean.csvand populates the tables in foreign-key order β 5 cities, 1,990 locality rows (1,968 distinct names, but 21 recur across cities), and 7,538 listings. Idempotent, safe to re-run. - Queries (
sql/queries.sql) and a runnable notebook (notebooks/sql_analysis.ipynb): JOIN-based analysis β average rent by city, the furnished premium per city, the priciest localities with a reliable sample (HAVING COUNT(*) >= 10), and price-per-sqft leaders per city via a window function.
# 1. Create the role and database (one time)
sudo -u postgres psql -c "CREATE ROLE rentradar LOGIN PASSWORD 'rentradar';"
sudo -u postgres psql -c "CREATE DATABASE rentradar OWNER rentradar;"
# 2. Configure the connection
cp .env.example .env # holds DATABASE_URL
# 3. Create the schema and load the data
.venv/bin/python -c "import psycopg2,os; from dotenv import load_dotenv; load_dotenv(); \
c=psycopg2.connect(os.environ['DATABASE_URL']); c.cursor().execute(open('sql/schema.sql').read()); c.commit()"
.venv/bin/python db/load_to_postgres.pyThe Streamlit app itself still reads the CSV β the Postgres layer is a standalone analytics artifact.
rentradar/
βββ README.md
βββ DATA_QUALITY.md # every data-cleaning decision
βββ requirements.txt
βββ app.py # the Streamlit app
βββ rentradar.py # shared helper (locality bucketing)
βββ data/
β βββ data.csv # raw source data
β βββ rentals_clean.csv # cleaned dataset
βββ sql/
β βββ schema.sql # normalized DDL
β βββ queries.sql # analytical JOIN queries
βββ db/
β βββ load_to_postgres.py # CSV -> Postgres loader
βββ models/
β βββ rent_model.pkl # trained Random Forest
β βββ model_columns.pkl # feature columns for inference
β βββ known_localities.pkl # localities kept (rest bucketed to "Other")
βββ notebooks/
β βββ EDA.ipynb # exploration & cleaning
β βββ Stats.ipynb # statistical testing (+ Tukey HSD)
β βββ modeling.ipynb # model training & evaluation
β βββ sql_analysis.ipynb # SQL queries over Postgres
βββ charts/ # exported visualizations


