Spaces:
Sleeping
Sleeping
Upload 3 files
Browse files- README.md +46 -6
- app.py +135 -0
- requirements.txt +7 -0
README.md
CHANGED
|
@@ -1,12 +1,52 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 6.
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Zürich Apartment Price Predictor
|
| 3 |
+
emoji: 🏠
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: red
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 6.8.0
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
+
short_description: Machine Learning Apartment Rent Price Predictor for Zurich
|
| 11 |
---
|
| 12 |
|
| 13 |
+
# Model Iterations Documentation
|
| 14 |
+
## Task: Apartment Price Prediction (Regression)
|
| 15 |
+
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
---
|
| 19 |
+
|
| 20 |
+
## Summary of Iterative Process
|
| 21 |
+
|
| 22 |
+
| Iteration | Objective | Key Changes | Models Used | CV Mean R² | CV Std Dev | Test MAE (CHF) | Fit Diagnosis |
|
| 23 |
+
|-----------|-----------|-------------|-------------|------------|------------|----------------|---------------|
|
| 24 |
+
| **1** | Build baseline model | - Basic cleaning<br>- 7 numerical features<br>- Train-test split (80/20)<br>- 5-fold CV | Linear Regression<br>Random Forest (n_estimators=100) | 0.429 (LR)<br>0.441 (RF) | 0.073 (LR)<br>0.075 (RF) | 549.90 (LR)<br>553.98 (RF) | ☐ Overfitting ☑ Underfitting ☐ Good Fit |
|
| 25 |
+
| **2** | Improve through feature engineering | - 7 new features created<br>- Feature scaling (Ridge)<br>- Hyperparameter tuning<br>- 5-fold CV | Ridge (alpha=10.0)<br>Random Forest (n_estimators=200, max_depth=15)<br>Gradient Boosting (n_estimators=150, max_depth=5) | 0.461 (Ridge)<br>0.481 (RF)<br>0.521 (GB) | 0.073 (Ridge)<br>0.070 (RF)<br>0.068 (GB) | 541.11 (Ridge)<br>544.92 (RF)<br>537.78 (GB) | ☐ Overfitting ☐ Underfitting ☑ Good Fit |
|
| 26 |
+
|
| 27 |
+
---
|
| 28 |
+
|
| 29 |
+
## Notes
|
| 30 |
+
|
| 31 |
+
**Metric:** MAE (Mean Absolute Error), R² (5-Fold Cross-Validation)
|
| 32 |
+
|
| 33 |
+
**Created Features:**
|
| 34 |
+
- rooms_per_sqm: Raumdichte (Zimmer pro m²)
|
| 35 |
+
- wealth_index: Wohlstandsindikator (tax_income × emp normalisiert)
|
| 36 |
+
- is_zurich_city: Binär-Feature für Zürich Stadt (PLZ 8000-8099)
|
| 37 |
+
- pop_emp_ratio: Bevölkerungs-Arbeitsplatz-Verhältnis
|
| 38 |
+
- log_area: Log-Transformation der Wohnfläche
|
| 39 |
+
- log_pop: Log-Transformation der Bevölkerung
|
| 40 |
+
- log_tax_income: Log-Transformation des Steuereinkommens
|
| 41 |
+
|
| 42 |
+
**Final Selected Features:**
|
| 43 |
+
- area (28.7% importance)
|
| 44 |
+
- log_area (25.0% importance)
|
| 45 |
+
- is_zurich_city (13.0% importance)
|
| 46 |
+
- rooms_per_sqm (12.2% importance)
|
| 47 |
+
- rooms, log_pop, pop_dens, pop, pop_emp_ratio, log_tax_income, emp, wealth_index, tax_income, frg_pct
|
| 48 |
+
|
| 49 |
+
**Final Model:** Gradient Boosting Regressor
|
| 50 |
+
|
| 51 |
+
**Reason for Selection:**
|
| 52 |
+
Best R² score (0.521) and lowest MAE (537.78 CHF). Consistent performance between CV and test set with lowest standard deviation (±67.62 CHF), indicating stable predictions.
|
app.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import numpy as np
|
| 4 |
+
import pickle
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
# Load model (or create dummy if not exists)
|
| 8 |
+
MODEL_PATH = "models/model.pkl"
|
| 9 |
+
|
| 10 |
+
if os.path.exists(MODEL_PATH):
|
| 11 |
+
with open(MODEL_PATH, 'rb') as f:
|
| 12 |
+
model_package = pickle.load(f)
|
| 13 |
+
|
| 14 |
+
# Check if it's the new model format (dict) or old format (single model)
|
| 15 |
+
if isinstance(model_package, dict):
|
| 16 |
+
model = model_package['model']
|
| 17 |
+
scaler = model_package.get('scaler', None)
|
| 18 |
+
features = model_package.get('features', None)
|
| 19 |
+
model_type = model_package.get('model_type', 'unknown')
|
| 20 |
+
using_dummy = False
|
| 21 |
+
print(f"✅ Loaded model: {model_type}")
|
| 22 |
+
else:
|
| 23 |
+
# Old format - single model
|
| 24 |
+
model = model_package
|
| 25 |
+
scaler = None
|
| 26 |
+
features = None
|
| 27 |
+
model_type = 'legacy'
|
| 28 |
+
using_dummy = False
|
| 29 |
+
print("✅ Loaded legacy model")
|
| 30 |
+
else:
|
| 31 |
+
# Dummy model for initial deployment
|
| 32 |
+
model = None
|
| 33 |
+
scaler = None
|
| 34 |
+
features = None
|
| 35 |
+
model_type = None
|
| 36 |
+
using_dummy = True
|
| 37 |
+
print("⚠️ No model found - using dummy")
|
| 38 |
+
|
| 39 |
+
def create_features(rooms, area, postalcode, pop, pop_dens, frg_pct, emp, tax_income):
|
| 40 |
+
"""Create engineered features from input"""
|
| 41 |
+
data = {
|
| 42 |
+
'rooms': rooms,
|
| 43 |
+
'area': area,
|
| 44 |
+
'pop': pop,
|
| 45 |
+
'pop_dens': pop_dens,
|
| 46 |
+
'frg_pct': frg_pct,
|
| 47 |
+
'emp': emp,
|
| 48 |
+
'tax_income': tax_income,
|
| 49 |
+
# Engineered features
|
| 50 |
+
'rooms_per_sqm': rooms / area,
|
| 51 |
+
'wealth_index': (tax_income / 100000) * (emp / 100000),
|
| 52 |
+
'is_zurich_city': 1 if (postalcode >= 8000 and postalcode < 8100) else 0,
|
| 53 |
+
'pop_emp_ratio': pop / (emp + 1),
|
| 54 |
+
'log_area': np.log1p(area),
|
| 55 |
+
'log_pop': np.log1p(pop),
|
| 56 |
+
'log_tax_income': np.log1p(tax_income)
|
| 57 |
+
}
|
| 58 |
+
return pd.DataFrame([data])
|
| 59 |
+
|
| 60 |
+
def predict_price(rooms, area, postalcode, pop, pop_dens, frg_pct, emp, tax_income):
|
| 61 |
+
"""
|
| 62 |
+
Predict apartment rental price based on input features
|
| 63 |
+
"""
|
| 64 |
+
if using_dummy:
|
| 65 |
+
# Simple dummy calculation for testing
|
| 66 |
+
base_price = area * 25 # rough estimate: 25 CHF per m²
|
| 67 |
+
room_factor = rooms * 200
|
| 68 |
+
location_factor = (tax_income / 1000) * 0.5
|
| 69 |
+
|
| 70 |
+
estimated_price = base_price + room_factor + location_factor
|
| 71 |
+
return f"💰 Geschätzter Mietpreis: CHF {estimated_price:.2f}/Monat\n\n⚠️ Dummy-Modell - Training folgt!"
|
| 72 |
+
|
| 73 |
+
else:
|
| 74 |
+
# Create feature dataframe
|
| 75 |
+
input_data = create_features(rooms, area, postalcode, pop, pop_dens, frg_pct, emp, tax_income)
|
| 76 |
+
|
| 77 |
+
# Select only the features the model was trained on
|
| 78 |
+
if features is not None:
|
| 79 |
+
input_data = input_data[features]
|
| 80 |
+
|
| 81 |
+
# Apply scaling if needed (for Ridge)
|
| 82 |
+
if scaler is not None:
|
| 83 |
+
input_data_scaled = scaler.transform(input_data)
|
| 84 |
+
prediction = model.predict(input_data_scaled)[0]
|
| 85 |
+
else:
|
| 86 |
+
prediction = model.predict(input_data)[0]
|
| 87 |
+
|
| 88 |
+
return f"💰 Geschätzter Mietpreis: CHF {prediction:.2f}/Monat\n\n🤖 Modell: {model_type.replace('_', ' ').title()}"
|
| 89 |
+
|
| 90 |
+
# Gradio Interface
|
| 91 |
+
with gr.Blocks(title="Zürich Apartment Price Predictor") as demo:
|
| 92 |
+
gr.Markdown("# 🏠 Zürich Apartment Rent Predictor")
|
| 93 |
+
gr.Markdown("Vorhersage von Mietpreisen für Wohnungen im Kanton Zürich")
|
| 94 |
+
|
| 95 |
+
with gr.Row():
|
| 96 |
+
with gr.Column():
|
| 97 |
+
rooms = gr.Number(label="Anzahl Zimmer", value=3.5, minimum=1, maximum=10)
|
| 98 |
+
area = gr.Number(label="Wohnfläche (m²)", value=75, minimum=10, maximum=500)
|
| 99 |
+
postalcode = gr.Number(label="Postleitzahl", value=8001, minimum=8000, maximum=8999)
|
| 100 |
+
|
| 101 |
+
with gr.Column():
|
| 102 |
+
pop = gr.Number(label="Bevölkerung (Gemeinde)", value=420217)
|
| 103 |
+
pop_dens = gr.Number(label="Bevölkerungsdichte", value=4778.99)
|
| 104 |
+
frg_pct = gr.Number(label="Ausländeranteil (%)", value=32.46)
|
| 105 |
+
emp = gr.Number(label="Anzahl Arbeitsplätze", value=491193)
|
| 106 |
+
tax_income = gr.Number(label="Durchschn. Steuereinkommen", value=85446)
|
| 107 |
+
|
| 108 |
+
predict_btn = gr.Button("🔍 Preis berechnen", variant="primary")
|
| 109 |
+
output = gr.Textbox(label="Ergebnis", lines=3)
|
| 110 |
+
|
| 111 |
+
predict_btn.click(
|
| 112 |
+
fn=predict_price,
|
| 113 |
+
inputs=[rooms, area, postalcode, pop, pop_dens, frg_pct, emp, tax_income],
|
| 114 |
+
outputs=output
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
gr.Markdown("---")
|
| 118 |
+
gr.Markdown("### 📊 Beispiel-Werte")
|
| 119 |
+
gr.Markdown("""
|
| 120 |
+
- **Zürich City (8001)**: PLZ 8001, Pop: 420217, Pop_dens: 4778.99, Frg_pct: 32.46, Emp: 491193, Tax: 85446
|
| 121 |
+
- **Winterthur (8400)**: PLZ 8400, Pop: 114220, Pop_dens: 2009.6, Frg_pct: 29.9, Emp: 57583, Tax: 72190
|
| 122 |
+
- **Rüti ZH (8630)**: PLZ 8630, Pop: 12286, Pop_dens: 1221.3, Frg_pct: 24.8, Emp: 5053, Tax: 66676
|
| 123 |
+
""")
|
| 124 |
+
|
| 125 |
+
gr.Markdown("---")
|
| 126 |
+
gr.Markdown("### 🎯 Modell-Performance")
|
| 127 |
+
gr.Markdown("""
|
| 128 |
+
- **Model**: Gradient Boosting Regressor
|
| 129 |
+
- **R² Score**: 0.521
|
| 130 |
+
- **MAE**: 537.78 CHF
|
| 131 |
+
- **Features**: 14 (inkl. engineered features)
|
| 132 |
+
""")
|
| 133 |
+
|
| 134 |
+
if __name__ == "__main__":
|
| 135 |
+
demo.launch(theme=gr.themes.Soft())
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=4.0.0
|
| 2 |
+
pandas>=2.0.0
|
| 3 |
+
scikit-learn>=1.3.0
|
| 4 |
+
numpy>=1.24.0
|
| 5 |
+
matplotlib>=3.7.0
|
| 6 |
+
seaborn>=0.12.0
|
| 7 |
+
xgboost>=2.0.0
|