SPB Git forge

spb/wp22_uqo

Public
6commits 1branches 0releases
142.4 MBsize
maindefault branch
2 h agolast push
TeX 55.1% Python 44.2%
3.9 KB · 68 lines python
Raw Blame History
1# Author: Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca2"""Index panel assembly (protocol §17 schema), normalisation, growth rates, reliability grades."""3from __future__ import annotations45import numpy as np6import polars as pl78SCHEMA_COLS = ["geography_id", "geography_name", "geography_level", "property_type", "month", "index_value", "base_period",9               "log_monthly_change", "monthly_pct_change", "annual_pct_change", "actual_transaction_count", "effective_sample_size",10               "local_data_weight", "lower_95", "upper_95", "reliability_grade", "direct_or_model_assisted", "data_cutoff",11               "publication_vintage", "model_version", "index_method", "partial_month_flag", "suppression_flag"]121314def normalise(log_index: np.ndarray, months: list[str], base: str = "2021") -> tuple[np.ndarray, str]:15    """2021 average = 100 (geometric mean over the available 2021 months) or a single month = 100."""16    li = np.asarray(log_index, float)17    if len(base) == 4:18        sel = [i for i, m in enumerate(months) if m.startswith(base) and not np.isnan(li[i])]19        if not sel:20            return np.full_like(li, np.nan), f"{base} average = 100 (unavailable)"21        c = float(np.mean(li[sel]))22        label = f"{base} average = 100" + ("" if len(sel) == 12 else f" ({len(sel)} months available)")23    else:24        i = months.index(base); c = li[i]; label = f"{base} = 100"25    return 100.0 * np.exp(li - c), label262728def growth(log_index: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:29    li = np.asarray(log_index, float)30    d1 = np.full_like(li, np.nan); d1[1:] = li[1:] - li[:-1]31    d12 = np.full_like(li, np.nan); d12[12:] = li[12:] - li[:-12]32    return d1, 100 * (np.exp(d1) - 1), 100 * (np.exp(d12) - 1)333435def grade(n: np.ndarray, ess: np.ndarray, se_log: np.ndarray) -> np.ndarray:36    """A–E reliability grade per month from effective sample size and posterior se of the log level.37    Thresholds are anchored on the Monte Carlo results (results/simulation) — see paper §7."""38    g = np.full(len(n), "E", dtype=object)39    g[(ess >= 15) & (se_log < 0.05)] = "D"40    g[(ess >= 30) & (se_log < 0.035)] = "C"41    g[(ess >= 75) & (se_log < 0.025)] = "B"42    g[(ess >= 150) & (se_log < 0.015)] = "A"43    return g444546def build_rows(geo_id: str, geo_name: str, level: str, cls: str, months: list[str], log_index: np.ndarray, var: np.ndarray,47               n: np.ndarray, ess: np.ndarray, weight: np.ndarray, method: str, data_cutoff: str, vintage: str, model_version: str,48               base: str = "2021", partial_last: bool = True, direct_rule: str = "n>0") -> pl.DataFrame:49    idx, base_label = normalise(log_index, months, base)50    d1, p1, p12 = growth(log_index)51    se = np.sqrt(np.maximum(np.asarray(var, float), 0))52    lo = idx * np.exp(-1.96 * se); hi = idx * np.exp(1.96 * se)53    gr = grade(np.asarray(n, float), np.asarray(ess, float), se)54    direct = np.where(np.asarray(n) > 0, "direct", "model_assisted")55    direct = np.where(np.asarray(ess) < 5, "insufficiently_identified", direct)56    suppress = (np.asarray(ess) < 5) | np.isnan(idx)57    partial = np.zeros(len(months), bool)58    if partial_last:59        partial[-1] = True60    return pl.DataFrame({61        "geography_id": geo_id, "geography_name": geo_name, "geography_level": level, "property_type": cls, "month": months,62        "index_value": idx, "base_period": base_label, "log_monthly_change": d1, "monthly_pct_change": p1, "annual_pct_change": p12,63        "actual_transaction_count": np.asarray(n, float).astype(int), "effective_sample_size": np.asarray(ess, float),64        "local_data_weight": np.asarray(weight, float), "lower_95": lo, "upper_95": hi, "reliability_grade": gr.astype(str),65        "direct_or_model_assisted": direct.astype(str), "data_cutoff": data_cutoff, "publication_vintage": vintage,66        "model_version": model_version, "index_method": method, "partial_month_flag": partial, "suppression_flag": suppress,67    }).select(SCHEMA_COLS)68