fmt
Some checks failed
CI / Check (push) Failing after 4m14s
Build and publish Docker image / build-and-push (push) Successful in 4m52s

This commit is contained in:
Andras Schmelczer 2026-07-14 14:04:44 +01:00
parent cf348c3ea4
commit d1faad314b
14 changed files with 55 additions and 35 deletions

View file

@ -315,7 +315,8 @@
"ax1.plot(yr, piv[\"existing\"].to_numpy(), marker=\"o\", label=\"Existing houses\", color=\"#0f766e\")\n",
"ax1.plot(yr, piv[\"new\"].to_numpy(), marker=\"s\", label=\"New-build houses\", color=\"#d97706\")\n",
"ax1.set_title(\"Median £/sqm by sale year\")\n",
"ax1.set_xlabel(\"Year\"); ax1.set_ylabel(\"£/sqm\")\n",
"ax1.set_xlabel(\"Year\")\n",
"ax1.set_ylabel(\"£/sqm\")\n",
"ax1.yaxis.set_major_formatter(mticker.StrMethodFormatter(\"£{x:,.0f}\"))\n",
"ax1.legend()\n",
"\n",
@ -323,9 +324,11 @@
"ax2.bar(yr, piv[\"premium_pct\"].to_numpy(), color=colors)\n",
"ax2.axhline(0, color=\"black\", lw=0.8)\n",
"ax2.set_title(\"New-build premium (median £/sqm, new vs existing)\")\n",
"ax2.set_xlabel(\"Year\"); ax2.set_ylabel(\"Premium %\")\n",
"ax2.set_xlabel(\"Year\")\n",
"ax2.set_ylabel(\"Premium %\")\n",
"ax2.yaxis.set_major_formatter(mticker.PercentFormatter(decimals=0))\n",
"plt.tight_layout(); plt.show()\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"piv.select(\"year\", \"existing\", \"new\", \"premium_pct\", \"n_new\", \"n_existing\")"
]
@ -426,7 +429,8 @@
"for y, v, nnew in zip(range(len(labels)), vals, area_prem[\"n_new\"].to_list()):\n",
" ax.text(v + (0.4 if v >= 0 else -0.4), y, f\"n={nnew}\", va=\"center\",\n",
" ha=\"left\" if v >= 0 else \"right\", fontsize=8, color=\"#555\")\n",
"plt.tight_layout(); plt.show()\n",
"plt.tight_layout()\n",
"plt.show()\n",
"area_prem"
]
},
@ -523,10 +527,12 @@
"ax.axvline(np.median(old_cagr), color=\"#0f766e\", ls=\"--\", lw=1.2)\n",
"ax.axvline(np.median(new_cagr), color=\"#d97706\", ls=\"--\", lw=1.2)\n",
"ax.set_title(\"Distribution of annualised resale growth (CAGR)\")\n",
"ax.set_xlabel(\"CAGR % per year\"); ax.set_ylabel(\"density\")\n",
"ax.set_xlabel(\"CAGR % per year\")\n",
"ax.set_ylabel(\"density\")\n",
"ax.xaxis.set_major_formatter(mticker.PercentFormatter(decimals=0))\n",
"ax.legend()\n",
"plt.tight_layout(); plt.show()"
"plt.tight_layout()\n",
"plt.show()"
]
},
{
@ -603,16 +609,19 @@
"\n",
"area_cagr = cagr_by(\"area\")\n",
"labels = area_cagr[\"area\"].to_list()\n",
"x = np.arange(len(labels)); w = 0.4\n",
"x = np.arange(len(labels))\n",
"w = 0.4\n",
"fig, ax = plt.subplots(figsize=(10, 4.5))\n",
"ax.bar(x - w/2, area_cagr[\"existing_cagr\"].to_numpy(), w, label=\"Existing\", color=\"#0f766e\")\n",
"ax.bar(x + w/2, area_cagr[\"newbuild_cagr\"].to_numpy(), w, label=\"New-build\", color=\"#d97706\")\n",
"ax.set_xticks(x); ax.set_xticklabels(labels)\n",
"ax.set_xticks(x)\n",
"ax.set_xticklabels(labels)\n",
"ax.set_title(\"Median resale CAGR by postal area\")\n",
"ax.set_ylabel(\"CAGR % per year\")\n",
"ax.yaxis.set_major_formatter(mticker.PercentFormatter(decimals=0))\n",
"ax.legend()\n",
"plt.tight_layout(); plt.show()\n",
"plt.tight_layout()\n",
"plt.show()\n",
"area_cagr"
]
},
@ -699,22 +708,25 @@
" area_cagr.select(\"area\", \"cagr_gap\", \"newbuild_cagr\", \"existing_cagr\"), on=\"area\", how=\"inner\"\n",
")\n",
"fig, ax = plt.subplots(figsize=(8, 6))\n",
"xs = combined[\"premium_pct\"].to_numpy(); ys = combined[\"cagr_gap\"].to_numpy()\n",
"xs = combined[\"premium_pct\"].to_numpy()\n",
"ys = combined[\"cagr_gap\"].to_numpy()\n",
"ax.scatter(xs, ys, s=70, color=\"#d97706\", zorder=3)\n",
"for a, xv, yv in zip(combined[\"area\"].to_list(), xs, ys):\n",
" ax.annotate(a, (xv, yv), textcoords=\"offset points\", xytext=(6, 4), fontsize=10)\n",
"ax.axhline(0, color=\"black\", lw=0.8); ax.axvline(0, color=\"black\", lw=0.8)\n",
"ax.axhline(0, color=\"black\", lw=0.8)\n",
"ax.axvline(0, color=\"black\", lw=0.8)\n",
"ax.set_xlabel(\"New-build £/sqm premium at purchase\")\n",
"ax.set_ylabel(\"New-build CAGR minus existing CAGR (pp/yr)\")\n",
"ax.xaxis.set_major_formatter(mticker.PercentFormatter(decimals=0))\n",
"ax.set_title(\"Premium paid vs appreciation gap, by London postal area\")\n",
"plt.tight_layout(); plt.show()\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"# Headline numbers for the writeup.\n",
"hl = summary.to_dicts()\n",
"new_row = next(r for r in hl if r[\"is_newbuild_prop\"])\n",
"old_row = next(r for r in hl if not r[\"is_newbuild_prop\"])\n",
"print(f\"London terraced/detached, repeat sales:\")\n",
"print(\"London terraced/detached, repeat sales:\")\n",
"print(f\" existing median CAGR: {old_row['median_cagr_pct']}%/yr (n={old_row['n']:,})\")\n",
"print(f\" new-build median CAGR: {new_row['median_cagr_pct']}%/yr (n={new_row['n']:,})\")\n",
"print(f\" appreciation gap: {round(new_row['median_cagr_pct']-old_row['median_cagr_pct'],2)} pp/yr\")\n",