#!/usr/bin/env python3 # Author: Simon-Pierre Boucher — contact@spboucher.ai # """Step 3 — Fit hedonic models A-E and export the paper's result tables. Inputs : data/processed/hedonic_maison_results.csv Outputs: results/model_comparison.csv (Table 4 of the paper) results/coefficients_model_D.csv (full model, Table 5/6 source) results/coefficients_model_E.csv (parsimonious model) results/descriptive_stats.csv (Table 2 source) results/similarity_stats.csv (Table 3 source) """ import sys from pathlib import Path import numpy as np import pandas as pd sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from src import config from src.models import fit_all_models from src.references import ENGLISH_LABELS, SIM_COLS MODEL_NAMES = { "A": "Structural only", "B": "+ Description length", "C": "+ Semantic similarities (20)", "D": "Full model (B + C)", "E": "Parsimonious (significant only)", } def coefficient_table(model, variables): """Coefficients with HC3 errors, p-values and percentage price impacts.""" rows = [] for v in variables: if v not in model.params: continue coef = model.params[v] rows.append({ "variable": v, "label": ENGLISH_LABELS.get(v.replace("sim_", ""), v), "coefficient": coef, "std_error": model.bse[v], "t_value": model.tvalues[v], "p_value": model.pvalues[v], "impact_pct": (np.exp(coef) - 1) * 100, }) return pd.DataFrame(rows) def main(): df = pd.read_csv(config.ANALYSIS_CSV) print(f"{len(df):,} observations") models, specs, sig_sims = fit_all_models(df) config.RESULTS_DIR.mkdir(parents=True, exist_ok=True) comparison = pd.DataFrame({ "model": list(models), "specification": [MODEL_NAMES[k] for k in models], "r2": [m.rsquared for m in models.values()], "adj_r2": [m.rsquared_adj for m in models.values()], "aic": [m.aic for m in models.values()], "bic": [m.bic for m in models.values()], "k": [int(m.df_model) for m in models.values()], "n": [int(m.nobs) for m in models.values()], }) comparison["delta_r2_vs_A"] = comparison["r2"] - models["A"].rsquared comparison.to_csv(config.RESULTS_DIR / "model_comparison.csv", index=False) print(comparison.round(4).to_string(index=False)) # F-test of the semantic block (D vs A), as reported in the paper mA, mD = models["A"], models["D"] df_diff = mD.df_model - mA.df_model f_stat = ((mA.ssr - mD.ssr) / df_diff) / (mD.ssr / mD.df_resid) from scipy import stats f_pval = 1 - stats.f.cdf(f_stat, df_diff, mD.df_resid) print(f"\nJoint F-test (D vs A): F = {f_stat:.2f}, p = {f_pval:.2e}") print(f"Significant similarities in D (p<0.05): {len(sig_sims)}/{len(SIM_COLS)}") coefficient_table(mD, specs["D"]).to_csv( config.RESULTS_DIR / "coefficients_model_D.csv", index=False) coefficient_table(models["E"], specs["E"]).to_csv( config.RESULTS_DIR / "coefficients_model_E.csv", index=False) desc_vars = ["price", "log_price"] + config.STRUCTURAL_VARS + ["remarks_length"] df[desc_vars].describe().T.to_csv(config.RESULTS_DIR / "descriptive_stats.csv") sim_stats = df[SIM_COLS].describe().T sim_stats.index = [ENGLISH_LABELS[c.replace("sim_", "")] for c in sim_stats.index] sim_stats["corr_log_price"] = [ df[c].corr(df["log_price"]) for c in SIM_COLS ] sim_stats.to_csv(config.RESULTS_DIR / "similarity_stats.csv") print(f"\n-> {config.RESULTS_DIR}/model_comparison.csv, coefficients_model_D.csv, " f"coefficients_model_E.csv, descriptive_stats.csv, similarity_stats.csv") if __name__ == "__main__": main()