spb/wp2_uqo Public
UQO Working Paper No. 2 — Decoding Real Estate Descriptions: text-based hedonic analysis of housing listings.
TeX 73.8%
Python 26%
1#!/usr/bin/env python32# Author: Simon-Pierre Boucher — contact@spboucher.ai3#4"""Step 3 — Fit hedonic models A-E and export the paper's result tables.56Inputs : data/processed/hedonic_maison_results.csv7Outputs: results/model_comparison.csv (Table 4 of the paper)8 results/coefficients_model_D.csv (full model, Table 5/6 source)9 results/coefficients_model_E.csv (parsimonious model)10 results/descriptive_stats.csv (Table 2 source)11 results/similarity_stats.csv (Table 3 source)12"""1314import sys15from pathlib import Path1617import numpy as np18import pandas as pd1920sys.path.insert(0, str(Path(__file__).resolve().parents[1]))2122from src import config23from src.models import fit_all_models24from src.references import ENGLISH_LABELS, SIM_COLS2526MODEL_NAMES = {27 "A": "Structural only",28 "B": "+ Description length",29 "C": "+ Semantic similarities (20)",30 "D": "Full model (B + C)",31 "E": "Parsimonious (significant only)",32}333435def coefficient_table(model, variables):36 """Coefficients with HC3 errors, p-values and percentage price impacts."""37 rows = []38 for v in variables:39 if v not in model.params:40 continue41 coef = model.params[v]42 rows.append({43 "variable": v,44 "label": ENGLISH_LABELS.get(v.replace("sim_", ""), v),45 "coefficient": coef,46 "std_error": model.bse[v],47 "t_value": model.tvalues[v],48 "p_value": model.pvalues[v],49 "impact_pct": (np.exp(coef) - 1) * 100,50 })51 return pd.DataFrame(rows)525354def main():55 df = pd.read_csv(config.ANALYSIS_CSV)56 print(f"{len(df):,} observations")5758 models, specs, sig_sims = fit_all_models(df)59 config.RESULTS_DIR.mkdir(parents=True, exist_ok=True)6061 comparison = pd.DataFrame({62 "model": list(models),63 "specification": [MODEL_NAMES[k] for k in models],64 "r2": [m.rsquared for m in models.values()],65 "adj_r2": [m.rsquared_adj for m in models.values()],66 "aic": [m.aic for m in models.values()],67 "bic": [m.bic for m in models.values()],68 "k": [int(m.df_model) for m in models.values()],69 "n": [int(m.nobs) for m in models.values()],70 })71 comparison["delta_r2_vs_A"] = comparison["r2"] - models["A"].rsquared72 comparison.to_csv(config.RESULTS_DIR / "model_comparison.csv", index=False)73 print(comparison.round(4).to_string(index=False))7475 # F-test of the semantic block (D vs A), as reported in the paper76 mA, mD = models["A"], models["D"]77 df_diff = mD.df_model - mA.df_model78 f_stat = ((mA.ssr - mD.ssr) / df_diff) / (mD.ssr / mD.df_resid)79 from scipy import stats80 f_pval = 1 - stats.f.cdf(f_stat, df_diff, mD.df_resid)81 print(f"\nJoint F-test (D vs A): F = {f_stat:.2f}, p = {f_pval:.2e}")82 print(f"Significant similarities in D (p<0.05): {len(sig_sims)}/{len(SIM_COLS)}")8384 coefficient_table(mD, specs["D"]).to_csv(85 config.RESULTS_DIR / "coefficients_model_D.csv", index=False)86 coefficient_table(models["E"], specs["E"]).to_csv(87 config.RESULTS_DIR / "coefficients_model_E.csv", index=False)8889 desc_vars = ["price", "log_price"] + config.STRUCTURAL_VARS + ["remarks_length"]90 df[desc_vars].describe().T.to_csv(config.RESULTS_DIR / "descriptive_stats.csv")9192 sim_stats = df[SIM_COLS].describe().T93 sim_stats.index = [ENGLISH_LABELS[c.replace("sim_", "")] for c in sim_stats.index]94 sim_stats["corr_log_price"] = [95 df[c].corr(df["log_price"]) for c in SIM_COLS96 ]97 sim_stats.to_csv(config.RESULTS_DIR / "similarity_stats.csv")9899 print(f"\n-> {config.RESULTS_DIR}/model_comparison.csv, coefficients_model_D.csv, "100 f"coefficients_model_E.csv, descriptive_stats.csv, similarity_stats.csv")101102103if __name__ == "__main__":104 main()105