Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
where $X_{ik}$ includes structural attributes (sqft, bedrooms, age), location variables (distance to CBD, school quality), and neighborhood characteristics. The implicit price of attribute $k$ is $\partial P/\partial X_k = \beta_k \cdot P$.
Repeat-Sales Index
For two sales of property $i$ at times $s$ and $t$:
where $D_\tau$ are time dummies and $\delta_\tau$ estimates log price changes. The Case-Shiller weighted repeat-sales accounts for variance growing with holding period.
Affordability Measures
Price-to-Income Ratio (PIR): $\text{PIR} = P / Y_{\text{median}}$
Housing Affordability Index (HAI): $\text{HAI} = \frac{\text{Qualifying Income}}{Y_{\text{median}}} \times 100$, where qualifying income = monthly payment × 12 / 0.28
import numpy as np
import pandas as pd
import statsmodels.api as sm
from sklearn.ensemble import GradientBoostingRegressor
import matplotlib.pyplot as plt
print("Housing market analysis environment ready")
Core Workflow
Step 1: Hedonic Regression with Spatial Effects
import numpy as np
import pandas as pd
import statsmodels.api as sm
from scipy.spatial.distance import cdist
import matplotlib.pyplot as plt
# -----------------------------------------------------------------# Simulate hedonic dataset: 1000 transactions# -----------------------------------------------------------------
np.random.seed(42)
n = 1000# Location: x, y coordinates in a 20km x 20km city
x = np.random.uniform(0, 20, n)
y = np.random.uniform(0, 20, n)
# Distance to CBD at (10, 10)
cbd_dist = np.sqrt((x - 10)**2 + (y - 10)**2)
# Structural attributes
sqft = np.random.lognormal(7.0, 0.4, n) # 330-2700 sqft
bedrooms = np.random.choice([1, 2, 3, 4, 5], n, p=[0.1, 0.25, 0.35, 0.2, 0.1])
age = np.random.randint(0, 80, n).astype(float)
garage = np.random.binomial(1, 0.6, n)
school_rating = np.random.uniform(3, 10, n)
# True hedonic model (log-linear)
ln_P = (
11.0# intercept ~ $60k baseline
+ 0.7 * np.log(sqft) # size elasticity 0.7
+ 0.05 * bedrooms # each bedroom +5%
- 0.005 * age # depreciation
+ 0.10 * garage # garage premium
+ 0.06 * school_rating # school quality
- 0.04 * cbd_dist # CBD gradient: -4%/km
+ np.random.normal(0, 0.12, n) # unexplained
)
price = np.exp(ln_P)
df = pd.DataFrame({
"price": price, "sqft": sqft, "bedrooms": bedrooms,
"age": age, "garage": garage, "school_rating": school_rating,
"cbd_dist": cbd_dist, "x": x, "y": y
})
# -----------------------------------------------------------------# OLS hedonic regression# -----------------------------------------------------------------
df["ln_price"] = np.log(df["price"])
df["ln_sqft"] = np.log(df["sqft"])
features = ["ln_sqft", "bedrooms", "age", "garage", "school_rating", "cbd_dist"]
X_reg = sm.add_constant(df[features])
ols = sm.OLS(df["ln_price"], X_reg).fit(cov_type="HC3")
print("=== OLS Hedonic Regression ===")
print(ols.summary().tables[1])
print(f"\nAdjusted R²: {ols.rsquared_adj:.4f}")
# Marginal implicit prices at median house
median_price = df["price"].median()
print(f"\nMedian house price: ${median_price:,.0f}")
print(f"Implicit price of 1 extra sqft: ${ols.params['ln_sqft'] * median_price / df['sqft'].median():,.0f}")
print(f"CBD gradient: {ols.params['cbd_dist']*100:.1f}% per km")
# -----------------------------------------------------------------# Bid-rent gradient visualization# -----------------------------------------------------------------
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
# Price vs CBD distance
axes[0].scatter(df["cbd_dist"], df["ln_price"], alpha=0.3, s=5, c="steelblue")
d_line = np.linspace(0, 14, 100)
axes[0].plot(d_line, ols.params["const"] + ols.params["cbd_dist"] * d_line +
ols.params["ln_sqft"] * np.log(df["sqft"].median()) +
ols.params["bedrooms"] * 3 + ols.params["school_rating"] * 7,
"r-", lw=2, label="Bid-rent gradient")
axes[0].set_xlabel("Distance from CBD (km)")
axes[0].set_ylabel("ln(Price)")
axes[0].set_title("Bid-Rent Gradient")
axes[0].legend()
# Price heatmap on city gridfrom scipy.interpolate import griddata
xi = np.linspace(0, 20, 100)
yi = np.linspace(0, 20, 100)
Xi, Yi = np.meshgrid(xi, yi)
Pi = griddata((df["x"], df["y"]), df["price"], (Xi, Yi), method="linear")
im = axes[1].contourf(Xi, Yi, Pi / 1e6, levels=20, cmap="RdYlGn_r")
axes[1].plot(10, 10, "k*", ms=15, label="CBD")
axes[1].set_title("Price Surface ($ million)")
plt.colorbar(im, ax=axes[1])
axes[1].legend()
# Residual spatial pattern
residuals = ols.resid
sc = axes[2].scatter(df["x"], df["y"], c=residuals, cmap="RdBu_r",
vmin=-0.3, vmax=0.3, s=10)
axes[2].plot(10, 10, "k*", ms=15)
axes[2].set_title("OLS Residuals (Spatial Pattern?)")
plt.colorbar(sc, ax=axes[2], label="Residual")
plt.tight_layout()
plt.savefig("hedonic_regression.png", dpi=150, bbox_inches="tight")
plt.close()
print("\nFigure saved: hedonic_regression.png")
Step 2: Repeat-Sales House Price Index
import numpy as np
import pandas as pd
import statsmodels.api as sm
import matplotlib.pyplot as plt
# -----------------------------------------------------------------# Simulate repeat sales dataset# 500 properties, each sold 2-3 times over 10 years (40 quarters)# -----------------------------------------------------------------
np.random.seed(42)
n_properties = 500
n_quarters = 40# True price index (simulates a housing boom-bust cycle)
true_index = np.zeros(n_quarters)
true_index[:20] = np.linspace(0, 0.8, 20) # 80% appreciation over 5 years
true_index[20:30] = np.linspace(0.8, 0.5, 10) # 30% crash
true_index[30:] = np.linspace(0.5, 0.7, 10) # partial recovery
records = []
for prop_id inrange(n_properties):
# Two sales: first sale in 0-19, second in 10-39
t1 = np.random.randint(0, 25)
t2 = np.random.randint(t1 + 4, n_quarters)
base_ln_price = np.random.normal(12.5, 0.5) # property fixed effect# Prices at each sale time
noise1 = np.random.normal(0, 0.08)
noise2 = np.random.normal(0, 0.08)
ln_p1 = base_ln_price + true_index[t1] + noise1
ln_p2 = base_ln_price + true_index[t2] + noise2
records.append({"prop_id": prop_id, "t": t1, "ln_price": ln_p1})
records.append({"prop_id": prop_id, "t": t2, "ln_price": ln_p2})
sales_df = pd.DataFrame(records).sort_values(["prop_id", "t"])
# -----------------------------------------------------------------# Construct repeat-sales pairs# -----------------------------------------------------------------
pairs = []
for prop_id, grp in sales_df.groupby("prop_id"):
grp = grp.reset_index(drop=True)
for i inrange(len(grp) - 1):
pairs.append({
"prop_id": prop_id,
"t_sell": int(grp.loc[i+1, "t"]),
"t_buy": int(grp.loc[i, "t"]),
"ln_price_diff": grp.loc[i+1, "ln_price"] - grp.loc[i, "ln_price"]
})
pairs_df = pd.DataFrame(pairs)
print(f"Repeat-sale pairs: {len(pairs_df)}")
# -----------------------------------------------------------------# Case-Shiller OLS repeat-sales regression# Time dummies: omit period 0 as base# -----------------------------------------------------------------
periods = list(range(1, n_quarters)) # periods 1..39defbuild_repeat_sales_matrix(pairs_df, periods):
"""Build design matrix for repeat-sales regression.
Each row: ln(P_sell) - ln(P_buy) = sum of time dummies between buy and sell.
Returns X (design matrix) and y (price changes).
"""
n = len(pairs_df)
X = np.zeros((n, len(periods)))
y = pairs_df["ln_price_diff"].values.copy()
period_map = {p: i for i, p inenumerate(periods)}
for row_idx, row inenumerate(pairs_df.itertuples()):
for tau inrange(row.t_buy + 1, row.t_sell + 1):
if tau in period_map:
X[row_idx, period_map[tau]] = 1return X, y
X_rs, y_rs = build_repeat_sales_matrix(pairs_df, periods)
# OLS (no constant — natural normalization: index[0] = 0)
result_rs = sm.OLS(y_rs, X_rs).fit()
index_ols = np.concatenate([[0.0], result_rs.params]) # prepend 0 for period 0print(f"R² of repeat-sales regression: {result_rs.rsquared:.4f}")
print(f"Peak index level: {index_ols.max():.3f} at period {np.argmax(index_ols)}")
# -----------------------------------------------------------------# Visualization# -----------------------------------------------------------------
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].plot(true_index, "b--", label="True Index", lw=2)
axes[0].plot(index_ols, "r-", label="OLS Repeat-Sales", lw=2)
axes[0].set_xlabel("Quarter")
axes[0].set_ylabel("Log Price Change (base=0)")
axes[0].set_title("Repeat-Sales House Price Index")
axes[0].legend()
axes[0].axhline(0, color="gray", lw=0.8)
# Distribution of holding period lengths
holding = pairs_df["t_sell"] - pairs_df["t_buy"]
axes[1].hist(holding, bins=20, color="steelblue", edgecolor="black")
axes[1].set_xlabel("Holding Period (quarters)")
axes[1].set_ylabel("Frequency")
axes[1].set_title("Distribution of Holding Periods")
plt.tight_layout()
plt.savefig("repeat_sales_index.png", dpi=150, bbox_inches="tight")
plt.close()
print("\nFigure saved: repeat_sales_index.png")