2026-04-07 20:26:19 +02:00
{
"cells": [
{
"cell_type": "markdown",
"id": "13c6141d",
"metadata": {},
"source": [
"# Behavioral Clustering of Carmignac Investors\n",
"\n",
"This notebook implements two complementary clustering analyses:\n",
"\n",
"| | Scope | Approach |\n",
"|---|---|---|\n",
"| **Part 1** | All active accounts (~7,000) | Global behavioral clustering |\n",
2026-04-10 11:05:13 +02:00
"| **Part 2** | Top ~400 accounts (AUM > €5M) | Clustering with performance reactivity features |\n",
2026-04-07 20:26:19 +02:00
"\n",
"Both analyses share the same preprocessing pipeline (RobustScaler, MAD winsorization) and visualization conventions (robust z-score heatmaps).\n",
"\n",
"---\n",
"**Structure:**\n",
"1. Imports & Configuration\n",
"2. Data Loading\n",
"3. Monthly Panel Construction\n",
"4. Feature Engineering\n",
"5. **Part 1** — Global Clustering (all accounts)\n",
" - 5a. Feature selection & preprocessing\n",
" - 5b. K-selection & clustering\n",
" - 5c. Cluster profiles (behavioral + allocation)\n",
" - 5d. Asset-type sub-clustering & cross-analysis\n",
"6. **Part 2** — Top 400 Accounts Clustering\n",
" - 6a. Account selection & feature engineering\n",
" - 6b. K-selection & clustering\n",
" - 6c. Cluster profiles & churn analysis\n",
"7. Cross-Analysis: Global vs Top 400\n"
]
},
{
"cell_type": "markdown",
"id": "28e588fe",
"metadata": {},
"source": [
"---\n",
"## 1. Imports & Configuration\n"
]
},
{
"cell_type": "code",
2026-04-11 10:11:35 +02:00
"execution_count": 88,
2026-04-07 20:26:19 +02:00
"id": "3bc1ffe0",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import s3fs\n",
"import warnings\n",
"warnings.filterwarnings(\"ignore\")\n",
"\n",
"os.environ[\"AWS_ACCESS_KEY_ID\"] = 'UMMV3Z72A70MCCSRV17O'\n",
"os.environ[\"AWS_SECRET_ACCESS_KEY\"] = 'wBFxaez78UPNW3BtchZOf4f238ZNXKnCexeGufaa'\n",
"os.environ[\"AWS_SESSION_TOKEN\"] = 'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiJVTU1WM1o3MkE3ME1DQ1NSVjE3TyIsImFjciI6IjAiLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJhdWQiOlsibWluaW8iLCJhY2NvdW50Il0sImF1dGhfdGltZSI6MTc3NTEzNTA4NiwiYXpwIjoib255eGlhLW1pbmlvIiwiZW1haWwiOiJzYXJhaC50aG91bXlyZUBlbnNhZS5mciIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE3NzYzNDQ3NDksImZhbWlseV9uYW1lIjoiVEhPVU1ZUkUiLCJnaXZlbl9uYW1lIjoiU2FyYWgiLCJncm91cHMiOlsiYmRjLWRhdGEiLCJiZGMtY2FybWlnbmFjLWczIl0sImlhdCI6MTc3NTEzNTE0OCwiaXNzIjoiaHR0cHM6Ly9hdXRoLmdyb3VwZS1nZW5lcy5mci9yZWFsbXMvZ2VuZXMiLCJqdGkiOiJlZGY1ZDQ1OC1hYzkxLTQ5NTAtYmI5Ny0zNjMwNWY1MTQwYTIiLCJuYW1lIjoiU2FyYWggVEhPVU1ZUkUiLCJwb2xpY3kiOiJzdHNvbmx5IiwicHJlZmVycmVkX3VzZXJuYW1lIjoic3Rob3VteXJlLWVuc2FlIiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwiZGVmYXVsdC1yb2xlcy1nZW5lcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsInNpZCI6IjMzMjg4YjJjLTlhMjAtNDNhOS1iMDlhLTdlMjc1OWQ1NjIxNiIsInN1YiI6ImVhYWVkN2QyLWM4MjYtNGIxNC05MzczLTYwYjNhODhlMWFiNiIsInR5cCI6IkJlYXJlciJ9.rffoTJijRiGK2DCDhXj5y8R31DRH1LWkTwuH_1lvU9qN_xJSTmBIM4uGR_zp7XpMnq_ePwVhlkoWN15cNUgjMA'\n",
"os.environ[\"AWS_DEFAULT_REGION\"] = 'us-east-1'\n",
"\n",
"fs = s3fs.S3FileSystem(\n",
" client_kwargs={'endpoint_url': 'https://'+'minio-simple.lab.groupe-genes.fr'},\n",
" key = os.environ[\"AWS_ACCESS_KEY_ID\"], \n",
" secret = os.environ[\"AWS_SECRET_ACCESS_KEY\"], \n",
" token = os.environ[\"AWS_SESSION_TOKEN\"])\n",
"\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
2026-04-10 11:05:13 +02:00
"from scipy import stats\n",
2026-04-07 20:26:19 +02:00
"\n",
"from sklearn.preprocessing import RobustScaler\n",
"from sklearn.cluster import KMeans\n",
"from sklearn.metrics import (\n",
" silhouette_score, davies_bouldin_score,\n",
" pairwise_distances, adjusted_rand_score\n",
")\n",
"from sklearn.linear_model import LinearRegression\n",
"\n",
"sns.set_style(\"whitegrid\")\n",
"pd.set_option(\"display.max_columns\", 200)\n",
"pd.set_option(\"display.max_rows\", 200)\n",
"\n",
"EPS = 1e-9\n",
"RANDOM_STATE = 42"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "69d2dc25",
"metadata": {},
"outputs": [],
"source": [
"# Column names\n",
"ID_COL = \"Registrar Account - ID\"\n",
"ISIN_COL = \"Product - Isin\"\n",
"FUND_COL = \"Product - Fund\"\n",
"ASSET_COL = \"Product - Asset Type\"\n",
"FLOW_DATE_COL = \"Centralisation Date\"\n",
"AUM_DATE_COL = \"Centralisation Date\"\n",
"FLOW_QTY_COL = \"Quantity - NetFlows\"\n",
"FLOW_SUB_COL = \"Quantity - Subscription\"\n",
"FLOW_RED_COL = \"Quantity - Redemption\"\n",
"AUM_QTY_COL = \"Quantity - AUM\"\n",
"AUM_VAL_COL = \"Value - AUM €\"\n",
"REGION_COL = \"Registrar Account - Region\"\n",
"COUNTRY_COL = \"RegistrarAccount - Country\"\n",
"NAV_DATE_COL = \"Dat\"\n",
"NAV_ISIN_COL = \"Isin\"\n",
"NAV_PRICE_COL = \"Price (TF PartPrice)\"\n",
"NAV_BENCH_COL = \"PriceBench\"\n",
"RATE_DATE_COL = \"Date\"\n",
"RATE_VAL_COL = \"Yld to Maturity\"\n",
"\n",
"PATH_NAV = \"s3://projet-bdc-data/carmignac/Data Modélisation/Nav/NAV_Bench_data.csv\"\n",
"PATH_RATES = \"s3://projet-bdc-data/carmignac/Data Modélisation/market data/esterRates.csv\""
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "bf5b7a0a",
"metadata": {},
"outputs": [],
"source": [
2026-04-10 11:05:13 +02:00
"# UTILITIES\n",
2026-04-07 20:26:19 +02:00
"def robust_zscore(s):\n",
" med = np.nanmedian(s)\n",
" mad = np.nanmedian(np.abs(s - med))\n",
" if mad == 0 or np.isnan(mad):\n",
" return np.zeros(len(s))\n",
" return (s - med) / (1.4826 * mad)\n",
"\n",
"def plot_heatmap(dfc, profile_vars, cluster_col, title, figsize=(16, 4)):\n",
" \"\"\"Cluster signature heatmap using robust z-scores, capped at ±3 for readability.\"\"\"\n",
" dfc_viz = dfc[profile_vars + [cluster_col]].copy()\n",
" for col in profile_vars:\n",
" vals = pd.to_numeric(dfc_viz[col], errors=\"coerce\").to_numpy(dtype=float)\n",
" lo = np.nanpercentile(vals, 2)\n",
" hi = np.nanpercentile(vals, 98)\n",
" dfc_viz[col] = np.clip(vals, lo, hi)\n",
" prof = dfc_viz.groupby(cluster_col)[profile_vars].median()\n",
" prof_z = prof.apply(lambda col: robust_zscore(col.values), axis=0)\n",
" prof_z = prof_z.clip(-3, 3) # cap for readability\n",
" plt.figure(figsize=figsize)\n",
" sns.heatmap(prof_z, cmap=\"RdBu_r\", center=0, annot=True, fmt=\".2f\",\n",
" xticklabels=profile_vars,\n",
" yticklabels=[f\"Cluster {i}\" for i in range(len(prof))])\n",
" plt.title(title)\n",
" plt.xticks(rotation=45, ha=\"right\")\n",
" plt.tight_layout()\n",
" plt.show()\n",
" return prof\n",
"\n",
"def winsorize_mad(series, n_sigma=3):\n",
" \"\"\"Winsorize using MAD n-sigma rule. Falls back to p95 clip when MAD~0.\"\"\"\n",
" vals = pd.to_numeric(series, errors=\"coerce\").to_numpy(dtype=float)\n",
" med = np.nanmedian(vals)\n",
" mad = np.nanmedian(np.abs(vals - med)) * 1.4826\n",
" if mad > 0:\n",
" return np.clip(vals, med - n_sigma * mad, med + n_sigma * mad)\n",
" else:\n",
" return np.clip(vals, 0, np.nanpercentile(vals, 95))\n",
"\n",
"def add_months_since_last_tx(dfc, df_month, id_col, suffix=\"\"):\n",
" \"\"\"Adds months_since_last_tx[suffix] to dfc.\"\"\"\n",
" col_name = f\"months_since_last_tx{suffix}\"\n",
" reference_date = df_month[\"month\"].max()\n",
" last_active = (\n",
" df_month[df_month[\"active_month\"] == 1]\n",
" .groupby(id_col)[\"month\"]\n",
" .max()\n",
" .reset_index(name=\"last_active_month\")\n",
" )\n",
" last_active[col_name] = (\n",
" (reference_date.to_period(\"M\") -\n",
" last_active[\"last_active_month\"].dt.to_period(\"M\"))\n",
" .apply(lambda x: x.n)\n",
" )\n",
" dfc = dfc.merge(last_active[[id_col, col_name]], on=id_col, how=\"left\")\n",
" max_months = dfc[col_name].max()\n",
" dfc[col_name] = dfc[col_name].fillna(max_months + 1)\n",
" return dfc"
]
},
{
"cell_type": "markdown",
"id": "312153e6",
"metadata": {},
"source": [
"---\n",
"## 2. Data Loading\n",
"\n",
"Three data sources are used:\n",
"- **AUM** (repaired): monthly share quantities per account and ISIN\n",
"- **Flows**: daily net transactions, aggregated to monthly\n",
"- **NAV / Rates**: fund performance and interest rate data for enrichment\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "011958df",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"flows: (2574461, 26)\n",
"aum: (4824814, 19)\n",
"nav: (623914, 6)\n"
]
}
],
"source": [
"df_flows = pd.read_csv(\"flows.csv\", low_memory=False)\n",
"df_aum = pd.read_csv(\n",
" \"s3://projet-bdc-carmignac-g3/paco/AUM_repaired.csv\", low_memory=False\n",
")\n",
"df_nav = pd.read_csv(PATH_NAV, sep=\";\")\n",
"df_rates = pd.read_csv(PATH_RATES, sep=\";\")\n",
"\n",
"# Date parsing\n",
"for df, col in [\n",
" (df_flows, FLOW_DATE_COL), (df_aum, AUM_DATE_COL),\n",
" (df_nav, NAV_DATE_COL), (df_rates, RATE_DATE_COL)\n",
"]:\n",
" df[col] = pd.to_datetime(df[col], errors=\"coerce\")\n",
" df[\"month\"] = df[col].dt.to_period(\"M\").dt.to_timestamp()\n",
"\n",
"for col in [FLOW_QTY_COL, FLOW_SUB_COL, FLOW_RED_COL]:\n",
" df_flows[col] = pd.to_numeric(df_flows[col], errors=\"coerce\")\n",
"for col in [AUM_QTY_COL, AUM_VAL_COL]:\n",
" df_aum[col] = pd.to_numeric(df_aum[col], errors=\"coerce\")\n",
"for col in [NAV_PRICE_COL, NAV_BENCH_COL]:\n",
" df_nav[col] = pd.to_numeric(df_nav[col], errors=\"coerce\")\n",
"df_rates[RATE_VAL_COL] = pd.to_numeric(df_rates[RATE_VAL_COL], errors=\"coerce\")\n",
"\n",
"for df in [df_flows, df_aum]:\n",
" df[ISIN_COL] = df[ISIN_COL].astype(str).str.strip()\n",
"df_nav[NAV_ISIN_COL] = df_nav[NAV_ISIN_COL].astype(str).str.strip()\n",
"\n",
"# Remove technical accounts (not investable)\n",
"df_flows = df_flows[~df_flows[ID_COL].isin(\n",
" [\"Off Distribution\", \"Private Clients\", \"Private Client\"]\n",
")]\n",
"df_aum = df_aum[~df_aum[ID_COL].isin(\n",
" [\"Off Distribution\", \"Private Clients\", \"Private Client\"]\n",
")]\n",
"\n",
"print(\"flows:\", df_flows.shape)\n",
"print(\"aum: \", df_aum.shape)\n",
"print(\"nav: \", df_nav.shape)"
]
},
{
"cell_type": "markdown",
"id": "d34f5ecf",
"metadata": {},
"source": [
"---\n",
"## 3. Monthly Panel Construction\n",
"\n",
"A full outer join of AUM and flows at `(account, ISIN, month)` granularity, enriched with NAV returns and interest rate changes.\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "25f3dce4",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Panel shape: (4754355, 24)\n"
]
}
],
"source": [
"df_flows_m = (\n",
" df_flows\n",
" .dropna(subset=[ID_COL, ISIN_COL, \"month\"])\n",
" .assign(\n",
" gross_flow_qty = lambda x: x[FLOW_QTY_COL].abs(),\n",
" sub_qty = lambda x: x[FLOW_SUB_COL].fillna(0),\n",
" red_qty = lambda x: x[FLOW_RED_COL].fillna(0)\n",
" )\n",
" .groupby([ID_COL, ISIN_COL, \"month\"], as_index=False)\n",
" .agg(\n",
" net_flow_qty = (FLOW_QTY_COL, \"sum\"),\n",
" gross_flow_qty = (\"gross_flow_qty\", \"sum\"),\n",
" sub_qty = (\"sub_qty\", \"sum\"),\n",
" red_qty = (\"red_qty\", \"sum\"),\n",
" n_tx = (FLOW_QTY_COL, \"size\"),\n",
" region = (REGION_COL, \"last\"),\n",
" country = (COUNTRY_COL, \"last\")\n",
" )\n",
")\n",
"\n",
"df_aum_m = (\n",
" df_aum\n",
" .dropna(subset=[ID_COL, ISIN_COL, \"month\"])\n",
" .groupby([ID_COL, ISIN_COL, \"month\"], as_index=False)\n",
" .agg(\n",
" aum_qty = (AUM_QTY_COL, \"sum\"),\n",
" aum_val = (AUM_VAL_COL, \"sum\"),\n",
" fund = (FUND_COL, \"last\"),\n",
" asset_type = (ASSET_COL, \"last\"),\n",
" region = (REGION_COL, \"last\"),\n",
" country = (COUNTRY_COL, \"last\")\n",
" )\n",
")\n",
"\n",
"keys = pd.concat([\n",
" df_flows_m[[ID_COL, ISIN_COL, \"month\"]],\n",
" df_aum_m[[ID_COL, ISIN_COL, \"month\"]]\n",
"]).drop_duplicates()\n",
"\n",
"df_rel_m = (\n",
" keys\n",
" .merge(df_aum_m, on=[ID_COL, ISIN_COL, \"month\"], how=\"left\")\n",
" .merge(df_flows_m, on=[ID_COL, ISIN_COL, \"month\"], how=\"left\",\n",
" suffixes=(\"\", \"_flow\"))\n",
")\n",
"\n",
"for c in [\"aum_qty\",\"aum_val\",\"net_flow_qty\",\"gross_flow_qty\",\n",
" \"sub_qty\",\"red_qty\",\"n_tx\"]:\n",
" df_rel_m[c] = df_rel_m[c].fillna(0)\n",
"\n",
"df_rel_m[\"region\"] = df_rel_m[\"region\"].fillna(df_rel_m.get(\"region_flow\"))\n",
"df_rel_m[\"country\"] = df_rel_m[\"country\"].fillna(df_rel_m.get(\"country_flow\"))\n",
"df_rel_m[\"active_rel_month\"] = (df_rel_m[\"gross_flow_qty\"] > 0).astype(int)\n",
"df_rel_m[\"holding_rel_month\"] = (df_rel_m[\"aum_qty\"] > 0).astype(int)\n",
"df_rel_m[\"flow_to_aum_rel\"] = df_rel_m[\"net_flow_qty\"] / (df_rel_m[\"aum_qty\"].abs() + EPS)\n",
"df_rel_m[\"turnover_rel\"] = df_rel_m[\"gross_flow_qty\"] / (df_rel_m[\"aum_qty\"].abs() + EPS)\n",
"\n",
"# --- NAV returns & interest rates ---\n",
"df_nav_m = (\n",
" df_nav\n",
" .dropna(subset=[NAV_ISIN_COL, \"month\", NAV_PRICE_COL])\n",
" .sort_values([NAV_ISIN_COL, \"month\"])\n",
" .groupby([NAV_ISIN_COL, \"month\"], as_index=False).tail(1).copy()\n",
")\n",
"df_nav_m[\"ret_fund_m\"] = df_nav_m.groupby(NAV_ISIN_COL)[NAV_PRICE_COL].pct_change()\n",
"df_nav_m[\"ret_bench_m\"] = df_nav_m.groupby(NAV_ISIN_COL)[NAV_BENCH_COL].pct_change()\n",
"df_nav_m[\"active_return_m\"] = df_nav_m[\"ret_fund_m\"] - df_nav_m[\"ret_bench_m\"]\n",
"df_nav_m = df_nav_m.rename(columns={NAV_ISIN_COL: ISIN_COL})[\n",
" [ISIN_COL, \"month\", \"ret_fund_m\", \"ret_bench_m\", \"active_return_m\"]\n",
"]\n",
"\n",
"df_rates_m = (\n",
" df_rates.dropna(subset=[\"month\", RATE_VAL_COL])\n",
" .sort_values(RATE_DATE_COL)\n",
" .groupby(\"month\", as_index=False).tail(1).copy()\n",
")\n",
"df_rates_m[\"delta_rate_m\"] = df_rates_m[RATE_VAL_COL].diff()\n",
"df_rates_m = df_rates_m[[\"month\", RATE_VAL_COL, \"delta_rate_m\"]]\n",
"\n",
"df_rel_m = df_rel_m.merge(df_nav_m, on=[ISIN_COL, \"month\"], how=\"left\")\n",
"df_rel_m = df_rel_m.merge(\n",
" df_rates_m[[\"month\", \"delta_rate_m\"]], on=\"month\", how=\"left\"\n",
")\n",
"for c in [\"ret_fund_m\",\"ret_bench_m\",\"active_return_m\",\"delta_rate_m\"]:\n",
" df_rel_m[c] = df_rel_m[c].fillna(0)\n",
"\n",
"print(\"Panel shape:\", df_rel_m.shape)"
]
},
{
"cell_type": "markdown",
"id": "9121da21",
"metadata": {},
"source": [
"---\n",
"## 4. Feature Engineering\n",
"\n",
"Features are built at three levels of granularity:\n",
"- **Account × month**: activity flags, turnover, drawdown\n",
"- **Account × ISIN**: entry/exit events, holding duration, performance reactivity\n",
"- **Account (static)**: aggregated behavioral summary used for clustering\n",
"\n",
"Asset type and fund composition shares are computed separately and used as **descriptive** post-clustering variables only.\n"
]
},
{
"cell_type": "code",
2026-04-11 10:11:35 +02:00
"execution_count": 86,
2026-04-07 20:26:19 +02:00
"id": "d4a01bcc",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Monthly panel shape: (931089, 22)\n",
"ISIN-level client features: (12582, 12)\n",
"Asset shares: (7473, 6)\n",
"Fund shares: (6591, 11)\n",
"df_client_base shape: (12582, 47)\n"
]
}
],
"source": [
"# 4a. Monthly account-level panel\n",
"tmp = df_rel_m.copy()\n",
"tmp[\"isin_held_flag\"] = (tmp[\"aum_qty\"] > 0).astype(int)\n",
"tmp[\"isin_active_flag\"] = (tmp[\"gross_flow_qty\"] > 0).astype(int)\n",
"\n",
"df_month = (\n",
" tmp.groupby([ID_COL, \"month\"], as_index=False)\n",
" .agg(\n",
" aum_qty = (\"aum_qty\", \"sum\"),\n",
" aum_val = (\"aum_val\", \"sum\"),\n",
" net_flow_qty = (\"net_flow_qty\", \"sum\"),\n",
" gross_flow_qty = (\"gross_flow_qty\", \"sum\"),\n",
" sub_qty = (\"sub_qty\", \"sum\"),\n",
" red_qty = (\"red_qty\", \"sum\"),\n",
" n_tx = (\"n_tx\", \"sum\"),\n",
" n_isin_held = (\"isin_held_flag\", \"sum\"),\n",
" n_isin_active = (\"isin_active_flag\", \"sum\"),\n",
" delta_rate_m = (\"delta_rate_m\", \"first\"),\n",
" ret_fund_m = (\"ret_fund_m\", \"mean\"),\n",
" region = (\"region\", \"first\"),\n",
" country = (\"country\", \"first\"),\n",
" )\n",
" .sort_values([ID_COL, \"month\"])\n",
" .reset_index(drop=True)\n",
")\n",
"\n",
"df_month[\"active_month\"] = (df_month[\"gross_flow_qty\"] > 0).astype(int)\n",
"df_month[\"flow_to_aum_m\"] = np.where(\n",
" df_month[\"aum_qty\"].abs() > 0,\n",
" df_month[\"net_flow_qty\"] / df_month[\"aum_qty\"].abs(), np.nan\n",
")\n",
"df_month[\"turnover_m\"] = np.where(\n",
" df_month[\"aum_qty\"].abs() > 0,\n",
" df_month[\"gross_flow_qty\"] / df_month[\"aum_qty\"].abs(), np.nan\n",
")\n",
"df_month[\"sub_share_m\"] = np.where(\n",
" df_month[\"gross_flow_qty\"] > 0,\n",
" df_month[\"sub_qty\"] / df_month[\"gross_flow_qty\"], np.nan\n",
")\n",
"df_month[\"red_share_m\"] = np.where(\n",
" df_month[\"gross_flow_qty\"] > 0,\n",
" df_month[\"red_qty\"] / df_month[\"gross_flow_qty\"], np.nan\n",
")\n",
"df_month[\"aum_peak_to_date\"] = df_month.groupby(ID_COL)[\"aum_qty\"].cummax()\n",
"df_month[\"aum_drawdown\"] = np.where(\n",
" df_month[\"aum_peak_to_date\"] > 0,\n",
" 1 - df_month[\"aum_qty\"] / df_month[\"aum_peak_to_date\"], np.nan\n",
")\n",
"\n",
"print(\"Monthly panel shape:\", df_month.shape)\n",
"\n",
"# 4b. ISIN-level features (entry/exit, performance reactivity)\n",
"tmp = df_rel_m.sort_values([ID_COL, ISIN_COL, \"month\"]).copy()\n",
"tmp[\"prev_aum\"] = tmp.groupby([ID_COL, ISIN_COL])[\"aum_qty\"].shift(1)\n",
"tmp[\"entry_event\"] = ((tmp[\"prev_aum\"].fillna(0) <= 0) & (tmp[\"aum_qty\"] > 0)).astype(int)\n",
"tmp[\"full_exit_event\"] = ((tmp[\"prev_aum\"] > 0) & (tmp[\"aum_qty\"] <= 0)).astype(int)\n",
"tmp[\"ret_fund_m_lag1\"] = tmp.groupby([ID_COL, ISIN_COL])[\"ret_fund_m\"].shift(1)\n",
"tmp[\"buy_on_perf\"] = ((tmp[\"net_flow_qty\"] > 0) & (tmp[\"ret_fund_m_lag1\"] > 0)).astype(int)\n",
"tmp[\"sell_on_perf\"] = ((tmp[\"net_flow_qty\"] < 0) & (tmp[\"ret_fund_m_lag1\"] < 0)).astype(int)\n",
"\n",
"df_rel_feat = (\n",
" tmp.groupby([ID_COL, ISIN_COL], as_index=False)\n",
" .agg(\n",
" rel_n_months = (\"month\", \"nunique\"),\n",
" rel_active_months = (\"active_rel_month\", \"sum\"),\n",
" rel_holding_months = (\"holding_rel_month\", \"sum\"),\n",
" rel_aum_mean = (\"aum_qty\", \"mean\"),\n",
" rel_turnover_mean = (\"turnover_rel\", \"mean\"),\n",
" rel_turnover_vol = (\"turnover_rel\", \"std\"),\n",
" rel_flow_to_aum_vol = (\"flow_to_aum_rel\", \"std\"),\n",
" rel_n_tx = (\"n_tx\", \"sum\"),\n",
" rel_full_exit_count = (\"full_exit_event\", \"sum\"),\n",
" rel_entry_count = (\"entry_event\", \"sum\"),\n",
" buy_on_perf_rate = (\"buy_on_perf\", \"mean\"),\n",
" sell_on_perf_rate = (\"sell_on_perf\", \"mean\"),\n",
" )\n",
")\n",
"\n",
"isin_aum = df_rel_feat.groupby(ID_COL)[\"rel_aum_mean\"].transform(\"sum\")\n",
"df_rel_feat[\"isin_weight\"] = np.where(\n",
" isin_aum > 0, df_rel_feat[\"rel_aum_mean\"] / isin_aum, np.nan\n",
")\n",
"hhi_isin = (\n",
" df_rel_feat.groupby(ID_COL)[\"isin_weight\"]\n",
" .apply(lambda w: np.sum(w**2))\n",
" .reset_index(name=\"hhi_isin\")\n",
")\n",
"\n",
"df_rel_client = (\n",
" df_rel_feat.groupby(ID_COL, as_index=False)\n",
" .agg(\n",
" n_isin_total = (ISIN_COL, \"nunique\"),\n",
" rel_turnover_mean_avg = (\"rel_turnover_mean\", \"mean\"),\n",
" rel_turnover_vol_avg = (\"rel_turnover_vol\", \"mean\"),\n",
" rel_flow_to_aum_vol_avg = (\"rel_flow_to_aum_vol\", \"mean\"), \n",
" full_exit_count = (\"rel_full_exit_count\", \"sum\"),\n",
" entry_count = (\"rel_entry_count\", \"sum\"),\n",
" avg_holding_months_per_isin = (\"rel_holding_months\", \"mean\"),\n",
" max_holding_months_per_isin = (\"rel_holding_months\", \"max\"),\n",
" buy_on_perf_rate_avg = (\"buy_on_perf_rate\", \"mean\"),\n",
" sell_on_perf_rate_avg = (\"sell_on_perf_rate\", \"mean\"),\n",
" )\n",
" .merge(hhi_isin, on=ID_COL, how=\"left\")\n",
")\n",
"\n",
"print(\"ISIN-level client features:\", df_rel_client.shape)\n",
"\n",
"# 4c. Asset type & fund composition shares\n",
"aum_by_asset = (\n",
" df_aum.dropna(subset=[ID_COL, ASSET_COL])\n",
" .groupby([ID_COL, ASSET_COL], as_index=False)[AUM_VAL_COL].sum()\n",
")\n",
"total_aum_acc = aum_by_asset.groupby(ID_COL)[AUM_VAL_COL].sum().rename(\"total_aum\")\n",
"aum_by_asset = aum_by_asset.merge(total_aum_acc, on=ID_COL)\n",
"aum_by_asset[\"share\"] = np.where(\n",
" aum_by_asset[\"total_aum\"] > 0,\n",
" aum_by_asset[AUM_VAL_COL] / aum_by_asset[\"total_aum\"], np.nan\n",
")\n",
"asset_shares = (\n",
" aum_by_asset\n",
" .pivot_table(index=ID_COL, columns=ASSET_COL, values=\"share\", aggfunc=\"mean\")\n",
" .fillna(0).reset_index()\n",
")\n",
"asset_shares.columns = [ID_COL] + [\n",
" f\"share_asset_{c.lower().replace(' ','_')}\" for c in asset_shares.columns[1:]\n",
"]\n",
"\n",
"aum_by_fund = (\n",
" df_aum.dropna(subset=[ID_COL, FUND_COL])\n",
" .groupby([ID_COL, FUND_COL], as_index=False)[AUM_VAL_COL].sum()\n",
")\n",
"aum_by_fund = aum_by_fund.merge(total_aum_acc, on=ID_COL)\n",
"aum_by_fund[\"share\"] = np.where(\n",
" aum_by_fund[\"total_aum\"] > 0,\n",
" aum_by_fund[AUM_VAL_COL] / aum_by_fund[\"total_aum\"], np.nan\n",
")\n",
"top_funds = aum_by_fund.groupby(FUND_COL)[AUM_VAL_COL].sum().nlargest(10).index\n",
"fund_shares = (\n",
" aum_by_fund[aum_by_fund[FUND_COL].isin(top_funds)]\n",
" .pivot_table(index=ID_COL, columns=FUND_COL, values=\"share\", aggfunc=\"mean\")\n",
" .fillna(0).reset_index()\n",
")\n",
"fund_shares.columns = [ID_COL] + [\n",
" f\"share_fund_{c.lower().replace(' ','_')[:30]}\" for c in fund_shares.columns[1:]\n",
"]\n",
"\n",
"print(\"Asset shares:\", asset_shares.shape)\n",
"print(\"Fund shares: \", fund_shares.shape)\n",
"\n",
"# 4d. Static client-level features\n",
"df_client_base = (\n",
" df_month.groupby(ID_COL, as_index=False)\n",
" .agg(\n",
" n_months = (\"month\", \"nunique\"),\n",
" n_active_months = (\"active_month\", \"sum\"),\n",
" flow_freq = (\"active_month\", \"mean\"),\n",
" aum_qty_mean = (\"aum_qty\", \"mean\"),\n",
" aum_qty_median = (\"aum_qty\", \"median\"),\n",
" aum_qty_max = (\"aum_qty\", \"max\"),\n",
" aum_qty_last = (\"aum_qty\", \"last\"),\n",
" net_flow_qty_sum = (\"net_flow_qty\", \"sum\"),\n",
" gross_flow_qty_sum = (\"gross_flow_qty\", \"sum\"),\n",
" gross_flow_qty_mean= (\"gross_flow_qty\", \"mean\"),\n",
" sub_qty_sum = (\"sub_qty\", \"sum\"),\n",
" red_qty_sum = (\"red_qty\", \"sum\"),\n",
" n_tx_total = (\"n_tx\", \"sum\"),\n",
" avg_n_isin_held = (\"n_isin_held\", \"mean\"),\n",
" max_n_isin_held = (\"n_isin_held\", \"max\"),\n",
" net_flow_qty_vol = (\"net_flow_qty\", \"std\"),\n",
" aum_drawdown_last = (\"aum_drawdown\", \"last\"),\n",
" aum_drawdown_max = (\"aum_drawdown\", \"max\"),\n",
" region = (\"region\", \"last\"),\n",
" country = (\"country\", \"last\"),\n",
" )\n",
")\n",
"df_client_base[\"net_flow_qty_vol\"] = df_client_base[\"net_flow_qty_vol\"].fillna(0)\n",
"\n",
"df_client_base = (\n",
" df_client_base\n",
" .merge(df_rel_client, on=ID_COL, how=\"left\")\n",
" .merge(asset_shares, on=ID_COL, how=\"left\")\n",
" .merge(fund_shares, on=ID_COL, how=\"left\")\n",
")\n",
"\n",
"print(\"df_client_base shape:\", df_client_base.shape)"
]
},
{
"cell_type": "markdown",
"id": "c383042d",
"metadata": {},
"source": [
"---\n",
"## 5. Part 1 — Global Clustering (All Accounts)\n",
"\n",
"### Objective\n",
2026-04-10 11:05:13 +02:00
"Segment the full client base into behavioral profiles using 8 selected features. The analysis covers ~7,000 accounts with at least 6 months of history.\n",
2026-04-07 20:26:19 +02:00
"\n",
"### Feature set\n",
"| Feature | Description |\n",
"|---|---|\n",
"| `flow_freq` | Proportion of months with at least one transaction |\n",
"| `gross_flow_to_aum` | Total gross flows relative to mean AUM (clipped p90, log-transformed) |\n",
"| `n_isin_total` | Total number of distinct ISINs held over the period |\n",
"| `avg_holding_months_per_isin` | Average holding duration per ISIN |\n",
"| `exit_rate_per_isin` | Average number of full exits per ISIN |\n",
"| `flow_direction_balance` | Ratio of net to gross flows (buyer vs seller signal) |\n",
2026-04-11 10:11:35 +02:00
"| `aum_qty_mean` | Log mean AUM — only size variable retained |\n",
2026-04-07 20:26:19 +02:00
"| `months_since_last_tx` | Months since last transaction (recency signal, most discriminant feature) |\n",
"\n",
"### Preprocessing\n",
"- MAD winsorization (3σ ) for long-tailed distributions\n",
"- Clip p90 + log-transform for `gross_flow_to_aum` and `flow_freq`\n",
"- RobustScaler before K-means\n",
"- Geographic and allocation variables excluded from clustering (used post-hoc as descriptors)\n"
]
},
{
"cell_type": "code",
2026-04-11 10:11:35 +02:00
"execution_count": 90,
2026-04-07 20:26:19 +02:00
"id": "0d8b7276-8213-4667-979c-d97b3729162a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Accounts after quality filters: 7177\n",
"Accounts: 7177 | Features: 8\n",
"Points > 5 std after scaling: 0 (0.0%)\n",
" k inertia silhouette davies_bouldin\n",
" 2 20240.673342 0.421930 0.973123\n",
" 3 16711.420111 0.241169 1.543030\n",
" 4 14679.824806 0.231005 1.511161\n",
" 5 13213.816987 0.228496 1.409421\n",
" 6 12021.187284 0.223428 1.417110\n",
" 7 11112.958987 0.229601 1.420989\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1500x400 with 3 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
2026-04-10 11:05:13 +02:00
"K=2 | sil=0.4219 | db=0.9731\n",
" n_comptes pct\n",
"cluster_k2 \n",
"1 1269 17.7\n",
"2 5908 82.3\n",
2026-04-07 20:26:19 +02:00
"K=4 | sil=0.2310 | db=1.5112\n",
" n_comptes pct\n",
"cluster_k4 \n",
2026-04-10 11:05:13 +02:00
"1 1478 20.6\n",
"2 1820 25.4\n",
"3 1171 16.3\n",
"4 2708 37.7\n"
2026-04-07 20:26:19 +02:00
]
}
],
"source": [
"# 2f. Engineered ratios\n",
"dfc = df_client_base.copy()\n",
2026-04-11 10:11:35 +02:00
"\n",
2026-04-07 20:26:19 +02:00
"dfc[\"gross_flow_to_aum\"] = np.where(\n",
" dfc[\"aum_qty_mean\"] > 1,\n",
" dfc[\"gross_flow_qty_sum\"] / dfc[\"aum_qty_mean\"], np.nan\n",
")\n",
"dfc[\"flow_direction_balance\"] = np.where(\n",
" dfc[\"gross_flow_qty_sum\"] > 0,\n",
" dfc[\"net_flow_qty_sum\"] / dfc[\"gross_flow_qty_sum\"], np.nan\n",
")\n",
"dfc[\"sub_share_mean\"] = np.where(\n",
" dfc[\"gross_flow_qty_sum\"] > 0,\n",
" dfc[\"sub_qty_sum\"] / dfc[\"gross_flow_qty_sum\"], np.nan\n",
")\n",
"dfc[\"redemption_bias\"] = np.where(\n",
" dfc[\"gross_flow_qty_sum\"] > 0,\n",
" (dfc[\"red_qty_sum\"] - dfc[\"sub_qty_sum\"]) / dfc[\"gross_flow_qty_sum\"], np.nan\n",
")\n",
"dfc[\"exit_rate_per_isin\"] = np.where(\n",
" dfc[\"n_isin_total\"] > 0,\n",
" dfc[\"full_exit_count\"] / dfc[\"n_isin_total\"], np.nan\n",
")\n",
"dfc[\"aum_final_to_peak\"] = np.where(\n",
" dfc[\"aum_qty_max\"] > 0,\n",
" dfc[\"aum_qty_last\"] / dfc[\"aum_qty_max\"], np.nan\n",
")\n",
"dfc[\"aum_drawdown_last\"] = dfc[\"aum_drawdown_last\"].clip(0, 1)\n",
"\n",
"for col in [\"aum_qty_mean\", \"gross_flow_qty_sum\", \"n_tx_total\"]:\n",
" dfc[f\"log_{col}\"] = np.log1p(dfc[col].clip(lower=0))\n",
"\n",
"# Filtres qualité\n",
"dfc = dfc[(dfc[\"n_months\"] >= 6) & (dfc[\"aum_qty_mean\"] > 0)].copy()\n",
"for col in [\"aum_qty_mean\", \"gross_flow_qty_sum\", \"n_tx_total\"]:\n",
" cap = dfc[col].quantile(0.99)\n",
" dfc = dfc[dfc[col] <= cap].copy()\n",
"\n",
"# Géographie\n",
"top_countries = dfc[\"country\"].fillna(\"Unknown\").value_counts().head(10).index\n",
"top_regions = dfc[\"region\"].fillna(\"Unknown\").value_counts().head(10).index\n",
"dfc[\"country_grp\"] = np.where(dfc[\"country\"].isin(top_countries), dfc[\"country\"], \"Other\")\n",
"dfc[\"region_grp\"] = np.where(dfc[\"region\"].isin(top_regions), dfc[\"region\"], \"Other\")\n",
"\n",
"# months_since_last_tx\n",
"dfc = add_months_since_last_tx(dfc, df_month, ID_COL)\n",
"\n",
"print(f\"Accounts after quality filters: {len(dfc)}\")\n",
"\n",
"# 5a. Feature selection & preprocessing\n",
"base_features_global = [\n",
" \"flow_freq\",\n",
" \"gross_flow_to_aum\",\n",
" \"n_isin_total\",\n",
" \"avg_holding_months_per_isin\",\n",
" \"exit_rate_per_isin\",\n",
" \"flow_direction_balance\",\n",
2026-04-11 10:11:35 +02:00
" \"aum_qty_mean\",\n",
2026-04-07 20:26:19 +02:00
" \"months_since_last_tx\",\n",
"]\n",
"all_features_global = [c for c in base_features_global if c in dfc.columns]\n",
"\n",
"dfc_clean = dfc.copy() # working copy for preprocessing\n",
"\n",
"for col in [\"flow_direction_balance\", \"months_since_last_tx\"]:\n",
" if col in dfc_clean.columns:\n",
" dfc_clean[col] = dfc_clean[col].fillna(0)\n",
"\n",
"for col in [\"n_isin_total\", \"exit_rate_per_isin\",\n",
" \"avg_holding_months_per_isin\", \"months_since_last_tx\"]:\n",
" if col in dfc_clean.columns:\n",
" dfc_clean[col] = winsorize_mad(dfc_clean[col], n_sigma=3)\n",
"\n",
"col = \"gross_flow_to_aum\"\n",
"if col in dfc_clean.columns:\n",
" vals = dfc_clean[col].to_numpy(dtype=float)\n",
" vals = np.clip(vals, 0, np.nanpercentile(vals, 90))\n",
" dfc_clean[col] = np.log1p(vals)\n",
"\n",
"col = \"flow_freq\"\n",
"if col in dfc_clean.columns:\n",
" vals = dfc_clean[col].to_numpy(dtype=float)\n",
" dfc_clean[col] = np.log1p(np.clip(vals, 0, None))\n",
"\n",
2026-04-11 10:11:35 +02:00
"col = \"aum_qty_mean\"\n",
2026-04-07 20:26:19 +02:00
"if col in dfc_clean.columns:\n",
2026-04-11 10:11:35 +02:00
" vals = dfc_clean[col].to_numpy(dtype=float)\n",
" dfc_clean[col] = winsorize_mad(\n",
" pd.Series(np.log1p(np.clip(vals, 0, None))), n_sigma=3\n",
" )\n",
" \n",
2026-04-07 20:26:19 +02:00
"X_global = dfc_clean[all_features_global].copy()\n",
"X_global = X_global.loc[:, ~X_global.columns.duplicated()]\n",
"X_global = X_global.fillna(X_global.median())\n",
"\n",
"scaler_global = RobustScaler()\n",
"X_global_scaled = scaler_global.fit_transform(X_global)\n",
"\n",
"# Diagnostic\n",
"X_df = pd.DataFrame(X_global_scaled, columns=X_global.columns)\n",
"extreme = (X_df.abs() > 5).any(axis=1).sum()\n",
"print(f\"Accounts: {X_global.shape[0]} | Features: {X_global.shape[1]}\")\n",
"print(f\"Points > 5 std after scaling: {extreme} ({extreme/len(X_df):.1%})\")\n",
"\n",
"# 5b. K-selection\n",
"rows = []\n",
"for k in range(2, 8):\n",
" km = KMeans(n_clusters=k, n_init=50, random_state=RANDOM_STATE)\n",
" labels = km.fit_predict(X_global_scaled)\n",
" rows.append({\n",
" \"k\": k, \"inertia\": km.inertia_,\n",
" \"silhouette\": silhouette_score(X_global_scaled, labels),\n",
" \"davies_bouldin\": davies_bouldin_score(X_global_scaled, labels),\n",
" })\n",
"df_kdiag_global = pd.DataFrame(rows)\n",
"print(df_kdiag_global.to_string(index=False))\n",
"\n",
"fig, axes = plt.subplots(1, 3, figsize=(15, 4))\n",
"for ax, col, title in zip(axes,\n",
" [\"inertia\", \"silhouette\", \"davies_bouldin\"],\n",
" [\"Elbow / Inertia\", \"Silhouette (higher=better)\", \"Davies-Bouldin (lower=better)\"]):\n",
" ax.plot(df_kdiag_global[\"k\"], df_kdiag_global[col], marker=\"o\")\n",
" ax.set_title(title); ax.set_xlabel(\"K\")\n",
"plt.suptitle(\"K-selection — Global Clustering\")\n",
2026-04-10 11:05:13 +02:00
"plt.tight_layout()\n",
"plt.savefig(\"k_selection_global.png\", dpi=300, bbox_inches=\"tight\")\n",
"plt.show()\n",
2026-04-07 20:26:19 +02:00
"\n",
"# 5c. Final clustering K=4\n",
"RESULTS_GLOBAL = {}\n",
2026-04-10 11:05:13 +02:00
"for k in [2, 4]:\n",
2026-04-07 20:26:19 +02:00
" km = KMeans(n_clusters=k, n_init=50, random_state=RANDOM_STATE)\n",
2026-04-10 11:05:13 +02:00
" dfc[f\"cluster_k{k}\"] = km.fit_predict(X_global_scaled) + 1\n",
2026-04-07 20:26:19 +02:00
" RESULTS_GLOBAL[k] = {\n",
" \"model\": km,\n",
" \"silhouette\": silhouette_score(X_global_scaled, dfc[f\"cluster_k{k}\"]),\n",
" \"davies_bouldin\": davies_bouldin_score(X_global_scaled, dfc[f\"cluster_k{k}\"]),\n",
" }\n",
" print(f\"K={k} | sil={RESULTS_GLOBAL[k]['silhouette']:.4f} \"\n",
" f\"| db={RESULTS_GLOBAL[k]['davies_bouldin']:.4f}\")\n",
" counts = dfc[f\"cluster_k{k}\"].value_counts().sort_index()\n",
" props = counts / counts.sum() * 100\n",
" print(pd.DataFrame({\"n_comptes\": counts, \"pct\": props.round(1)}))"
]
},
{
"cell_type": "code",
2026-04-11 10:11:35 +02:00
"execution_count": 91,
2026-04-10 11:05:13 +02:00
"id": "8188f322-c38b-4f1c-b67f-899b5fb71482",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"================================================================================\n",
"PREPROCESSING DIAGNOSTIC — Global clustering (K=4, 8 features)\n",
"================================================================================\n",
"\n",
"--- Check 1: Extreme values after scaling (threshold: 5 std) ---\n",
" n_extreme pct_extreme STATUS\n",
"flow_freq 0 0.0 OK\n",
"gross_flow_to_aum 0 0.0 OK\n",
"n_isin_total 0 0.0 OK\n",
"avg_holding_months_per_isin 0 0.0 OK\n",
"exit_rate_per_isin 0 0.0 OK\n",
"flow_direction_balance 0 0.0 OK\n",
2026-04-11 10:11:35 +02:00
"aum_qty_mean 0 0.0 OK\n",
2026-04-10 11:05:13 +02:00
"months_since_last_tx 0 0.0 OK\n",
"\n",
"→ 0 feature(s) above 1% threshold\n",
"\n",
"--- Check 2 + 3: Discriminant power (η²) and ANOVA significance ---\n",
" A feature passes if η² > 0.05 OR ANOVA p < 0.05\n",
" feature eta2 F_stat p_value note STATUS\n",
" flow_freq 0.8126 10368.87 0.00000 OK\n",
"avg_holding_months_per_isin 0.5713 3185.95 0.00000 OK\n",
" months_since_last_tx 0.4229 1751.79 0.00000 OK\n",
" flow_direction_balance 0.3431 1062.83 0.00000 OK\n",
" n_isin_total 0.2364 740.07 0.00000 OK\n",
" exit_rate_per_isin 0.1653 473.65 0.00000 OK\n",
2026-04-11 10:11:35 +02:00
" aum_qty_mean 0.0797 207.17 0.00000 OK\n",
2026-04-10 11:05:13 +02:00
" gross_flow_to_aum 0.0022 5.01 0.00181 low η² but significant ANOVA — retained OK\n",
"\n",
"→ 0 feature(s) failing both η² and ANOVA criteria\n"
]
}
],
"source": [
"# ============================================================\n",
"# PREPROCESSING DIAGNOSTIC \n",
"# ============================================================\n",
"\n",
"def feature_diagnostic_full(dfc, features, X_scaled, feature_names_scaled,\n",
" cluster_col=None, label=\"\"):\n",
" \"\"\"\n",
" Full preprocessing diagnostic:\n",
" - Check 1: extreme values after scaling (> 5 std)\n",
" - Check 2: variance ratio eta² per feature\n",
" - Check 3: one-way ANOVA F-statistic per feature\n",
" \n",
" A feature passes if:\n",
" - Check 1: pct extreme < 1%\n",
" - Check 2 + 3 combined: eta² > 0.05 OR ANOVA p < 0.05\n",
" (a feature with low eta² but significant ANOVA is retained —\n",
" it may have modest effect size but genuine discriminant power)\n",
" \"\"\"\n",
" print(f\"\\n{'='*80}\")\n",
" print(f\"PREPROCESSING DIAGNOSTIC — {label}\")\n",
" print(f\"{'='*80}\")\n",
"\n",
" # ── Check 1 — Extreme values after scaling ────────────────────────────\n",
" print(\"\\n--- Check 1: Extreme values after scaling (threshold: 5 std) ---\")\n",
" X_df = pd.DataFrame(X_scaled, columns=feature_names_scaled)\n",
" extreme_by_feat = (X_df.abs() > 5).sum().sort_values(ascending=False)\n",
" extreme_pct = extreme_by_feat / len(X_df) * 100\n",
"\n",
" check1 = pd.DataFrame({\n",
" \"n_extreme\": extreme_by_feat,\n",
" \"pct_extreme\": extreme_pct.round(2),\n",
" \"STATUS\": [\"⚠ FAIL\" if p > 1 else \"OK\" for p in extreme_pct]\n",
" })\n",
" print(check1.to_string())\n",
" n_fail_1 = (check1[\"STATUS\"] == \"⚠ FAIL\").sum()\n",
" print(f\"\\n→ {n_fail_1} feature(s) above 1% threshold\")\n",
"\n",
" # ── Check 2 + 3 — η² and ANOVA (combined) ────────────────────────────\n",
" print(\"\\n--- Check 2 + 3: Discriminant power (η²) and ANOVA significance ---\")\n",
" print(\" A feature passes if η² > 0.05 OR ANOVA p < 0.05\")\n",
" \n",
" if cluster_col and cluster_col in dfc.columns:\n",
" rows = []\n",
" for col in features:\n",
" if col not in dfc.columns:\n",
" continue\n",
" vals = pd.to_numeric(dfc[col], errors=\"coerce\").to_numpy(dtype=float)\n",
" labels = dfc[cluster_col].to_numpy()\n",
" finite = np.isfinite(vals)\n",
" if finite.sum() < 10:\n",
" continue\n",
" vals_f = vals[finite]\n",
" labels_f = labels[finite]\n",
"\n",
" # η²\n",
" grand_mean = np.mean(vals_f)\n",
" ss_total = np.sum((vals_f - grand_mean) ** 2)\n",
" ss_between = sum(\n",
" np.sum(labels_f == k) *\n",
" (np.mean(vals_f[labels_f == k]) - grand_mean) ** 2\n",
" for k in np.unique(labels_f)\n",
" )\n",
" eta2 = ss_between / (ss_total + 1e-9)\n",
"\n",
" # ANOVA\n",
" groups = [vals_f[labels_f == k] for k in np.unique(labels_f)\n",
" if (labels_f == k).sum() > 1]\n",
" f_stat, p_val = stats.f_oneway(*groups) if len(groups) >= 2 \\\n",
" else (np.nan, np.nan)\n",
"\n",
" # Combined status: fail only if BOTH criteria fail\n",
" fail = (eta2 < 0.05) and (np.isnan(p_val) or p_val > 0.05)\n",
" note = \"\"\n",
" if eta2 < 0.05 and not fail:\n",
" note = \"low η² but significant ANOVA — retained\"\n",
"\n",
" rows.append({\n",
" \"feature\": col,\n",
" \"eta2\": round(eta2, 4),\n",
" \"F_stat\": round(f_stat, 2) if not np.isnan(f_stat) else np.nan,\n",
" \"p_value\": round(p_val, 6) if not np.isnan(p_val) else np.nan,\n",
" \"note\": note,\n",
" \"STATUS\": \"⚠ FAIL\" if fail else \"OK\",\n",
" })\n",
"\n",
" df_diag = pd.DataFrame(rows).sort_values(\"eta2\", ascending=False)\n",
" print(df_diag.to_string(index=False))\n",
" n_fail_23 = (df_diag[\"STATUS\"] == \"⚠ FAIL\").sum()\n",
" print(f\"\\n→ {n_fail_23} feature(s) failing both η² and ANOVA criteria\")\n",
"\n",
" else:\n",
" print(\"No cluster column provided — skipping.\")\n",
" df_diag = None\n",
" n_fail_23 = 0\n",
"\n",
" return check1, df_diag\n",
"\n",
"\n",
"# ── Run diagnostic on global clustering features ──────────────────────────\n",
"check1_g, diag_g = feature_diagnostic_full(\n",
" dfc = dfc,\n",
" features = base_features_global,\n",
" X_scaled = X_global_scaled,\n",
" feature_names_scaled = list(X_global.columns),\n",
" cluster_col = \"cluster_k4\",\n",
" label = \"Global clustering (K=4, 8 features)\"\n",
")"
]
},
{
"cell_type": "code",
2026-04-11 10:11:35 +02:00
"execution_count": 92,
2026-04-07 20:26:19 +02:00
"id": "1c0ea35a",
"metadata": {},
"outputs": [
{
"data": {
2026-04-11 10:11:35 +02:00
"image/png": "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
2026-04-07 20:26:19 +02:00
"text/plain": [
2026-04-10 11:05:13 +02:00
"<Figure size 1600x400 with 2 Axes>"
2026-04-07 20:26:19 +02:00
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== Median behavioral features — K=4 ===\n",
2026-04-11 10:11:35 +02:00
" gross_flow_to_aum flow_freq flow_direction_balance n_isin_total avg_holding_months_per_isin exit_rate_per_isin aum_qty_mean months_since_last_tx\n",
"cluster_k4 \n",
"1 1.159 0.043 -1.000 3.0 60.000 0.400 174.302 27.0\n",
"2 1.476 0.012 -1.000 3.0 12.000 0.714 29.191 127.0\n",
"3 5.351 0.617 -0.006 12.0 28.897 0.667 6391.751 3.0\n",
"4 7.889 0.071 0.000 1.0 11.333 1.000 195.345 69.0\n"
2026-04-07 20:26:19 +02:00
]
},
{
"data": {
2026-04-10 11:05:13 +02:00
"image/png": "iVBORw0KGgoAAAANSUhEUgAABO8AAAGGCAYAAAAjJaUdAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAA/aNJREFUeJzs3XdYU9cbwPFvWCooMl04EJShiKIo4hb3Vtwi7q11j2KrdVWtiraOuvdWFPe2jlqruFoXLhwVtA5AhqCs/P7gRzQmICAKbd/P8+RRTs49973JvRlvzlAolUolQgghhBBCCCGEEEKIHEcnuwMQQgghhBBCCCGEEEJoJ8k7IYQQQgghhBBCCCFyKEneCSGEEEIIIYQQQgiRQ0nyTgghhBBCCCGEEEKIHEqSd0IIIYQQQgghhBBC5FCSvBNCCCGEEEIIIYQQIoeS5J0QQgghhBBCCCGEEDmUJO+EEEIIIYQQQgghhMihJHknhBBCCCGEEEIIIUQOJck7IYQQ/xrnz5/H3t6enTt3ZncoWcLDwwNvb2+1Mm9vbzw8PLIpooz7+uuvsbe3z9YYFixYgL29PcHBwdkaR1rs7e35+uuvszuM/5zg4GDs7e1ZsGBBtmyflp07d2Jvb8/58+ezvG34d51zOekaVyqVdOzYkVGjRmV3KOkyePBgjfcZIYQQOY8k74QQQnySlITZ+zcXFxc8PT1Zu3YtiYmJ2R1ilouMjGTBggWZ/lIdERGBs7Mz9vb27Nq1K2uDywY7d+5kzZo12R1GjvPs2TNmzZpFixYtcHFxwcnJCQ8PD0aPHs3vv//+xeMJDg5mwYIFBAYGfvF9f0m//PILQ4YMoVatWjg5OeHi4kKTJk0YP358tjzuX5Kcc9lv3759XL9+na+++kqt3MPDg+bNm2vUj46OxsvLC3t7e5YtW5bl8SQlJdGxY0fs7e3p37+/xv1fffUVFy5c4Pjx41m+byGEEFlHL7sDEEII8e/QvHlzatWqhVKp5Pnz5/j7+zN9+nTu3bvH1KlTszu8LBUZGcnChQsZMmQIbm5uGd5+7969xMXFUbRoUXbs2EHr1q2zPsgvyN/fn5CQEHr06KFx39SpU5k8efKXDyqbnTx5kpEjRxIXF0fjxo3p2LEjuXLlIiQkhOPHj9OjRw+WLVtG7dq1v1hMISEhLFy4ECsrKxwdHb/Yfr+UN2/eMGrUKI4dO0bJkiVp3bo1xYoVIzExkYcPH3LixAl27NiBr6+v1iTKP91/+ZwbOHAg/fr1w8DA4LPtI70WLVpEnTp1sLa2/mjdsLAwevfuza1bt5g6dSodOnTI8ng2bdrEnTt3Ur3fwcGBKlWq8PPPP1OvXr0s378QQoisIck7IYQQWaJMmTK0atVK9XeXLl1o0qQJ27dvZ9iwYVhYWGjdLjo6mrx5836pMHMEPz8/3NzcqFevHtOnT+fx48cUK1Ysu8P6LPT19bM7hC/u7t27DBs2jPz587N69WpsbW3V7h82bBh79uzJEYmGrJTd1/KkSZM4duwYvXv3ZvTo0ejoqA8wGTduHEePHiV37tzZFOHn818/5/T09NDTy/6vNb///jsPHjxI15DZp0+f0rNnT4KDg/H19aVp06ZZHs/ff//N3LlzGTp0KDNnzky1XqtWrRg/fjw3btygbNmyWR6HEEKITyfDZoUQQnwWefPmxcXFBaVSyePHj4F3c7jdvHmT3r17U6lSJVq2bKna5sKFC/Ts2ZNKlSrh7OxMmzZt2L59u9b2jx07RuvWrSlXrhy1a9fmxx9/JCEhQaNeWnMhaZtTDuDcuXP069cPNzc3ypUrR7169Rg/fjxhYWGcP39e1Tth4cKFqqHC6Z2H7saNGwQGBtKmTRuaN2+Onp4efn5+6do2LRl57B49eoSPj49qWGGNGjUYOHAg169fV9U5c+YMw4cPp169ejg7O+Pq6kqvXr0ICAhQa8vDw4OAgABCQkLUhk6nDClObc67W7duMXjwYNVj3LRpU5YvX64xzDpl+6ioKL777jvc3d0pV64cnTp14s8//8zQYxQbG8u0adOoXr06zs7OtG/fXm0oYVxcHFWrVqVTp05at1+xYgX29vZcuHAhzf3Mnz+fN2/eMG3aNI0kCoBCoaBVq1a4u7un2kZac6lpO6efPn2Kj48PdevWxcnJCXd3dzp16oS/vz+QPLS5W7duAPj4+Kiep/fPf6VSyaZNm/D09KR8+fK4uLjg7e3NuXPnUo3twIEDeHp64uzszLRp09J8XD6nW7du4e/vT8WKFRkzZoxG4g6SH/eGDRtSq1atj7aXkJDAsmXLaNq0KeXKlcPNzY3Bgwdz+/btVLfZt28fLVq0oFy5ctSpU4cFCxZovCYFBQUxadIkmjVrhouLC+XLl8fT0zPVazW9/uvnnLb4Usru37/P3LlzVa93LVu25NSpUxrHGBsby4wZM6hRowbOzs506NCB33//PUPzdh48eBBdXV2qV6+eZr0HDx7QpUsX/v77bxYvXvxZEncAkydPplixYqrnITUp18TBgwc/SxxCCCE+Xfb/RCWEEOJfSalU8ujRIwBMTU1V5U+ePKF79+40btyYhg0bEhMTA7ybp8rCwoKePXuSN29e9u/fz7fffktwcDAjRoxQtXH06FG++uorrKysGDx4MLq6uuzcuVPrF7KM2rJlC5MmTaJgwYJ06tQJKysrnjx5wokTJ3j27Bm2trb4+PgwY8YMGjRoQIMGDQAwMjJKV/t+fn4YGhrSsGFDDA0NqVOnDrt27WLYsGFaEw7pkZHH7tq1a/To0YOEhATatWtH6dKliYiIICAggCtXruDk5AQkD4WNiIigdevWFCpUiGfPnrF9+3Z69OjBunXrcHV1BWD8+PH4+voSHh6Oj4+Paj/aEgjvx+Dt7Y2enh5eXl5YWFhw4sQJ5syZw61bt/D19dXYpnfv3piZmTF48GBevXrF6tWr6devH8ePH093b69x48aho6ND3759iY6OZuvWrfTp04fly5dTrVo1DAwMaNOmDatWreL+/fvY2Niobb9jxw6sra2pXLlyqvt4+/YtJ0+epHDhwulKEmWFhIQEevbsybNnz+jSpQvW1tZER0dz+/ZtLl68SJs2bahcuTIDBgxgyZIldOzYkUqVKgGo9YgdM2YM+/fvp1GjRnh6ehIXF8fevXvp1asXCxYs0BhSd+zYMdavX0/nzp3p1KlTtva6O3LkCADt2rVDoVB8cnujR4/m4MGDVK9enc6dO/Py5Us2btxIp06d2LhxI2XKlFGr/8svv/D48WPV+fzLL7+wcOFCnjx5wowZM1T1AgICuHjxInXq1KFo0aLExsZy6NAhvv32W8LCwrTOSfYxcs6l7euvv0ZPT49evXoRHx/P2rVrGTx4MIcOHaJo0aKqesOGDePUqVPUr1+fatWqERwczODBg9XqfMyFCxcoVaoUhoaGqdYJDAykd+/exMfHs2rVKipWrKi1XlhYWLr3my9fPo1ezocOHeLEiRNs2bIFXV3dNLe3tLTEyspK48cZIYQQOYck74QQQmSJ2NhY1ZeN58+fs2HDBm7dukWFChXU5v4JDg5m2rRptG/fXlWWmJjI1KlTMTQ0ZPv27RQsWBBIHnrbrVs3li1bRps2bbC2tiYxMZHvv/+e/Pnzs337dszMzADo1KmTWi++zPj777+ZNm0aNjY2bNmyBWNjY9V9w4cPJykpCR0dHerXr8+MGTOwt7dXGyr8MW/fvmXfvn00atRI9eWudevWHD16lF9//TVTc1Fl5LFTKpX4+PgQFxfH9u3bcXBwULXTv39/kpKSVH+ntPm+Tp060axZM5YuXapK3tWvX5+1a9fy9u3bdD8W33//PXFxcWzZskUVQ9euXRk+fDj79u2jXbt2Gj2EypQpw6RJk1R/29raquqn1lPuQ7q6umzcuFE1dLBdu3Y0adKEqVOnqnqcdOjQgVWrVuHn58fYsWNV2166dIn79+8zevToNPfx8OFD4uLi1B7bz+3evXs8ePCA0aNH07dvX611ihUrRrVq1ViyZAkVKlTQeK6OHj3K3r17mTJlCh07dlSVd+vWjQ4
2026-04-07 20:26:19 +02:00
"text/plain": [
2026-04-10 11:05:13 +02:00
"<Figure size 1400x400 with 2 Axes>"
2026-04-07 20:26:19 +02:00
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== Median allocation — K=4 ===\n",
2026-04-10 11:05:13 +02:00
" share_asset_fixed_income share_asset_diversified share_asset_equity share_fund_carmignac_patrimoine\n",
"cluster_k4 \n",
"1 0.000 0.373 0.227 0.260\n",
"2 0.000 0.326 0.099 0.156\n",
"3 0.284 0.207 0.154 0.149\n",
"4 0.768 0.000 0.000 0.000\n"
2026-04-07 20:26:19 +02:00
]
}
],
"source": [
"# 5d. Cluster profiles\n",
"profile_vars_behavior = [\n",
" \"gross_flow_to_aum\", \"flow_freq\", \"flow_direction_balance\",\n",
" \"n_isin_total\", \"avg_holding_months_per_isin\", \"exit_rate_per_isin\",\n",
2026-04-11 10:11:35 +02:00
" \"aum_qty_mean\", \"months_since_last_tx\",\n",
2026-04-07 20:26:19 +02:00
"]\n",
"profile_vars_behavior = [c for c in profile_vars_behavior if c in dfc.columns]\n",
"\n",
2026-04-10 11:05:13 +02:00
"prof_global = plot_heatmap_annotated(\n",
" dfc,\n",
" profile_vars = profile_vars_behavior,\n",
" cluster_col = \"cluster_k4\",\n",
" title = \"Cluster Signatures — Global Clustering (K=4)\\n\"\n",
" \"Color: robust z-score | Values: cluster medians\",\n",
" figsize = (16, 4)\n",
2026-04-07 20:26:19 +02:00
")\n",
"print(\"\\n=== Median behavioral features — K=4 ===\")\n",
2026-04-10 11:05:13 +02:00
"print(prof_global.round(3).to_string())\n",
2026-04-07 20:26:19 +02:00
"\n",
"profile_vars_allocation = [\n",
" c for c in [\n",
" \"share_asset_fixed_income\", \"share_asset_diversified\",\n",
" \"share_asset_equity\", \"share_fund_carmignac_patrimoine\",\n",
" ] if c in dfc.columns\n",
"]\n",
"\n",
2026-04-10 11:05:13 +02:00
"prof_alloc = plot_heatmap_annotated(\n",
" dfc,\n",
" profile_vars = profile_vars_allocation,\n",
" cluster_col = \"cluster_k4\",\n",
" title = \"Product Allocation by Cluster — Global Clustering (K=4)\\n\"\n",
" \"Color: robust z-score | Values: cluster medians | Post-clustering descriptor\",\n",
" figsize = (14, 4)\n",
2026-04-07 20:26:19 +02:00
")\n",
"print(\"\\n=== Median allocation — K=4 ===\")\n",
2026-04-10 11:05:13 +02:00
"print(prof_alloc.round(3).to_string())"
2026-04-07 20:26:19 +02:00
]
},
2026-04-08 17:41:37 +02:00
{
"cell_type": "markdown",
"id": "9fb2786e",
"metadata": {},
"source": [
"---\n",
"### 5e. Asset-Type Sub-Clustering & Cross-Analysis\n",
"\n",
"A complementary clustering is performed **within each asset type** (Fixed Income, Diversified, Equity, Alternative) using the same behavioral features restricted to each asset's positions. The cross-analysis with the global clustering uses the Adjusted Rand Index to measure how much the two segmentations overlap.\n"
]
},
2026-04-07 20:26:19 +02:00
{
"cell_type": "code",
2026-04-11 10:11:35 +02:00
"execution_count": 93,
2026-04-07 20:26:19 +02:00
"id": "bea76665-7a28-44ac-80a3-e32c595ff630",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"=== Asset types available ===\n",
"Product - Asset Type\n",
"Equity 2002728\n",
"Diversified 1365811\n",
"Fixed Income 933096\n",
"Alternative 210440\n",
"Private Assets 118\n",
"Name: count, dtype: int64\n",
"\n",
"Accounts per asset type:\n",
"Product - Asset Type\n",
"Diversified 4159\n",
"Fixed Income 3932\n",
"Equity 3899\n",
"Alternative 1317\n",
"Private Assets 11\n",
"Name: Registrar Account - ID, dtype: int64\n",
"\n",
"Retained asset types (>= 50 accounts): ['Alternative', 'Diversified', 'Equity', 'Fixed Income']\n",
"\n",
"============================================================\n",
"ASSET TYPE: Alternative\n",
"============================================================\n",
" k silhouette davies_bouldin\n",
" 2 0.4577 0.9931\n",
" 3 0.3432 1.1315\n",
" 4 0.2579 1.3841\n",
" 5 0.2823 1.2409\n",
" 6 0.2644 1.3500\n",
"→ Retained K: 2 (silhouette=0.4577)\n",
" n_accounts pct\n",
"cluster_alternative \n",
"0 310 23.5\n",
"1 1007 76.5\n"
]
},
{
"data": {
2026-04-11 10:11:35 +02:00
"image/png": "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
2026-04-07 20:26:19 +02:00
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Alternative:\n",
2026-04-11 10:11:35 +02:00
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance aum_qty_mean months_since_last_tx_asset aum_final_to_peak aum_drawdown_last\n",
"cluster_alternative \n",
"0 0.085 1.039 1.000 0.104 5.776 12.0 0.915 0.085\n",
"1 0.069 4.730 0.512 -0.072 5.063 66.0 0.000 1.000\n",
2026-04-07 20:26:19 +02:00
"\n",
"============================================================\n",
"ASSET TYPE: Diversified\n",
"============================================================\n",
" k silhouette davies_bouldin\n",
" 2 0.6037 0.6502\n",
" 3 0.5111 0.8181\n",
" 4 0.4851 0.9788\n",
" 5 0.4695 0.8712\n",
" 6 0.3429 1.1031\n",
"→ Retained K: 2 (silhouette=0.6037)\n",
" n_accounts pct\n",
"cluster_diversified \n",
"0 3369 81.0\n",
"1 790 19.0\n"
]
},
{
"data": {
2026-04-11 10:11:35 +02:00
"image/png": "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
2026-04-07 20:26:19 +02:00
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Diversified:\n",
2026-04-11 10:11:35 +02:00
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance aum_qty_mean months_since_last_tx_asset aum_final_to_peak aum_drawdown_last\n",
"cluster_diversified \n",
"0 0.044 3.042 0.625 -0.578 5.063 80.0 0.000 1.000\n",
"1 0.085 0.217 1.000 -0.675 5.150 12.0 0.907 0.093\n",
2026-04-07 20:26:19 +02:00
"\n",
"============================================================\n",
"ASSET TYPE: Equity\n",
"============================================================\n",
" k silhouette davies_bouldin\n",
" 2 0.3706 1.3811\n",
" 3 0.4255 0.9469\n",
" 4 0.2870 1.3650\n",
" 5 0.2594 1.4419\n",
" 6 0.2784 1.3111\n",
"→ Retained K: 3 (silhouette=0.4255)\n",
" n_accounts pct\n",
"cluster_equity \n",
"0 767 19.7\n",
"1 748 19.2\n",
"2 2384 61.1\n"
]
},
{
"data": {
2026-04-11 10:11:35 +02:00
"image/png": "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
2026-04-07 20:26:19 +02:00
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Equity:\n",
2026-04-11 10:11:35 +02:00
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance aum_qty_mean months_since_last_tx_asset aum_final_to_peak aum_drawdown_last\n",
"cluster_equity \n",
"0 0.071 0.067 1.046 -0.935 4.552 12.0 0.975 0.025\n",
"1 0.646 3.610 3.588 -0.099 8.474 0.0 0.154 0.846\n",
"2 0.025 3.296 0.576 -0.835 3.976 90.0 0.000 1.000\n",
2026-04-07 20:26:19 +02:00
"\n",
"============================================================\n",
"ASSET TYPE: Fixed Income\n",
"============================================================\n",
" k silhouette davies_bouldin\n",
" 2 0.6775 0.5104\n",
" 3 0.4227 0.8458\n",
" 4 0.4350 0.9964\n",
" 5 0.4607 0.9170\n",
" 6 0.4388 0.9468\n",
"→ Retained K: 2 (silhouette=0.6775)\n",
" n_accounts pct\n",
"cluster_fixed_income \n",
"0 3140 79.9\n",
"1 792 20.1\n"
]
},
{
"data": {
2026-04-11 10:11:35 +02:00
"image/png": "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
2026-04-07 20:26:19 +02:00
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Fixed Income:\n",
2026-04-11 10:11:35 +02:00
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance aum_qty_mean months_since_last_tx_asset aum_final_to_peak aum_drawdown_last\n",
"cluster_fixed_income \n",
"0 0.060 6.239 0.48 0.000 5.146 69.0 0.000 1.000\n",
"1 0.182 2.310 1.50 0.471 7.273 2.0 0.998 0.002\n",
2026-04-07 20:26:19 +02:00
"\n",
"============================================================\n",
"SUMMARY — Asset-type clustering\n",
"============================================================\n",
" Alternative : K=2, sil=0.4577, n=1317\n",
" Diversified : K=2, sil=0.6037, n=4159\n",
" Equity : K=3, sil=0.4255, n=3899\n",
" Fixed Income : K=2, sil=0.6775, n=3932\n"
]
}
],
"source": [
"# ============================================================\n",
"# ASSET-TYPE SUB-CLUSTERING\n",
"# ============================================================\n",
"\n",
"print(\"=== Asset types available ===\")\n",
"print(df_aum[ASSET_COL].value_counts())\n",
"\n",
"# Build account × asset type monthly panel\n",
"df_rel_m_asset = df_rel_m.copy()\n",
"df_rel_m_asset = df_rel_m_asset.merge(\n",
" df_aum[[ID_COL, ISIN_COL, \"month\", ASSET_COL]].drop_duplicates(),\n",
" on=[ID_COL, ISIN_COL, \"month\"], how=\"left\"\n",
")\n",
"\n",
"tmp_asset = df_rel_m_asset.copy()\n",
"tmp_asset[\"isin_held_flag\"] = (tmp_asset[\"aum_qty\"] > 0).astype(int)\n",
"tmp_asset[\"isin_active_flag\"] = (tmp_asset[\"gross_flow_qty\"] > 0).astype(int)\n",
"\n",
"df_month_asset = (\n",
" tmp_asset.dropna(subset=[ASSET_COL])\n",
" .groupby([ID_COL, ASSET_COL, \"month\"], as_index=False)\n",
" .agg(\n",
" aum_qty = (\"aum_qty\", \"sum\"),\n",
" net_flow_qty = (\"net_flow_qty\", \"sum\"),\n",
" gross_flow_qty = (\"gross_flow_qty\", \"sum\"),\n",
" sub_qty = (\"sub_qty\", \"sum\"),\n",
" red_qty = (\"red_qty\", \"sum\"),\n",
" n_tx = (\"n_tx\", \"sum\"),\n",
" n_isin_held = (\"isin_held_flag\", \"sum\"),\n",
" )\n",
" .sort_values([ID_COL, ASSET_COL, \"month\"])\n",
" .reset_index(drop=True)\n",
")\n",
"\n",
"df_month_asset[\"active_month\"] = (df_month_asset[\"gross_flow_qty\"] > 0).astype(int)\n",
"df_month_asset[\"flow_direction\"] = np.where(\n",
" df_month_asset[\"gross_flow_qty\"] > 0,\n",
" df_month_asset[\"net_flow_qty\"] / df_month_asset[\"gross_flow_qty\"], np.nan\n",
")\n",
"df_month_asset[\"aum_peak\"] = df_month_asset.groupby(\n",
" [ID_COL, ASSET_COL])[\"aum_qty\"].cummax()\n",
"df_month_asset[\"aum_drawdown\"] = np.where(\n",
" df_month_asset[\"aum_peak\"] > 0,\n",
" 1 - df_month_asset[\"aum_qty\"] / df_month_asset[\"aum_peak\"], np.nan\n",
")\n",
"\n",
"# Feature engineering per account × asset type\n",
"reference_date = df_month_asset[\"month\"].max()\n",
"last_active_asset = (\n",
" df_month_asset[df_month_asset[\"active_month\"] == 1]\n",
" .groupby([ID_COL, ASSET_COL])[\"month\"].max()\n",
" .reset_index(name=\"last_active_month\")\n",
")\n",
"last_active_asset[\"months_since_last_tx_asset\"] = (\n",
" (reference_date.to_period(\"M\") -\n",
" last_active_asset[\"last_active_month\"].dt.to_period(\"M\"))\n",
" .apply(lambda x: x.n)\n",
")\n",
"\n",
"df_client_asset = (\n",
" df_month_asset.groupby([ID_COL, ASSET_COL], as_index=False)\n",
" .agg(\n",
" n_months = (\"month\", \"nunique\"),\n",
" n_active_months = (\"active_month\", \"sum\"),\n",
" flow_freq = (\"active_month\", \"mean\"),\n",
" aum_qty_mean = (\"aum_qty\", \"mean\"),\n",
" aum_qty_max = (\"aum_qty\", \"max\"),\n",
" aum_qty_last = (\"aum_qty\", \"last\"),\n",
" gross_flow_qty_sum = (\"gross_flow_qty\", \"sum\"),\n",
" net_flow_qty_sum = (\"net_flow_qty\", \"sum\"),\n",
" n_tx_total = (\"n_tx\", \"sum\"),\n",
" avg_n_isin_held = (\"n_isin_held\", \"mean\"),\n",
" aum_drawdown_last = (\"aum_drawdown\", \"last\"),\n",
" )\n",
")\n",
"\n",
"df_client_asset = df_client_asset.merge(\n",
" last_active_asset[[ID_COL, ASSET_COL, \"months_since_last_tx_asset\"]],\n",
" on=[ID_COL, ASSET_COL], how=\"left\"\n",
")\n",
"df_client_asset[\"months_since_last_tx_asset\"] = (\n",
" df_client_asset[\"months_since_last_tx_asset\"]\n",
" .fillna(df_client_asset[\"months_since_last_tx_asset\"].max() + 1)\n",
")\n",
"df_client_asset[\"gross_flow_to_aum\"] = np.where(\n",
" df_client_asset[\"aum_qty_mean\"] > 1,\n",
" df_client_asset[\"gross_flow_qty_sum\"] / df_client_asset[\"aum_qty_mean\"], np.nan\n",
")\n",
"df_client_asset[\"flow_direction_balance\"] = np.where(\n",
" df_client_asset[\"gross_flow_qty_sum\"] > 0,\n",
" df_client_asset[\"net_flow_qty_sum\"] / df_client_asset[\"gross_flow_qty_sum\"], np.nan\n",
")\n",
"df_client_asset[\"aum_final_to_peak\"] = np.where(\n",
" df_client_asset[\"aum_qty_max\"] > 0,\n",
" np.clip(df_client_asset[\"aum_qty_last\"] / df_client_asset[\"aum_qty_max\"], 0, 1), np.nan\n",
")\n",
2026-04-11 10:11:35 +02:00
"df_client_asset[\"aum_qty_mean\"] = np.log1p(\n",
2026-04-07 20:26:19 +02:00
" df_client_asset[\"aum_qty_mean\"].clip(lower=0)\n",
")\n",
"df_client_asset = df_client_asset[\n",
" (df_client_asset[\"n_months\"] >= 6) &\n",
" (df_client_asset[\"aum_qty_mean\"] > 0)\n",
"].copy()\n",
"\n",
"print(\"\\nAccounts per asset type:\")\n",
"print(df_client_asset.groupby(ASSET_COL)[ID_COL].nunique().sort_values(ascending=False))\n",
"\n",
"# Select asset types with enough accounts\n",
"min_accounts = 50\n",
"asset_counts = df_client_asset.groupby(ASSET_COL)[ID_COL].nunique()\n",
"valid_assets = asset_counts[asset_counts >= min_accounts].index.tolist()\n",
"print(f\"\\nRetained asset types (>= {min_accounts} accounts): {valid_assets}\")\n",
"\n",
"# Feature set\n",
"asset_features = [\n",
" \"flow_freq\", \"gross_flow_to_aum\", \"avg_n_isin_held\",\n",
2026-04-11 10:11:35 +02:00
" \"flow_direction_balance\", \"aum_qty_mean\",\n",
2026-04-07 20:26:19 +02:00
" \"months_since_last_tx_asset\", \"aum_final_to_peak\", \"aum_drawdown_last\",\n",
"]\n",
"\n",
"# Clustering loop\n",
"ASSET_RESULTS = {}\n",
"\n",
"for asset in valid_assets:\n",
" print(f\"\\n{'='*60}\")\n",
" print(f\"ASSET TYPE: {asset}\")\n",
" print(f\"{'='*60}\")\n",
"\n",
" df_a = df_client_asset[df_client_asset[ASSET_COL] == asset].copy()\n",
" feats = [c for c in asset_features if c in df_a.columns]\n",
"\n",
" d = df_a.copy()\n",
" d[\"flow_direction_balance\"] = d[\"flow_direction_balance\"].fillna(0)\n",
"\n",
" for col in [\"avg_n_isin_held\", \"months_since_last_tx_asset\",\n",
" \"aum_drawdown_last\", \"aum_final_to_peak\"]:\n",
" if col not in d.columns:\n",
" continue\n",
" d[col] = winsorize_mad(d[col], n_sigma=3)\n",
"\n",
" for col in [\"gross_flow_to_aum\"]:\n",
" if col not in d.columns:\n",
" continue\n",
" vals = d[col].to_numpy(dtype=float)\n",
" d[col] = np.log1p(np.clip(vals, 0, np.nanpercentile(vals, 90)))\n",
"\n",
" for col in [\"flow_freq\"]:\n",
" if col not in d.columns:\n",
" continue\n",
" vals = d[col].to_numpy(dtype=float)\n",
" d[col] = np.log1p(np.clip(vals, 0, None))\n",
"\n",
" X_a = d[feats].fillna(d[feats].median()).to_numpy()\n",
" X_a_scaled = RobustScaler().fit_transform(X_a)\n",
"\n",
" best_k, best_sil = 2, -1\n",
" rows_k = []\n",
" max_k = min(6, len(df_a) // 50)\n",
"\n",
" for k in range(2, max_k + 1):\n",
" km = KMeans(n_clusters=k, n_init=30, random_state=RANDOM_STATE)\n",
" labels = km.fit_predict(X_a_scaled)\n",
" sil = silhouette_score(X_a_scaled, labels)\n",
" db = davies_bouldin_score(X_a_scaled, labels)\n",
" rows_k.append({\"k\": k, \"silhouette\": round(sil, 4), \"davies_bouldin\": round(db, 4)})\n",
" if sil > best_sil:\n",
" best_sil, best_k = sil, k\n",
"\n",
" print(pd.DataFrame(rows_k).to_string(index=False))\n",
" print(f\"→ Retained K: {best_k} (silhouette={best_sil:.4f})\")\n",
"\n",
" km_final = KMeans(n_clusters=best_k, n_init=50, random_state=RANDOM_STATE)\n",
" cluster_col = f\"cluster_{asset.lower().replace(' ','_')}\"\n",
" df_a[cluster_col] = km_final.fit_predict(X_a_scaled)\n",
"\n",
" counts = df_a[cluster_col].value_counts().sort_index()\n",
" props = counts / counts.sum() * 100\n",
" print(pd.DataFrame({\"n_accounts\": counts, \"pct\": props.round(1)}))\n",
"\n",
" profile_vars_asset = [c for c in asset_features if c in df_a.columns]\n",
" prof = plot_heatmap(\n",
" df_a, profile_vars_asset, cluster_col,\n",
" title=f\"Cluster Signatures — {asset} (K={best_k}, robust z-score)\",\n",
" figsize=(14, 4)\n",
" )\n",
" print(f\"\\nMedians — {asset}:\")\n",
" print(prof.round(3).to_string())\n",
"\n",
" ASSET_RESULTS[asset] = {\n",
" \"df\": df_a, \"cluster_col\": cluster_col,\n",
" \"k\": best_k, \"silhouette\": best_sil, \"profile\": prof,\n",
" }\n",
"\n",
"print(\"\\n\" + \"=\"*60)\n",
"print(\"SUMMARY — Asset-type clustering\")\n",
"print(\"=\"*60)\n",
"for asset, res in ASSET_RESULTS.items():\n",
" print(f\" {asset:20s}: K={res['k']}, sil={res['silhouette']:.4f}, n={len(res['df'])}\")"
]
},
{
"cell_type": "code",
2026-04-11 10:11:35 +02:00
"execution_count": 94,
2026-04-10 11:05:13 +02:00
"id": "d986e1f1-57b9-4884-8507-a8e39cbe8c9a",
2026-04-07 20:26:19 +02:00
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
2026-04-10 11:05:13 +02:00
"Available columns: ['Registrar Account - ID', 'cluster_k4', 'cluster_carmignac_patrimoine', 'cluster_carmignac_sécurité', 'cluster_carmignac_investissement', 'cluster_carmignac_portfolio_sécurité', 'cluster_carmignac_portfolio_flexible_b', 'cluster_carmignac_emergents', 'cluster_carmignac_portfolio_patrimoine', 'cluster_carmignac_portfolio_global_bon', 'cluster_carmignac_portfolio_credit', 'cluster_carmignac_portfolio_emerging_p', 'cluster_carmignac_portfolio_grande_eur', 'cluster_carmignac_court_terme', 'cluster_carmignac_portfolio_long-short', 'cluster_carmignac_portfolio_climate_tr', 'cluster_carmignac_credit_2027']\n",
"Shape: (7177, 17)\n"
2026-04-07 20:26:19 +02:00
]
},
{
"data": {
2026-04-10 11:05:13 +02:00
"image/png": "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
2026-04-07 20:26:19 +02:00
"text/plain": [
2026-04-10 11:05:13 +02:00
"<Figure size 1800x2500 with 30 Axes>"
2026-04-07 20:26:19 +02:00
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"============================================================\n",
2026-04-10 11:05:13 +02:00
"Global × Carmignac Patrimoine\n",
2026-04-07 20:26:19 +02:00
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
2026-04-10 11:05:13 +02:00
" Not exposed Fund C0 Fund C1\n",
"Global C1 39.6 24.8 35.7\n",
"Global C2 51.0 4.5 44.5\n",
"Global C3 34.2 12.3 53.5\n",
"Global C4 83.5 1.6 14.8\n",
2026-04-07 20:26:19 +02:00
"\n",
2026-04-10 11:05:13 +02:00
"% per fund cluster (each column sums to 100%):\n",
" Not exposed Fund C0 Fund C1\n",
"Global C1 14.0 57.6 22.3\n",
"Global C2 22.2 12.8 34.2\n",
"Global C3 9.6 22.7 26.5\n",
"Global C4 54.2 6.9 17.0\n",
2026-04-07 20:26:19 +02:00
"\n",
"============================================================\n",
2026-04-10 11:05:13 +02:00
"Global × Carmignac Sécurité\n",
2026-04-07 20:26:19 +02:00
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
2026-04-10 11:05:13 +02:00
" Not exposed Fund C0 Fund C1\n",
"Global C1 83.2 6.2 10.6\n",
"Global C2 85.7 1.6 12.7\n",
"Global C3 44.3 10.5 45.2\n",
"Global C4 88.3 0.6 11.1\n",
2026-04-07 20:26:19 +02:00
"\n",
2026-04-10 11:05:13 +02:00
"% per fund cluster (each column sums to 100%):\n",
" Not exposed Fund C0 Fund C1\n",
"Global C1 21.6 35.4 12.9\n",
"Global C2 27.4 11.2 19.0\n",
"Global C3 9.1 47.3 43.5\n",
"Global C4 42.0 6.2 24.7\n",
2026-04-07 20:26:19 +02:00
"\n",
"============================================================\n",
2026-04-10 11:05:13 +02:00
"Global × Carmignac Investissement\n",
2026-04-07 20:26:19 +02:00
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
2026-04-10 11:05:13 +02:00
" Not exposed Fund C0 Fund C1\n",
"Global C1 55.3 44.4 0.3\n",
"Global C2 71.8 28.2 0.0\n",
"Global C3 45.0 33.2 21.8\n",
"Global C4 91.3 8.5 0.2\n",
2026-04-07 20:26:19 +02:00
"\n",
2026-04-10 11:05:13 +02:00
"% per fund cluster (each column sums to 100%):\n",
" Not exposed Fund C0 Fund C1\n",
"Global C1 16.0 36.7 1.5\n",
"Global C2 25.5 28.7 0.0\n",
"Global C3 10.3 21.7 96.6\n",
"Global C4 48.3 12.9 1.9\n",
2026-04-07 20:26:19 +02:00
"\n",
"============================================================\n",
2026-04-10 11:05:13 +02:00
"Global × Carmignac Portfolio Sécurité\n",
2026-04-07 20:26:19 +02:00
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
2026-04-10 11:05:13 +02:00
" Not exposed Fund C0 Fund C1\n",
"Global C1 94.9 3.6 1.5\n",
"Global C2 96.3 2.9 0.9\n",
"Global C3 66.3 26.6 7.2\n",
"Global C4 81.2 18.2 0.6\n",
2026-04-07 20:26:19 +02:00
"\n",
2026-04-10 11:05:13 +02:00
"% per fund cluster (each column sums to 100%):\n",
" Not exposed Fund C0 Fund C1\n",
"Global C1 22.9 5.8 15.9\n",
"Global C2 28.6 5.7 11.6\n",
"Global C3 12.7 34.2 60.9\n",
"Global C4 35.9 54.2 11.6\n",
2026-04-07 20:26:19 +02:00
"\n",
"============================================================\n",
2026-04-10 11:05:13 +02:00
"Global × Carmignac Portfolio Flexible Bond\n",
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
" Not exposed Fund C0 Fund C1\n",
"Global C1 92.6 7.3 0.1\n",
"Global C2 94.3 5.7 0.0\n",
"Global C3 56.1 39.5 4.4\n",
"Global C4 91.9 7.9 0.1\n",
"\n",
"% per fund cluster (each column sums to 100%):\n",
" Not exposed Fund C0 Fund C1\n",
"Global C1 22.0 12.2 1.8\n",
"Global C2 27.5 11.6 0.0\n",
"Global C3 10.5 52.1 91.1\n",
"Global C4 39.9 24.1 7.1\n",
"\n",
"============================================================\n",
"Global × Carmignac Emergents\n",
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
" Not exposed Fund C0 Fund C1 Fund C2\n",
"Global C1 69.8 12.3 17.8 0.1\n",
"Global C2 79.1 2.0 18.9 0.0\n",
"Global C3 48.0 9.0 28.0 15.0\n",
"Global C4 92.5 0.5 7.0 0.0\n",
"\n",
"% per fund cluster (each column sums to 100%):\n",
" Not exposed Fund C0 Fund C1 Fund C2\n",
"Global C1 18.6 53.8 23.4 0.6\n",
"Global C2 26.0 10.9 30.6 0.0\n",
"Global C3 10.1 31.1 29.2 98.9\n",
"Global C4 45.2 4.1 16.8 0.6\n",
"\n",
"============================================================\n",
"Global × Carmignac Portfolio Patrimoine\n",
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
" Not exposed Fund C0 Fund C1 Fund C2\n",
"Global C1 95.1 1.2 2.9 0.7\n",
"Global C2 93.7 1.9 4.3 0.1\n",
"Global C3 71.1 7.9 16.8 4.1\n",
"Global C4 81.4 2.8 12.3 3.5\n",
"\n",
"% per fund cluster (each column sums to 100%):\n",
" Not exposed Fund C0 Fund C1 Fund C2\n",
"Global C1 22.9 8.2 6.6 7.0\n",
"Global C2 27.7 15.5 12.0 1.3\n",
"Global C3 13.5 42.3 30.2 30.6\n",
"Global C4 35.8 34.1 51.2 61.1\n",
"\n",
"============================================================\n",
"Global × Carmignac Portfolio Global Bond\n",
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
" Not exposed Fund C0 Fund C1 Fund C2\n",
"Global C1 90.3 5.1 2.1 2.5\n",
"Global C2 92.5 5.3 2.1 0.2\n",
"Global C3 57.3 27.2 10.2 5.3\n",
"Global C4 70.3 24.1 2.4 3.1\n",
"\n",
"% per fund cluster (each column sums to 100%):\n",
" Not exposed Fund C0 Fund C1 Fund C2\n",
"Global C1 23.9 6.6 12.2 19.9\n",
"Global C2 30.1 8.4 15.0 1.6\n",
"Global C3 12.0 27.9 46.9 33.3\n",
"Global C4 34.1 57.1 26.0 45.2\n",
"\n",
"============================================================\n",
"Global × Carmignac Portfolio Credit\n",
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
" Not exposed Fund C0 Fund C1\n",
"Global C1 90.3 0.1 9.6\n",
"Global C2 96.5 0.0 3.5\n",
"Global C3 73.2 4.5 22.3\n",
"Global C4 86.0 0.0 14.0\n",
"\n",
"% per fund cluster (each column sums to 100%):\n",
" Not exposed Fund C0 Fund C1\n",
"Global C1 21.3 3.6 16.8\n",
"Global C2 28.0 0.0 7.6\n",
"Global C3 13.7 96.4 30.9\n",
"Global C4 37.1 0.0 44.8\n",
"\n",
"============================================================\n",
"Global × Carmignac Portfolio Emerging Patrimoine\n",
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
" Not exposed Fund C0 Fund C1 Fund C2\n",
"Global C1 89.6 7.4 3.0 0.0\n",
"Global C2 92.1 7.0 0.9 0.0\n",
"Global C3 56.3 32.9 6.3 4.5\n",
"Global C4 93.1 6.4 0.4 0.1\n",
"\n",
"% per fund cluster (each column sums to 100%):\n",
" Not exposed Fund C0 Fund C1 Fund C2\n",
"Global C1 21.4 13.8 30.1 0.0\n",
"Global C2 27.1 16.1 11.0 0.0\n",
"Global C3 10.7 48.4 50.7 96.4\n",
"Global C4 40.8 21.6 8.2 3.6\n",
"\n",
"============================================================\n",
"Global × Carmignac Portfolio Grande Europe\n",
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
" Not exposed Fund C0 Fund C1\n",
"Global C1 78.6 0.2 21.2\n",
"Global C2 91.0 0.0 9.0\n",
"Global C3 57.5 10.2 32.3\n",
"Global C4 90.1 0.1 9.8\n",
"\n",
"% per fund cluster (each column sums to 100%):\n",
" Not exposed Fund C0 Fund C1\n",
"Global C1 19.6 2.4 28.0\n",
"Global C2 27.9 0.0 14.6\n",
"Global C3 11.3 94.5 33.8\n",
"Global C4 41.1 3.1 23.7\n",
"\n",
"============================================================\n",
"Global × Carmignac Court Terme\n",
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
" Not exposed Fund C0 Fund C1\n",
"Global C1 96.7 2.4 0.9\n",
"Global C2 96.6 3.1 0.2\n",
"Global C3 79.0 16.7 4.4\n",
"Global C4 97.5 2.4 0.1\n",
"\n",
"% per fund cluster (each column sums to 100%):\n",
" Not exposed Fund C0 Fund C1\n",
"Global C1 21.2 10.0 19.4\n",
"Global C2 26.0 16.2 5.6\n",
"Global C3 13.7 55.6 70.8\n",
"Global C4 39.1 18.2 4.2\n",
"\n",
"============================================================\n",
"Global × Carmignac Portfolio Long-Short European Equities\n",
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
" Not exposed Fund C0 Fund C1 Fund C2 Fund C3\n",
"Global C1 97.2 1.2 0.1 1.0 0.5\n",
"Global C2 98.6 0.4 0.0 0.7 0.3\n",
"Global C3 74.6 4.0 2.0 15.0 4.4\n",
"Global C4 95.2 0.4 0.8 2.4 1.3\n",
"\n",
"% per fund cluster (each column sums to 100%):\n",
" Not exposed Fund C0 Fund C1 Fund C2 Fund C3\n",
"Global C1 21.5 20.5 4.3 5.6 7.1\n",
"Global C2 26.9 8.4 0.0 4.5 6.1\n",
"Global C3 13.1 56.6 51.1 65.9 52.0\n",
"Global C4 38.6 14.5 44.7 24.0 34.7\n",
"\n",
"============================================================\n",
"Global × Carmignac Portfolio Climate Transition\n",
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
" Not exposed Fund C0 Fund C1\n",
"Global C1 86.3 0.0 13.7\n",
"Global C2 88.5 0.0 11.5\n",
"Global C3 53.8 6.6 39.6\n",
"Global C4 93.0 0.1 6.9\n",
"\n",
"% per fund cluster (each column sums to 100%):\n",
" Not exposed Fund C0 Fund C1\n",
"Global C1 21.1 0.0 19.0\n",
"Global C2 26.7 0.0 19.7\n",
"Global C3 10.4 96.2 43.7\n",
"Global C4 41.7 3.8 17.5\n",
"\n",
"============================================================\n",
"Global × Carmignac Credit 2027\n",
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
" Not exposed Fund C0 Fund C1 Fund C2 Fund C3\n",
"Global C1 98.2 0.1 0.1 1.2 0.4\n",
"Global C2 99.7 0.0 0.0 0.0 0.3\n",
"Global C3 85.1 2.7 6.1 4.3 1.8\n",
"Global C4 98.8 0.3 0.0 0.7 0.1\n",
"\n",
"% per fund cluster (each column sums to 100%):\n",
" Not exposed Fund C0 Fund C1 Fund C2 Fund C3\n",
"Global C1 20.9 2.4 2.7 19.5 17.1\n",
"Global C2 26.2 0.0 0.0 0.0 14.3\n",
"Global C3 14.4 76.2 95.9 57.5 60.0\n",
"Global C4 38.6 21.4 1.4 23.0 8.6\n",
"\n",
"============================================================\n",
"Adjusted Rand Index — coherence: global clustering × fund clustering\n",
2026-04-07 20:26:19 +02:00
"============================================================\n",
"(1 = identical, 0 = independent, <0 = worse than random)\n",
"\n",
2026-04-10 11:05:13 +02:00
" Carmignac Patrimoine : ARI=0.0238 (n=3000 shared accounts)\n",
" Carmignac Sécurité : ARI=0.0119 (n=1477 shared accounts)\n",
" Carmignac Investissement : ARI=0.0426 (n=2053 shared accounts)\n",
" Carmignac Portfolio Sécurité : ARI=0.0820 (n=1047 shared accounts)\n",
" Carmignac Portfolio Flexible Bond : ARI=-0.0448 (n=944 shared accounts)\n",
" Carmignac Emergents : ARI=0.0153 (n=1640 shared accounts)\n",
" Carmignac Portfolio Patrimoine : ARI=0.0118 (n=1029 shared accounts)\n",
" Carmignac Portfolio Global Bond : ARI=0.0799 (n=1584 shared accounts)\n",
" Carmignac Portfolio Credit : ARI=0.0090 (n=901 shared accounts)\n",
" Carmignac Portfolio Emerging Patrim : ARI=-0.0332 (n=996 shared accounts)\n",
" Carmignac Portfolio Grande Europe : ARI=-0.0264 (n=1247 shared accounts)\n",
" Carmignac Court Terme : ARI=-0.0347 (n=423 shared accounts)\n",
" Carmignac Portfolio Long-Short Euro : ARI=0.0516 (n=495 shared accounts)\n",
" Carmignac Portfolio Climate Transit : ARI=-0.0456 (n=1141 shared accounts)\n",
" Carmignac Credit 2027 : ARI=0.0470 (n=238 shared accounts)\n",
2026-04-07 20:26:19 +02:00
"\n",
"============================================================\n",
2026-04-10 11:05:13 +02:00
"Fund exposure by global cluster\n",
2026-04-07 20:26:19 +02:00
"============================================================\n",
"\n",
2026-04-10 11:05:13 +02:00
"Average number of funds held per global cluster:\n",
2026-04-07 20:26:19 +02:00
"cluster_k4\n",
2026-04-10 11:05:13 +02:00
"1 2.42\n",
"2 1.72\n",
"3 5.98\n",
"4 1.66\n",
"Name: n_funds_held, dtype: float64\n",
2026-04-07 20:26:19 +02:00
"\n",
2026-04-10 11:05:13 +02:00
"Distribution of fund count per global cluster:\n",
" 0 fund(s) 1 fund(s) 2 fund(s) 3 fund(s) 4 fund(s) 5 fund(s) 6 fund(s) 7 fund(s) 8 fund(s) 9 fund(s) 10 fund(s) 11 fund(s) 12 fund(s) 13 fund(s) 14 fund(s) 15 fund(s)\n",
"Global C1 4.5 42.8 18.9 11.2 8.9 5.8 2.6 2.0 1.2 0.4 0.4 0.5 0.2 0.2 0.3 0.1\n",
"Global C2 8.6 56.9 16.6 7.1 4.3 2.5 0.8 1.0 0.8 0.6 0.4 0.2 0.1 0.1 0.0 0.0\n",
"Global C3 7.3 11.6 8.0 7.2 10.1 5.5 7.6 6.5 7.3 5.3 5.8 4.4 4.3 3.8 4.1 1.4\n",
"Global C4 8.7 61.9 15.3 4.6 3.0 1.5 1.3 1.1 1.0 0.6 0.6 0.2 0.1 0.0 0.1 0.0\n",
"\n",
"============================================================\n",
"Summary — ARI comparison: asset-type vs fund-level\n",
"============================================================\n",
" level name ARI n\n",
"Asset-type Equity 0.1579 3689\n",
"Asset-type Fixed Income 0.1112 3742\n",
" Fund Carmignac Portfolio Sécurité 0.0820 1047\n",
" Fund Carmignac Portfolio Global Bond 0.0799 1584\n",
" Fund Carmignac Portfolio Long-Short Euro 0.0516 495\n",
" Fund Carmignac Credit 2027 0.0470 238\n",
" Fund Carmignac Investissement 0.0426 2053\n",
"Asset-type Diversified 0.0344 3978\n",
"Asset-type Alternative 0.0274 1164\n",
" Fund Carmignac Patrimoine 0.0238 3000\n",
" Fund Carmignac Emergents 0.0153 1640\n",
" Fund Carmignac Sécurité 0.0119 1477\n",
" Fund Carmignac Portfolio Patrimoine 0.0118 1029\n",
" Fund Carmignac Portfolio Credit 0.0090 901\n",
" Fund Carmignac Portfolio Grande Europe -0.0264 1247\n",
" Fund Carmignac Portfolio Emerging Patrim -0.0332 996\n",
" Fund Carmignac Court Terme -0.0347 423\n",
" Fund Carmignac Portfolio Flexible Bond -0.0448 944\n",
" Fund Carmignac Portfolio Climate Transit -0.0456 1141\n"
2026-04-07 20:26:19 +02:00
]
2026-04-10 11:05:13 +02:00
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1000x500 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
2026-04-07 20:26:19 +02:00
}
],
"source": [
2026-04-10 11:05:13 +02:00
"# ============================================================\n",
"# CROSS-ANALYSIS — Global clustering × Fund-level clustering\n",
"# ============================================================\n",
2026-04-07 20:26:19 +02:00
"\n",
2026-04-10 11:05:13 +02:00
"# Step 1. Merge fund cluster labels into global dataframe\n",
"dfc_cross_fund = dfc[[ID_COL, \"cluster_k4\"]].copy()\n",
"\n",
"for fund, res in FUND_RESULTS.items():\n",
2026-04-07 20:26:19 +02:00
" cluster_col = res[\"cluster_col\"]\n",
2026-04-10 11:05:13 +02:00
" df_f = res[\"df\"][[ID_COL, cluster_col]].copy()\n",
" dfc_cross_fund = dfc_cross_fund.merge(df_f, on=ID_COL, how=\"left\")\n",
2026-04-07 20:26:19 +02:00
"\n",
2026-04-10 11:05:13 +02:00
"print(\"Available columns:\", dfc_cross_fund.columns.tolist())\n",
"print(\"Shape:\", dfc_cross_fund.shape)\n",
2026-04-07 20:26:19 +02:00
"\n",
2026-04-10 11:05:13 +02:00
"# Step 2. Contingency tables: global clusters × fund clusters\n",
"fund_list = list(FUND_RESULTS.keys())\n",
"n_funds = len(fund_list)\n",
"n_cols = 3\n",
"n_rows = (n_funds + n_cols - 1) // n_cols\n",
"\n",
"fig, axes = plt.subplots(n_rows, n_cols, figsize=(18, 5 * n_rows))\n",
2026-04-08 17:41:37 +02:00
"axes = axes.flatten()\n",
2026-04-07 20:26:19 +02:00
"\n",
2026-04-10 11:05:13 +02:00
"for i, (fund, res) in enumerate(FUND_RESULTS.items()):\n",
2026-04-08 17:41:37 +02:00
" cluster_col = res[\"cluster_col\"]\n",
2026-04-10 11:05:13 +02:00
" if cluster_col not in dfc_cross_fund.columns:\n",
2026-04-08 17:41:37 +02:00
" continue\n",
" ct = pd.crosstab(\n",
2026-04-10 11:05:13 +02:00
" dfc_cross_fund[\"cluster_k4\"],\n",
" dfc_cross_fund[cluster_col].fillna(-1).astype(int),\n",
2026-04-08 17:41:37 +02:00
" normalize=\"index\"\n",
" ).round(3) * 100\n",
" col_names = {\n",
2026-04-10 11:05:13 +02:00
" c: f\"Fund C{c}\" if c >= 0 else \"Not exposed\"\n",
2026-04-08 17:41:37 +02:00
" for c in ct.columns\n",
" }\n",
" ct = ct.rename(columns=col_names)\n",
" ct.index = [f\"Global C{i}\" for i in ct.index]\n",
" sns.heatmap(\n",
2026-04-10 11:05:13 +02:00
" ct, cmap=\"Greens\", annot=True, fmt=\".1f\",\n",
2026-04-08 17:41:37 +02:00
" ax=axes[i], cbar_kws={\"label\": \"%\"},\n",
" vmin=0, vmax=100,\n",
" )\n",
2026-04-10 11:05:13 +02:00
" axes[i].set_title(f\"Global × {fund[:35]} (% per global cluster)\",\n",
" fontsize=9)\n",
" axes[i].set_xlabel(\"Fund cluster\", fontsize=8)\n",
" axes[i].set_ylabel(\"Global cluster\", fontsize=8)\n",
" axes[i].tick_params(labelsize=8)\n",
"\n",
"# Hide empty subplots\n",
"for j in range(i + 1, len(axes)):\n",
" axes[j].set_visible(False)\n",
"\n",
"plt.suptitle(\"Cross-Analysis: Global Clustering × Fund-Level Clustering\",\n",
" fontsize=14, y=1.01)\n",
2026-04-08 17:41:37 +02:00
"plt.tight_layout()\n",
"plt.show()\n",
2026-04-07 20:26:19 +02:00
"\n",
2026-04-10 11:05:13 +02:00
"# Step 3. Detailed contingency tables\n",
"for fund, res in FUND_RESULTS.items():\n",
2026-04-08 17:41:37 +02:00
" cluster_col = res[\"cluster_col\"]\n",
2026-04-10 11:05:13 +02:00
" if cluster_col not in dfc_cross_fund.columns:\n",
2026-04-08 17:41:37 +02:00
" continue\n",
" print(f\"\\n{'='*60}\")\n",
2026-04-10 11:05:13 +02:00
" print(f\"Global × {fund}\")\n",
2026-04-08 17:41:37 +02:00
" print(f\"{'='*60}\")\n",
2026-04-10 11:05:13 +02:00
"\n",
2026-04-08 17:41:37 +02:00
" ct_row = pd.crosstab(\n",
2026-04-10 11:05:13 +02:00
" dfc_cross_fund[\"cluster_k4\"],\n",
" dfc_cross_fund[cluster_col].fillna(-1).astype(int),\n",
2026-04-08 17:41:37 +02:00
" normalize=\"index\"\n",
" ).round(3) * 100\n",
2026-04-10 11:05:13 +02:00
" ct_row.index = [f\"Global C{i}\" for i in ct_row.index]\n",
" ct_row.columns = [f\"Fund C{c}\" if c >= 0 else \"Not exposed\"\n",
2026-04-08 17:41:37 +02:00
" for c in ct_row.columns]\n",
" print(\"\\n% per global cluster (each row sums to 100%):\")\n",
" print(ct_row.to_string())\n",
2026-04-07 20:26:19 +02:00
"\n",
2026-04-08 17:41:37 +02:00
" ct_col = pd.crosstab(\n",
2026-04-10 11:05:13 +02:00
" dfc_cross_fund[\"cluster_k4\"],\n",
" dfc_cross_fund[cluster_col].fillna(-1).astype(int),\n",
2026-04-08 17:41:37 +02:00
" normalize=\"columns\"\n",
" ).round(3) * 100\n",
2026-04-10 11:05:13 +02:00
" ct_col.index = [f\"Global C{i}\" for i in ct_col.index]\n",
" ct_col.columns = [f\"Fund C{c}\" if c >= 0 else \"Not exposed\"\n",
2026-04-08 17:41:37 +02:00
" for c in ct_col.columns]\n",
2026-04-10 11:05:13 +02:00
" print(\"\\n% per fund cluster (each column sums to 100%):\")\n",
2026-04-08 17:41:37 +02:00
" print(ct_col.to_string())\n",
2026-04-07 20:26:19 +02:00
"\n",
2026-04-08 17:41:37 +02:00
"# Step 4. Adjusted Rand Index\n",
2026-04-07 20:26:19 +02:00
"print(\"\\n\" + \"=\"*60)\n",
2026-04-10 11:05:13 +02:00
"print(\"Adjusted Rand Index — coherence: global clustering × fund clustering\")\n",
2026-04-07 20:26:19 +02:00
"print(\"=\"*60)\n",
2026-04-08 17:41:37 +02:00
"print(\"(1 = identical, 0 = independent, <0 = worse than random)\\n\")\n",
"\n",
2026-04-10 11:05:13 +02:00
"for fund, res in FUND_RESULTS.items():\n",
2026-04-08 17:41:37 +02:00
" cluster_col = res[\"cluster_col\"]\n",
2026-04-10 11:05:13 +02:00
" if cluster_col not in dfc_cross_fund.columns:\n",
2026-04-08 17:41:37 +02:00
" continue\n",
2026-04-10 11:05:13 +02:00
" mask = dfc_cross_fund[cluster_col].notna()\n",
" labels_global = dfc_cross_fund.loc[mask, \"cluster_k4\"].values\n",
" labels_fund = dfc_cross_fund.loc[mask, cluster_col].values\n",
" ari = adjusted_rand_score(labels_global, labels_fund)\n",
" print(f\" {fund[:35]:35s} : ARI={ari:.4f} (n={mask.sum()} shared accounts)\")\n",
2026-04-07 20:26:19 +02:00
"\n",
2026-04-10 11:05:13 +02:00
"# Step 5. Fund exposure per global cluster\n",
2026-04-07 20:26:19 +02:00
"print(\"\\n\" + \"=\"*60)\n",
2026-04-10 11:05:13 +02:00
"print(\"Fund exposure by global cluster\")\n",
2026-04-07 20:26:19 +02:00
"print(\"=\"*60)\n",
"\n",
2026-04-10 11:05:13 +02:00
"fund_cols = [res[\"cluster_col\"] for res in FUND_RESULTS.values()\n",
" if res[\"cluster_col\"] in dfc_cross_fund.columns]\n",
"dfc_cross_fund[\"n_funds_held\"] = dfc_cross_fund[fund_cols].notna().sum(axis=1)\n",
2026-04-07 20:26:19 +02:00
"\n",
2026-04-10 11:05:13 +02:00
"print(\"\\nAverage number of funds held per global cluster:\")\n",
"print(dfc_cross_fund.groupby(\"cluster_k4\")[\"n_funds_held\"].mean().round(2))\n",
2026-04-07 20:26:19 +02:00
"\n",
2026-04-10 11:05:13 +02:00
"print(\"\\nDistribution of fund count per global cluster:\")\n",
"ct_fund_multi = pd.crosstab(\n",
" dfc_cross_fund[\"cluster_k4\"],\n",
" dfc_cross_fund[\"n_funds_held\"],\n",
2026-04-08 17:41:37 +02:00
" normalize=\"index\"\n",
").round(3) * 100\n",
2026-04-10 11:05:13 +02:00
"ct_fund_multi.index = [f\"Global C{i}\" for i in ct_fund_multi.index]\n",
"ct_fund_multi.columns = [f\"{c} fund(s)\" for c in ct_fund_multi.columns]\n",
"print(ct_fund_multi.to_string())\n",
"\n",
"# Step 6. Summary ARI comparison — asset vs fund\n",
"print(\"\\n\" + \"=\"*60)\n",
"print(\"Summary — ARI comparison: asset-type vs fund-level\")\n",
"print(\"=\"*60)\n",
"\n",
"rows_ari = []\n",
"for asset, res in ASSET_RESULTS.items():\n",
" cluster_col = res[\"cluster_col\"]\n",
" if cluster_col not in dfc_cross.columns:\n",
" continue\n",
" mask = dfc_cross[cluster_col].notna()\n",
" ari = adjusted_rand_score(\n",
" dfc_cross.loc[mask, \"cluster_k4\"].values,\n",
" dfc_cross.loc[mask, cluster_col].values\n",
" )\n",
" rows_ari.append({\"level\": \"Asset-type\", \"name\": asset,\n",
" \"ARI\": round(ari, 4), \"n\": mask.sum()})\n",
"\n",
"for fund, res in FUND_RESULTS.items():\n",
" cluster_col = res[\"cluster_col\"]\n",
" if cluster_col not in dfc_cross_fund.columns:\n",
" continue\n",
" mask = dfc_cross_fund[cluster_col].notna()\n",
" ari = adjusted_rand_score(\n",
" dfc_cross_fund.loc[mask, \"cluster_k4\"].values,\n",
" dfc_cross_fund.loc[mask, cluster_col].values\n",
" )\n",
" rows_ari.append({\"level\": \"Fund\", \"name\": fund[:35],\n",
" \"ARI\": round(ari, 4), \"n\": mask.sum()})\n",
"\n",
"df_ari_summary = pd.DataFrame(rows_ari).sort_values(\"ARI\", ascending=False)\n",
"print(df_ari_summary.to_string(index=False))\n",
"\n",
"# Visualisation\n",
"fig, ax = plt.subplots(figsize=(10, 5))\n",
"colors = [\"#1f77b4\" if l == \"Asset-type\" else \"#2ca02c\"\n",
" for l in df_ari_summary[\"level\"]]\n",
"ax.barh(df_ari_summary[\"name\"], df_ari_summary[\"ARI\"], color=colors)\n",
"ax.axvline(0, color=\"black\", linewidth=0.8)\n",
"ax.axvline(0.1, color=\"gray\", linestyle=\"--\", linewidth=0.8,\n",
" label=\"ARI = 0.1 (weak agreement)\")\n",
"ax.set_xlabel(\"Adjusted Rand Index\")\n",
"ax.set_title(\"Coherence between global clustering and sub-clusterings\\n\"\n",
" \"(asset-type in blue, fund-level in green)\")\n",
"ax.legend()\n",
"plt.tight_layout()\n",
"plt.show()"
2026-04-07 20:26:19 +02:00
]
},
{
2026-04-10 11:05:13 +02:00
"cell_type": "code",
2026-04-11 10:11:35 +02:00
"execution_count": 95,
2026-04-10 11:05:13 +02:00
"id": "05d06b16",
2026-04-07 20:26:19 +02:00
"metadata": {},
2026-04-10 11:05:13 +02:00
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Available columns: ['Registrar Account - ID', 'cluster_k4', 'cluster_alternative', 'cluster_diversified', 'cluster_equity', 'cluster_fixed_income']\n",
"Shape: (7177, 6)\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1800x1200 with 8 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"============================================================\n",
"Global × Alternative\n",
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
" Not exposed Asset C0 Asset C1\n",
"Global C1 86.5 6.2 7.3\n",
"Global C2 93.2 0.9 5.9\n",
"Global C3 48.8 10.8 40.4\n",
"Global C4 91.1 1.1 7.9\n",
"\n",
"% per asset cluster (each column sums to 100%):\n",
" Not exposed Asset C0 Asset C1\n",
"Global C1 21.3 34.6 12.0\n",
"Global C2 28.2 6.5 11.9\n",
"Global C3 9.5 47.9 52.5\n",
"Global C4 41.0 11.0 23.6\n",
"\n",
"============================================================\n",
"Global × Diversified\n",
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
" Not exposed Asset C0 Asset C1\n",
"Global C1 31.6 40.5 27.9\n",
"Global C2 40.9 54.0 5.1\n",
"Global C3 21.2 61.7 17.1\n",
"Global C4 64.2 33.9 1.8\n",
"\n",
"% per asset cluster (each column sums to 100%):\n",
" Not exposed Asset C0 Asset C1\n",
"Global C1 14.6 18.6 54.6\n",
"Global C2 23.3 30.5 12.3\n",
"Global C3 7.8 22.4 26.5\n",
"Global C4 54.4 28.5 6.6\n",
"\n",
"============================================================\n",
"Global × Equity\n",
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
" Not exposed Asset C0 Asset C1 Asset C2\n",
"Global C1 37.3 29.4 0.3 32.9\n",
"Global C2 44.2 6.8 0.0 49.1\n",
"Global C3 18.6 9.7 48.5 23.1\n",
"Global C4 70.7 3.2 0.2 25.9\n",
"\n",
"% per asset cluster (each column sums to 100%):\n",
" Not exposed Asset C0 Asset C1 Asset C2\n",
"Global C1 15.8 57.2 0.9 20.7\n",
"Global C2 23.1 16.2 0.0 38.0\n",
"Global C3 6.2 15.0 98.3 11.5\n",
"Global C4 54.9 11.6 0.9 29.8\n",
"\n",
"============================================================\n",
"Global × Fixed Income\n",
"============================================================\n",
"\n",
"% per global cluster (each row sums to 100%):\n",
" Not exposed Asset C0 Asset C1\n",
"Global C1 65.4 21.2 13.5\n",
"Global C2 72.0 24.1 3.9\n",
"Global C3 19.2 52.9 27.8\n",
"Global C4 34.5 61.5 4.0\n",
"\n",
"% per asset cluster (each column sums to 100%):\n",
" Not exposed Asset C0 Asset C1\n",
"Global C1 28.1 10.3 28.2\n",
"Global C2 38.2 14.4 10.1\n",
"Global C3 6.6 20.4 46.2\n",
"Global C4 27.2 54.9 15.5\n",
"\n",
"============================================================\n",
"Adjusted Rand Index — coherence between global and asset-type clusterings\n",
"============================================================\n",
"(1 = identical, 0 = independent, <0 = worse than random)\n",
"\n",
" Alternative : ARI=0.0274 (n=1164 shared accounts)\n",
" Diversified : ARI=0.0344 (n=3978 shared accounts)\n",
" Equity : ARI=0.1579 (n=3689 shared accounts)\n",
" Fixed Income : ARI=0.1112 (n=3742 shared accounts)\n",
"\n",
"============================================================\n",
"Multi-asset exposure by global cluster\n",
"============================================================\n",
"\n",
"Average number of asset types per global cluster:\n",
"cluster_k4\n",
"1 1.79\n",
"2 1.50\n",
"3 2.92\n",
"4 1.40\n",
"Name: n_asset_types, dtype: float64\n",
"\n",
"Distribution of asset type count per global cluster:\n",
" 0 asset type(s) 1 asset type(s) 2 asset type(s) 3 asset type(s) 4 asset type(s)\n",
"Global C1 0.0 49.1 29.3 14.9 6.7\n",
"Global C2 0.0 64.7 23.3 9.6 2.4\n",
"Global C3 0.8 17.5 13.7 24.9 43.1\n",
"Global C4 0.4 73.9 14.8 7.3 3.5\n"
]
}
],
"source": [
"# Step 1. Merge asset cluster labels into global dataframe\n",
"dfc_cross = dfc[[ID_COL, \"cluster_k4\"]].copy()\n",
2026-04-07 20:26:19 +02:00
"\n",
2026-04-10 11:05:13 +02:00
"for asset, res in ASSET_RESULTS.items():\n",
" cluster_col = res[\"cluster_col\"]\n",
" df_a = res[\"df\"][[ID_COL, cluster_col]].copy()\n",
" dfc_cross = dfc_cross.merge(df_a, on=ID_COL, how=\"left\")\n",
2026-04-07 20:26:19 +02:00
"\n",
2026-04-10 11:05:13 +02:00
"print(\"Available columns:\", dfc_cross.columns.tolist())\n",
"print(\"Shape:\", dfc_cross.shape)\n",
"\n",
"# Step 2. Contingency tables: global clusters × asset clusters\n",
"fig, axes = plt.subplots(2, 2, figsize=(18, 12))\n",
"axes = axes.flatten()\n",
"\n",
"for i, (asset, res) in enumerate(ASSET_RESULTS.items()):\n",
" cluster_col = res[\"cluster_col\"]\n",
" if cluster_col not in dfc_cross.columns:\n",
" continue\n",
" ct = pd.crosstab(\n",
" dfc_cross[\"cluster_k4\"],\n",
" dfc_cross[cluster_col].fillna(-1).astype(int),\n",
" normalize=\"index\"\n",
" ).round(3) * 100\n",
" col_names = {\n",
" c: f\"Asset C{c}\" if c >= 0 else \"Not exposed\"\n",
" for c in ct.columns\n",
" }\n",
" ct = ct.rename(columns=col_names)\n",
" ct.index = [f\"Global C{i}\" for i in ct.index]\n",
" sns.heatmap(\n",
" ct, cmap=\"Blues\", annot=True, fmt=\".1f\",\n",
" ax=axes[i], cbar_kws={\"label\": \"%\"},\n",
" vmin=0, vmax=100,\n",
" )\n",
" axes[i].set_title(f\"Global × {asset} (% per global cluster)\")\n",
" axes[i].set_xlabel(f\"{asset} cluster\")\n",
" axes[i].set_ylabel(\"Global cluster\")\n",
"\n",
"plt.suptitle(\"Cross-Analysis: Global Clustering × Asset-Type Clustering\",\n",
" fontsize=14, y=1.02)\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"# Step 3. Detailed contingency tables (row % and column %)\n",
"for asset, res in ASSET_RESULTS.items():\n",
" cluster_col = res[\"cluster_col\"]\n",
" if cluster_col not in dfc_cross.columns:\n",
" continue\n",
" print(f\"\\n{'='*60}\")\n",
" print(f\"Global × {asset}\")\n",
" print(f\"{'='*60}\")\n",
" ct_row = pd.crosstab(\n",
" dfc_cross[\"cluster_k4\"],\n",
" dfc_cross[cluster_col].fillna(-1).astype(int),\n",
" normalize=\"index\"\n",
" ).round(3) * 100\n",
" ct_row.index = [f\"Global C{i}\" for i in ct_row.index]\n",
" ct_row.columns = [f\"Asset C{c}\" if c >= 0 else \"Not exposed\"\n",
" for c in ct_row.columns]\n",
" print(\"\\n% per global cluster (each row sums to 100%):\")\n",
" print(ct_row.to_string())\n",
"\n",
" ct_col = pd.crosstab(\n",
" dfc_cross[\"cluster_k4\"],\n",
" dfc_cross[cluster_col].fillna(-1).astype(int),\n",
" normalize=\"columns\"\n",
" ).round(3) * 100\n",
" ct_col.index = [f\"Global C{i}\" for i in ct_col.index]\n",
" ct_col.columns = [f\"Asset C{c}\" if c >= 0 else \"Not exposed\"\n",
" for c in ct_col.columns]\n",
" print(\"\\n% per asset cluster (each column sums to 100%):\")\n",
" print(ct_col.to_string())\n",
"\n",
"# Step 4. Adjusted Rand Index\n",
"from sklearn.metrics import adjusted_rand_score\n",
"\n",
"print(\"\\n\" + \"=\"*60)\n",
"print(\"Adjusted Rand Index — coherence between global and asset-type clusterings\")\n",
"print(\"=\"*60)\n",
"print(\"(1 = identical, 0 = independent, <0 = worse than random)\\n\")\n",
"\n",
"for asset, res in ASSET_RESULTS.items():\n",
" cluster_col = res[\"cluster_col\"]\n",
" if cluster_col not in dfc_cross.columns:\n",
" continue\n",
" mask = dfc_cross[cluster_col].notna()\n",
" labels_global = dfc_cross.loc[mask, \"cluster_k4\"].values\n",
" labels_asset = dfc_cross.loc[mask, cluster_col].values\n",
" ari = adjusted_rand_score(labels_global, labels_asset)\n",
" print(f\" {asset:20s} : ARI={ari:.4f} (n={mask.sum()} shared accounts)\")\n",
"\n",
"# Step 5. Multi-asset exposure by global cluster\n",
"print(\"\\n\" + \"=\"*60)\n",
"print(\"Multi-asset exposure by global cluster\")\n",
"print(\"=\"*60)\n",
"\n",
"asset_cols = [res[\"cluster_col\"] for res in ASSET_RESULTS.values()\n",
" if res[\"cluster_col\"] in dfc_cross.columns]\n",
"dfc_cross[\"n_asset_types\"] = dfc_cross[asset_cols].notna().sum(axis=1)\n",
"\n",
"print(\"\\nAverage number of asset types per global cluster:\")\n",
"print(dfc_cross.groupby(\"cluster_k4\")[\"n_asset_types\"].mean().round(2))\n",
"\n",
"print(\"\\nDistribution of asset type count per global cluster:\")\n",
"ct_multi = pd.crosstab(\n",
" dfc_cross[\"cluster_k4\"],\n",
" dfc_cross[\"n_asset_types\"],\n",
" normalize=\"index\"\n",
").round(3) * 100\n",
"ct_multi.index = [f\"Global C{i}\" for i in ct_multi.index]\n",
"ct_multi.columns = [f\"{c} asset type(s)\" for c in ct_multi.columns]\n",
"print(ct_multi.to_string())"
2026-04-07 20:26:19 +02:00
]
},
{
"cell_type": "code",
2026-04-11 10:11:35 +02:00
"execution_count": 96,
2026-04-10 11:05:13 +02:00
"id": "f276e2b4-2290-449e-ad67-1d5953073ec5",
2026-04-07 20:26:19 +02:00
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
2026-04-10 11:05:13 +02:00
"Saved: Figures/donut_geography_global.png\n"
2026-04-07 20:26:19 +02:00
]
},
{
"data": {
2026-04-10 11:05:13 +02:00
"image/png": "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
2026-04-07 20:26:19 +02:00
"text/plain": [
2026-04-10 11:05:13 +02:00
"<Figure size 1800x500 with 4 Axes>"
2026-04-07 20:26:19 +02:00
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
2026-04-10 11:05:13 +02:00
"Saved: Figures/donut_asset_global.png\n"
2026-04-07 20:26:19 +02:00
]
},
{
"data": {
2026-04-10 11:05:13 +02:00
"image/png": "iVBORw0KGgoAAAANSUhEUgAABv0AAAIgCAYAAACmv6VnAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd4U+XbB/DvSZpuyqZA2cOylyIIKMgQFQVkKwgoIIqDnygIvG5BREFkKFsQEJC9p2xKobSMlpYWaEsn3XtmnfePkEhswbYkORnfz3X1Ak5OzrlT0j45536e+xZEURRBRERERERERERERERERDZLJnUARERERERERERERERERPR4mPQjIiIiIiIiIiIiIiIisnFM+hERERERERERERERERHZOCb9iIiIiIiIiIiIiIiIiGwck35ERERERERERERERERENo5JPyIiIiIiIiIiIiIiIiIbx6QfERERERERERERERERkY1j0o+IiIiIiIiIiIiIiIjIxjHpR0RERERERERERERERGTjmPQjIiIiIovr3bs33nzzTanDIDtn7e+zXbt2wdfXF5cuXZI6FIezdOlS+Pr6Ij4+XpLnP8qbb76J3r17m/y4gP2956zpZ/zWrVto1aoV/Pz8pA7lP6WmpqJ9+/bYvXu31KEQERERkYk5SR0AEREREZnGpUuXMHbsWMO/ZTIZPD094e3tjdatW2PAgAF49tlnIQiChFFav6VLl6Jly5bo27dvuZ+r0WjQq1cvpKSk4KOPPsL7779vhggrrjyvrXfv3khISCjTcTds2IAuXbo8bnh2w9/fH1u3bsW1a9eQnp4OhUKBxo0b49lnn8Xrr7+O2rVrWzSev//+Gzdv3sSHH35o0fNaUm5uLrZs2YKTJ08iOjoaeXl58PT0RJMmTdC9e3cMGzbM4t93S+J7Tno//PADOnXqhO7duxu26cflGTNmYMKECUb7BwQE4L333oObmxvWrFmDFi1amDSe8PBwDB06FGq1GosXL8aLL75oeKxmzZoYNWoUFi1ahBdffBFubm4mPTcRERERSYdJPyIiIiI788orr+C5556DKIrIz89HdHQ0Tpw4gT179qBbt25YvHgxvLy8JI3xyJEjkp7/UZYtW4bXXnutQkm/s2fPIiUlBQ0aNMDu3bsxZcoUq0qylue1zZ49G/n5+YZ/R0VFYcWKFejX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
2026-04-07 20:26:19 +02:00
"text/plain": [
2026-04-10 11:05:13 +02:00
"<Figure size 1800x500 with 4 Axes>"
2026-04-07 20:26:19 +02:00
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
2026-04-10 11:05:13 +02:00
"Saved: Figures/donut_fund_global.png\n"
]
},
{
"data": {
"image/png": "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5ZW8/vrrF7X+W21/zZUW35nbVq5cyR133EHnzp3p378/o0ePLnF8gKVLl3LzzTc73x/GjRvH+vXriYqKYs6cOef82QHs3r2b+Ph4/v73v59zP6vVyksvvcT333/PXXfdxSeffFIpCb/ff/+dFStW8Oabb55zFmRgYCA9e/Zk6dKlFR6DiIiIiNR8muknIiIiIpXizjvvZNu2baxatYq//e1vAMTHxzNkyBCys7O59957ad68OZs3b+brr79m+/btTJkyBS+v4h9RX375ZQICAnj88cdJS0tj8uTJDB8+nGeeeYaPPvqIoUOHcscddzBr1ixee+01Wrdu7TzfuSQmJjJs2DAGDRrESy+9xMGDB5kxYwY5OTl8++23zv0OHTrEb7/9xtVXX02zZs2wWCysWbOGjz/+mLi4ON56660Sx/70008pKCjg7rvvxsfHhx9//JGRI0fSrFkzevbsSXh4OB9++CEvvfQSf/vb37jrrrvK9bMtLCwkLS2t2DZvb2/q1KlTruOc6R//+Afh4eE89dRTZGRkMHnyZB577DGWL1/unDFmsVj4xz/+wZ49e7jlllvo2rUrBw8e5OGHHyY0NLRc51uxYgWxsbHcd999REREsGLFCsaNG0dCQgLvv/8+AHfddRfTpk1j9uzZPP/888We/8svv2Cz2RgyZMg5z+PoOC/vz/hijB8/ns8//5yrrrqKoUOH4unpSVxcHCtWrMBsNuPt7c2HH37I+++/T1hYWLHkj2NG0h9//MHTTz9N8+bNeeSRRwgJCWHnzp18/vnnHDhwgM8//7zYOXNzc7n//vvp0aMH//73v0u8PqpSbGws+/bto1evXrRq1eqij7dixQqefvppIiIiePjhhwkKCmLhwoW8+uqrxMXF8dxzzxXbPy8vjwceeIAuXbowYsQIYmJimD59Ort27WLu3LlERkYCRa/nSZMmcc011zBw4ED8/f3Zs2cPs2fPZvv27cyePfuCkj96zZ3dqlWrmD59uvN9e/ny5Xz77beEhIQUi2nRokWMGDGCZs2a8fTTT+Pp6ckvv/zCihUryvwz2bx5MwBdunQ56z6FhYX861//YsWKFQwfPpwXX3yx1P1yc3MpLCws03l9fX0JDAwsti0nJ4e33nqLu+++my5dujB9+vRzHqN79+6sXbuW6OhoWrduXabzioiIiIiAkn4iIiIiUkkcs9bOnKX1ySefkJaWxoQJE5yzL+677z5Gjx7Nt99+y9y5c0skcSIjIxk/fjwmkwmAsLAw3n33Xd566y0WLlxIw4YNARg8eDB///vfmT59epmSfjExMXz66acMHjzYuc3Dw4Pp06dz7NgxZ7Kid+/eLF++3Hl+gIceeogXX3yRmTNn8vTTT1OvXr1ixzabzcyaNcuZMLjuuusYOHAgP/zwAz179iQgIIBbbrmFl156iaZNm5a7VOesWbOYNWtWsW1du3bl559/LtdxztShQwfeeOMN5/etW7fm3//+NwsWLGDo0KFA0UxGx0ygZ555pti+77//Po0bNy7z+Q4ePMisWbPo2LEjAPfffz9PP/00c+bM4e6776Zbt25ccskldO/enblz5/Lvf/+72OyY2bNn07p1a3r06HHO8xw5cgSA9u3blzm2i7Vs2TJat27NV199VWz7Cy+84Pz/LbfcwpgxY4iIiCj
"text/plain": [
"<Figure size 1800x500 with 4 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# ============================================================\n",
"# DONUT CHARTS — Geographic, Asset-type, Fund distribution\n",
"# by global cluster\n",
"# ============================================================\n",
"\n",
"def plot_donuts_by_cluster(dfc, var_col, cluster_col, title,\n",
" top_n=6, figsize=(18, 5), save_path=None):\n",
" clusters = sorted(dfc[cluster_col].unique())\n",
" n_clusters = len(clusters)\n",
" fig, axes = plt.subplots(1, n_clusters, figsize=figsize)\n",
"\n",
" all_cats = dfc[var_col].fillna(\"Unknown\").value_counts().head(top_n + 1).index.tolist()\n",
" palette = sns.color_palette(\"tab10\", len(all_cats))\n",
" color_map = {cat: palette[i] for i, cat in enumerate(all_cats)}\n",
"\n",
" for ax, cluster in zip(axes, clusters):\n",
" subset = dfc[dfc[cluster_col] == cluster][var_col].fillna(\"Unknown\")\n",
" counts = subset.value_counts()\n",
" counts = counts.reindex(all_cats, fill_value=0)\n",
" counts = counts[counts > 0]\n",
" colors = [color_map[c] for c in counts.index]\n",
" n = subset.shape[0]\n",
"\n",
" wedges, texts, autotexts = ax.pie(\n",
" counts.values,\n",
" labels=None,\n",
" colors=colors,\n",
" autopct=lambda p: f\"{p:.1f}%\" if p > 5 else \"\",\n",
" pctdistance=0.75,\n",
" wedgeprops=dict(width=0.5, edgecolor=\"white\", linewidth=1.5),\n",
" startangle=90,\n",
" )\n",
" for autotext in autotexts:\n",
" autotext.set_fontsize(8)\n",
"\n",
" ax.set_title(f\"Cluster {cluster}\\n(n={n:,})\", fontsize=11, pad=8)\n",
"\n",
" legend_patches = [\n",
" plt.matplotlib.patches.Patch(color=color_map[c], label=c)\n",
" for c in all_cats if c in dfc[var_col].fillna(\"Unknown\").values\n",
" ]\n",
" fig.legend(\n",
" handles=legend_patches,\n",
" loc=\"lower center\",\n",
" ncol=min(len(legend_patches), 7),\n",
" fontsize=15,\n",
" frameon=False,\n",
" bbox_to_anchor=(0.5, -0.05)\n",
" )\n",
" fig.suptitle(title, fontsize=13, y=1.02)\n",
" plt.tight_layout()\n",
"\n",
" if save_path:\n",
" fig.savefig(save_path, dpi=150, bbox_inches=\"tight\")\n",
" print(f\"Saved: {save_path}\")\n",
"\n",
" plt.show()\n",
"\n",
"\n",
"# ── 1. Geographic distribution ────────────────────────────────────────────\n",
"dfc[\"country_grp_clean\"] = dfc[\"country_grp\"].str.strip().str.title()\n",
"top_countries = dfc[\"country_grp_clean\"].value_counts().head(6).index\n",
"dfc[\"country_grp_clean\"] = np.where(\n",
" dfc[\"country_grp_clean\"].isin(top_countries),\n",
" dfc[\"country_grp_clean\"], \"Other\"\n",
")\n",
"\n",
"plot_donuts_by_cluster(\n",
" dfc,\n",
" var_col = \"country_grp_clean\",\n",
" cluster_col = \"cluster_k4\",\n",
" title = \"Geographic Distribution by Cluster — Global Clustering (K=4)\",\n",
" top_n = 6,\n",
" figsize = (18, 5),\n",
" save_path = \"Figures/donut_geography_global.png\"\n",
")\n",
"\n",
"# ── 2. Asset-type distribution ────────────────────────────────────────────\n",
"asset_cols = [c for c in dfc.columns if c.startswith(\"share_asset_\")]\n",
"\n",
"if asset_cols:\n",
" dfc[\"dominant_asset\"] = (\n",
" dfc[asset_cols]\n",
" .rename(columns=lambda c: c.replace(\"share_asset_\", \"\")\n",
" .replace(\"_\", \" \").title())\n",
" .idxmax(axis=1)\n",
" )\n",
" dfc.loc[dfc[asset_cols].sum(axis=1) == 0, \"dominant_asset\"] = \"Not exposed\"\n",
"\n",
" plot_donuts_by_cluster(\n",
" dfc,\n",
" var_col = \"dominant_asset\",\n",
" cluster_col = \"cluster_k4\",\n",
" title = \"Dominant Asset Type by Cluster — Global Clustering (K=4)\",\n",
" top_n = 6,\n",
" figsize = (18, 5),\n",
" save_path = \"Figures/donut_asset_global.png\"\n",
" )\n",
"\n",
"# ── 3. Fund distribution ──────────────────────────────────────────────────\n",
"fund_name_map = {\n",
" \"share_fund_carmignac_patrimoine\": \"Patrimoine\",\n",
" \"share_fund_carmignac_sécurité\": \"Sécurité\",\n",
" \"share_fund_carmignac_investissement\": \"Investissement\",\n",
" \"share_fund_carmignac_portfolio_sécurité\": \"Port. Sécurité\",\n",
" \"share_fund_carmignac_portfolio_flexible_bond\": \"Port. Flexible Bond\",\n",
" \"share_fund_carmignac_emergents\": \"Emergents\",\n",
" \"share_fund_carmignac_portfolio_patrimoine\": \"Port. Patrimoine\",\n",
" \"share_fund_carmignac_portfolio_global_bond\": \"Port. Global Bond\",\n",
" \"share_fund_carmignac_portfolio_global_bon\": \"Port. Global Bond\",\n",
" \"share_fund_carmignac_portfolio_credit\": \"Port. Credit\",\n",
" \"share_fund_carmignac_portfolio_emerging_patrimoine\": \"Port. Emerg. Patrim.\",\n",
" \"share_fund_carmignac_portfolio_grande_europe\": \"Port. Grande Europe\",\n",
" \"share_fund_carmignac_court_terme\": \"Court Terme\",\n",
" \"share_fund_carmignac_portfolio_long-short_european\": \"Port. L/S Europe\",\n",
" \"share_fund_carmignac_portfolio_climate_transition\": \"Port. Climate Trans.\",\n",
" \"share_fund_carmignac_credit_2027\": \"Credit 2027\",\n",
"}\n",
"\n",
"fund_cols = [c for c in dfc.columns if c.startswith(\"share_fund_\")]\n",
"\n",
"if fund_cols:\n",
" fund_renamed = dfc[fund_cols].rename(columns=fund_name_map)\n",
" dfc[\"dominant_fund\"] = fund_renamed.idxmax(axis=1)\n",
" dfc.loc[dfc[fund_cols].sum(axis=1) == 0, \"dominant_fund\"] = \"Not exposed\"\n",
"\n",
" plot_donuts_by_cluster(\n",
" dfc,\n",
" var_col = \"dominant_fund\",\n",
" cluster_col = \"cluster_k4\",\n",
" title = \"Dominant Fund by Cluster — Global Clustering (K=4)\",\n",
" top_n = 7,\n",
" figsize = (18, 5),\n",
" save_path = \"Figures/donut_fund_global.png\"\n",
" )\n",
" "
]
},
{
"cell_type": "markdown",
"id": "16115b05",
"metadata": {},
"source": [
"---\n",
"## 6. Part 2 — Top 400 Accounts Clustering\n",
"\n",
"### Objective\n",
"Focus on the accounts representing the highest AUM (> €5M as of October 2025), which together account for over 97% of total assets. On this restricted universe, the longer and denser time series allow for additional features — in particular, **lagged correlations between flows and fund performance** — that are too sparse to use on the full dataset.\n",
"\n",
"### Additional features (vs global clustering)\n",
"| Feature | Description |\n",
"|---|---|\n",
"| `corr_flow_fund_lag3` | Correlation between flow-to-AUM and fund return lagged 3 months |\n",
"| `corr_flow_fund_lag6` | Same at 6-month lag |\n",
"| `corr_flow_rate_lag3` | Correlation between flow-to-AUM and interest rate change lagged 3 months |\n",
"| `activity_intensity` | Number of transactions per month |\n",
"| `flow_to_aum_vol` | Volatility of the flow-to-AUM ratio |\n",
"\n",
"### Preprocessing\n",
"Identical to Part 1: MAD winsorization, clip p90 + log-transform for `gross_flow_to_aum` and `flow_freq`, RobustScaler.\n"
]
},
{
"cell_type": "code",
"execution_count": 60,
"id": "083087d6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Selected accounts (AUM > €5M): 431\n",
"Accounts after quality filters: 427\n",
"Feature set: 13 features\n",
"['log_aum_qty_mean', 'flow_freq', 'gross_flow_to_aum', 'n_tx_total', 'n_isin_total', 'avg_holding_months_per_isin', 'exit_rate_per_isin', 'flow_direction_balance', 'aum_drawdown_last', 'months_since_last_tx', 'corr_flow_fund_lag3', 'corr_flow_fund_lag6', 'corr_flow_rate_lag3']\n",
"Accounts: 427 | Features: 13\n",
"Points > 5 std after scaling: 12 (2.8%)\n",
"Features with extreme values after scaling:\n",
"months_since_last_tx 12\n",
" k inertia silhouette davies_bouldin\n",
" 2 3292.213936 0.317621 1.447549\n",
" 3 2891.756531 0.158566 1.801947\n",
" 4 2599.086861 0.167596 1.749729\n",
" 5 2420.318207 0.166294 1.797919\n",
" 6 2302.475137 0.152136 1.803708\n",
" 7 2200.224213 0.148174 1.844575\n",
" 8 2127.271606 0.143511 1.938188\n",
" 9 2069.314390 0.118802 1.986200\n",
"10 1998.491936 0.113821 2.007113\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABdEAAAGMCAYAAAA1CuswAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAA+EdJREFUeJzs3XdYFFfbBvB7d+ldiohgA12QLqgIdiyxJoqJWLAbS+zR1xYTRY2aRE2ixm7sNVGJxhZNojEKNlCwK1aKSFF63Z3vDz42roCAArvg/buuXMnOnDlzntkNM/vszHNEgiAIICIiIiIiIiIiIiKiQsSqHgARERERERERERERkbpiEp2IiIiIiIiIiIiIqBhMohMRERERERERERERFYNJdCIiIiIiIiIiIiKiYjCJTkRERERERERERERUDCbRiYiIiIiIiIiIiIiKwSQ6EREREREREREREVExmEQnIiIiIiIiIiIiIioGk+hERERERERERERERMVgEp2IiIiICjlw4ADs7e0RFRVV6fv29fXFzJkzK32/RERERERERWESnYiIiEiNFCSvIyIilJanpqbi448/houLC/755x8Vja78hIaGYuXKlUhJSVH1UCpMVFQU7O3tS/VPZf9YcejQIdjb26NJkyZFro+MjMSIESPQpEkTNG/eHP/73/+QlJRUqJ1cLseGDRvg6+sLFxcX9OzZE7///nuZx/Ptt9/C3t4ekydPLvO2VUVmZiZWrlyJCxcuqHooRERERFRGGqoeABERERG9WVpaGoYPH447d+5g1apVaNOmjaqH9M7CwsKwatUq9O7dG0ZGRkrrjh8/DpFIpKKRlR9TU1N8++23Sss2b96MZ8+eYdasWYXaVpb09HR899130NPTK3L9s2fPMHDgQBgaGmLKlCnIyMjAzz//jLt37+KXX36BlpaWou3333+P9evXo2/fvnBxccGff/6JqVOnQiQSoXv37qUajyAIOHLkCKytrfH3338jLS0NBgYG5RKrOsnMzMSqVaswfvx4eHl5qXo4RERERFQGTKITERERqbG0tDSMGDECt27dwqpVq9C2bVtVD6nCvZqkrcr09PTw0UcfKS07evQoUlJSCi2vTGvWrIG+vj68vLzw559/Flq/du1aZGZm4sCBA6hduzYAwNXVFcOGDcPBgwfh7+8PAIiLi8PmzZsxcOBAfPXV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
"text/plain": [
"<Figure size 1500x400 with 3 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"K=2 | sil=0.3176 | db=1.4475\n",
" n_accounts pct\n",
"cluster_k2 \n",
"0 325 76.1\n",
"1 102 23.9\n",
"\n",
"K=4 | sil=0.1676 | db=1.7497\n",
" n_accounts pct\n",
"cluster_k4 \n",
"0 127 29.7\n",
"1 67 15.7\n",
"2 38 8.9\n",
"3 195 45.7\n",
"\n",
"K=5 | sil=0.1663 | db=1.7979\n",
" n_accounts pct\n",
"cluster_k5 \n",
"0 67 15.7\n",
"1 37 8.7\n",
"2 62 14.5\n",
"3 137 32.1\n",
"4 124 29.0\n",
"\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1600x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== Cluster medians — K=2 ===\n",
2026-04-07 20:26:19 +02:00
" log_aum_qty_mean gross_flow_to_aum flow_freq n_tx_total n_isin_total avg_holding_months_per_isin exit_rate_per_isin flow_direction_balance aum_drawdown_last aum_final_to_peak months_since_last_tx corr_flow_fund_lag3 corr_flow_fund_lag6 corr_flow_rate_lag3\n",
"cluster_k2 \n",
"0 11.425 4.452 0.786 1872.0 32.0 48.347 0.637 0.073 1.00 0.00 0.0 0.054 0.050 -0.041\n",
"1 10.777 2.652 0.154 24.0 7.0 35.523 0.381 0.347 0.17 0.83 2.0 0.024 0.014 -0.025\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1600x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== Cluster medians — K=5 ===\n",
" log_aum_qty_mean gross_flow_to_aum flow_freq n_tx_total n_isin_total avg_holding_months_per_isin exit_rate_per_isin flow_direction_balance aum_drawdown_last aum_final_to_peak months_since_last_tx corr_flow_fund_lag3 corr_flow_fund_lag6 corr_flow_rate_lag3\n",
"cluster_k5 \n",
"0 10.975 1.488 0.557 819.0 25.0 52.905 0.567 -0.487 1.000 0.000 0.0 0.002 0.016 -0.024\n",
"1 11.174 1.389 0.043 4.0 2.0 42.429 0.250 0.557 0.303 0.697 19.0 -0.000 -0.007 -0.012\n",
"2 10.357 4.383 0.372 90.5 12.5 32.149 0.434 0.287 0.077 0.923 1.0 0.042 0.025 -0.034\n",
"3 11.045 5.471 0.777 1448.0 24.0 40.857 0.688 0.245 1.000 0.000 0.0 0.009 -0.008 0.005\n",
"4 11.994 5.155 0.926 4935.5 47.5 57.100 0.620 0.037 1.000 0.000 0.0 0.158 0.130 -0.140\n",
"seg_2D\n",
"Highly active 137\n",
"Dormant 136\n",
"Small rebalancers 77\n",
"Occasional large movers 77\n",
"Name: count, dtype: int64\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 800x500 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== Overall churn rates ===\n",
"churn_hard 0.684\n",
"churn_soft 0.775\n",
"churn_warning 0.321\n",
"dtype: float64\n",
"\n",
"=== Churn rates by cluster — K=2 ===\n",
" n_accounts churn_hard churn_soft churn_warning\n",
"cluster_k2 \n",
"0 325 0.883 0.969 0.385\n",
"1 102 0.049 0.157 0.118\n"
]
},
{
"data": {
"image/png": "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/fr1i/e6k3Lo0CHt2rVLH3/8cYLr79+/r/3796tFixbmUCFJLVu2lJOTk3bs2GFetnPnThkMBnXo0MG8LHv27Grbtq2OHDmS7Lq2bdum2rVr69VXX5WHh4f5OHja2bNnNXjwYL366quqWLGiGjVqpDlz5li0CQsL08cff6w6derIy8tLDRo00Pjx4xUdHW1uk5z3O+4z6MljTkp4blHccXbmzBl16dJFlSpVUt26dbVkyRKL7ZL6DLlw4YI++OADvfbaa/L29tbrr7+uoUOH6t9//03yvduzZ48ePXqk2rVrJ9rm0aNH+vDDD/XDDz9owoQJat++fZL7TK7q1atbhIq4ZXny5NG5c+fita9du7YOHz6sO3fupEn/gDWy9ldkQCb0008/qWjRoika1nL48GF99913evfdd5UrVy6tXr1agwYN0k8//aS8efOmqo4DBw5ox44d6ty5s/LmzavChQubx9VPnjxZuXPn1sCBA3XlyhWtXLlSkyZN0ty5c1PVV9xJwpNDay5fvqzdu3ercePGKlKkiG7evKmvv/5afn5+2r59u9zd3eXh4aFBgwZp/vz56tChg6pWrSpJ5vcuJCREvXv3lpeXlwYOHCg7OzvzN//r1q0zn4yPHz9eu3btkp+fnzw8PHTnzh0dPnxYZ8+eVYUKFZ5Z/5AhQ1S4cGENGzZMR48e1erVq3Xv3j3zSXjLli01duxYnT59WmXKlDFvd+zYMV24cEH9+/dPdN9xf/C7du0qg8GQwnc2ZYKCgjR58mQ1atRIXbt21cOHD3Xq1Cn9/vvvat68ud58801duHBB//nPfzR69GjzsRV39WnBggWaN2+e3n77bbVt21bh4eFas2aNOnfurM2bN1v8/3vnzh317t1bTZs2VYsWLeTq6pqury2lbt68qY4dOyoyMlJdunRR3rx5tWnTJvXv31/z58/Xm2++adF+wYIFsrOzU+/evXXr1i2tXLlS3bp105YtW5QjR44k+zIajeYhMInN6zh16pQePXokLy8vi+WOjo4qV66cxfyAkydPqkSJEhYBRJL5eD958qQKFSqUZE1hYWE6ePCgpk6dKklq2rSpVq5cqU8++USOjo7mdn/99Zc6d+4sBwcHdejQQYULF9alS5f0448/mr8ZDwsLU9u2bfXvv/+qffv2KlWqlMLCwrRr1y5FRUXJ0dExxe93ct29e1e9evXSm2++qbffflu7du3SzJkzVaZMGdWrVy/Jz5Do6Gj17NlT0dHR8vPzk5ubm8LCwrRnzx7du3dPL730UqL9HjlyRHny5FHhwoUTXG80GvXhhx/q+++/17hx49SxY8d4bWJiYp4ZYOLkyZMnXph40oMHD/TgwYME/x5UqFBBJpNJR44cUf369ZPVH5BeCBZAGrp//77CwsLk6+ubou3Onj2r4OBgFStWTJJUs2ZNtWzZUtu3b5efn1+qajl//ry2bdumV155xbws7uQlT548Wr58ufnb89jYWK1evVr//vtvkn9s49y9e1fS4+EvBw4c0Lp165QvXz5Vr17d3MbT01O7du2y+GPZsmVLvf322/r22281YMAAubm56fXXX9f8+fNVuXJli2EsJpNJEyZMUM2aNbV06VJzrR07dlTTpk01d+5cLV++XJK0d+9etW/f3uJb/ScnXD5LkSJFtGDBAkm
"text/plain": [
"<Figure size 800x400 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== Churn rates by cluster — K=5 ===\n",
" n_accounts churn_hard churn_soft churn_warning\n",
"cluster_k5 \n",
"0 67 0.612 0.925 0.955\n",
"1 37 0.108 0.297 0.108\n",
"2 62 0.000 0.032 0.048\n",
"3 137 0.964 0.978 0.117\n",
"4 124 0.927 0.984 0.403\n"
]
},
{
"data": {
"image/png": "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6ty5s21azpw51bFjRx06dCjFdW3evFn169dX3bp15e3tbdsPHnTq1CkNHTpUdevWVZUqVdSsWTN9+OGHdvNcuXJF77zzjho0aCA/Pz81btxY48aNU2xsrG2elGzv+M+g+/c5KfGxRfH72V9//aUePXqoatWqatiwoRYtWmS3XHKfIWfPntUbb7yhp59+Wv7+/nrmmWc0fPhw/ffff8luu127dunu3buqX79+kvPcvXtXb775pr755huNHz9enTp1SrbNtLh165bMZnOy89SvX1/fffedrFZruq8fSK3s/RMZkAV99913KlmyZKq6tRw8eFBfffWVXn75ZeXKlUsrV67UkCFD9N133yl//vxpqmPfvn3atm2bunXrpvz586t48eK2fvWTJ09Wnjx5NHjwYF28eFHLly/XxIkTNWvWrDStK/4g4f7T9BcuXNDOnTvVvHlzlShRQteuXdOnn36q7t27a8uWLSpSpIi8vb01ZMgQBQcHq3PnznrqqackybbtwsLC1L9/f/n5+Wnw4MFycnKy/fK/Zs0a28H4uHHjtGPHDnXv3l3e3t76559/dPDgQZ06dUqVK1d+aP3Dhg1T8eLFNWLECP36669auXKl/v33X9tBeNu2bfXuu+/qxIkT8vHxsS13+PBhnT17VgMHDkyy7YMHD+qff/5Rz549ZTKZUrllUyc0NFSTJ09Ws2bN1LNnT925c0fHjx/Xb7/9ptatW+v555/X2bNn9eWXX2r06NG2fSv+7NO8efM0e/Zsvfjii+rYsaMiIyO1atUqdevWTRs3brT7+/7zzz/q37+/WrZsqTZt2qhgwYIZ+tpS69q1a+rSpYuio6PVo0cP5c+fXxs2bNDAgQMVHBys559/3m7+efPmycnJSf3799f169e1fPly9erVS1988YXc3NySXZfZbLZ1gUlqXMfx48d19+5d+fn52U13dXVVxYoV7cYHHDt2TGXKlLELIJJs+/uxY8dUrFixZGu6cuWK9u/fr6lTp0qSWrZsqeXLl+t///ufXF1dbfP9+eef6tatm3LkyKHOnTurePHiOn/+vL799lsNHz7c1lbHjh3133//qVOnTipXrpyuXLmiHTt2KCYmRq6urqne3il18+ZN9evXT88//7xefPFF7dixQzNmzJCPj48aNWqU7GdIbGys+vbtq9jYWHXv3l1eXl66cuWKdu3apX///Ve5c+dOcr2HDh1Svnz5VLx48USfN5vNevPNN/X1119r7Nix6tKlS4J54uLiHhpg4uXLl0/Ozva/9fbs2VNRUVFycXFRgwYNFBgYqDJlyiRYtnLlylq2bJlOnjxp9/kEOALBAkhHt27d0pUrV9SkSZNULXfq1Clt3bpVpUqVkiTVqVNHbdu21ZYtW9S9e/c01XLmzBlt3rxZTz75pG1a/MFLvnz5tHTpUtuv5xaLRStXrtR///2X7JdtvJs3b0q61/1l3759WrNmjQoUKKBatWrZ5vH19dWOHTvsvizbtm2rF198UZ9//rkGDRokLy8vPfPMMwoODla1atXsurFYrVaNHz9ederU0eLFi221dunSRS1bttSsWbO0dOlSSdLu3bvVqVMnu1/17x9w+TAlSpTQvHnzJN3rrubp6ak1a9aoT58+qlChgpo3b65JkyZp06ZNeuutt2zLbdq0SR4eHnr
"text/plain": [
"<Figure size 800x400 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"===== Cluster profile — K=2 =====\n",
" n_accounts aum_mean_med freq_med gross_flow_to_aum_med n_tx_med holding_med exit_rate_med flow_dir_med drawdown_med months_inactive_med corr_fund_lag3_med corr_rate_lag3_med\n",
"cluster_k2 \n",
"0 325 91586.099 0.786 4.452 1872.0 48.347 0.637 0.073 1.00 0.0 0.054 -0.041\n",
"1 102 47913.297 0.154 2.652 24.0 35.523 0.381 0.347 0.17 2.0 0.024 -0.025\n",
"\n",
"===== Cluster profile — K=5 =====\n",
" n_accounts aum_mean_med freq_med gross_flow_to_aum_med n_tx_med holding_med exit_rate_med flow_dir_med drawdown_med months_inactive_med corr_fund_lag3_med corr_rate_lag3_med\n",
"cluster_k5 \n",
"3 137 62616.679 0.777 5.471 1448.0 40.857 0.688 0.245 1.000 0.0 0.009 0.005\n",
"4 124 161746.356 0.926 5.155 4935.5 57.100 0.620 0.037 1.000 0.0 0.158 -0.140\n",
"0 67 58391.143 0.557 1.488 819.0 52.905 0.567 -0.487 1.000 0.0 0.002 -0.024\n",
"2 62 31466.909 0.372 4.383 90.5 32.149 0.434 0.287 0.077 1.0 0.042 -0.034\n",
"1 37 71234.484 0.043 1.389 4.0 42.429 0.250 0.557 0.303 19.0 -0.000 -0.012\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAuEAAAKyCAYAAAB7WgDLAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsvXecZUWZPv7UyTffvp17untyACY4IAxJUIKKoCxZlMEALKyk1d0V+MpPQREUWZRkZAUWwQSigCQFUYkSlDBMYlJP93TuvvneE+v3R9Wpus3MwIAjDO55Ph8+9Nx77jl16tSpeut9n/d5CaWUIkKECBEiRIgQIUKECG8blHe6AREiRIgQIUKECBEi/F9DZIRHiBAhQoQIESJEiPA2IzLCI0SIECFChAgRIkR4mxEZ4REiRIgQIUKECBEivM2IjPAIESJEiBAhQoQIEd5mREZ4hAgRIkSIECFChAhvMyIjPEKECBEiRIgQIUKEtxmRER4hQoQIESJEiBAhwtuMyAiPECFChAgRIkSIEOFtRmSER9hl8Ktf/Qrz589Hf3//O92UdxTLly/H8uXL3+lmRNgGomcTIQLw4osvYuHChRgYGHinm7JNXHXVVTjhhBPe6WZEiPCGiIzwdylCg/Wll15607+t1Wq47rrr8PTTT/8DWrbr4o9//COuu+66t/WaF154IebPny/+W7p0KQ499FCcd955ePDBBxEEwU65zvPPP4/rrrsOxWJxp5zvnUbYb3vuuSfq9fpW32/cuFH06f/8z/+86fMPDw/juuuuw8qVK3dGc3cZLF++fMp4295/b/d7UCwWsd9++2H+/Pl44IEHtvrecRx861vfwoEHHojFixfjhBNOwOOPP77Ncz3//PM4+eSTsWTJEhxwwAG47LLLUKlU3lR71q1bh/nz52PRokX/NO/MtnDPPffg5ptv3unn/fa3v40jjzwS06ZNE58tX74cRx111FbHPvnkk1iyZAmOOeYY5PP5v+u6hxxyyDbH85e//OUpx33qU5/CqlWr8PDDD/9d14sQ4R8N7Z1uQIS3H7VaDddffz3OOeccLFu27J1uztuGP/7xj7jttttw7rnnvq3XNQwDl112GQDAtm0MDAzgD3/4A8477zzss88++N73vodkMimOfytG5V//+ldcf/31OOaYY5BOp3da299JaJqGer2ORx55BB/5yEemfHfPPffANE3Ytv2Wzj0yMoLrr78e06ZNw2677bbDv3srz+btxFlnnYXjjz9e/Pull17CrbfeirPOOguzZs0Sn8+fP/9tbde11167zc1UiAsvvBAPPvggTj31VMyYMQN33XUX/vVf/xW33HIL3vve94rjVq5ciU9/+tOYPXs2LrzwQgwNDeHHP/4xNm7ciBtvvHGH23P33XejtbUVhUIBDz744D+t1/Tee+/F2rVr8elPf3qnnXPlypV44okn8LOf/ewNj33yySdx1llnYebMmbjpppuQzWb/7uvvtttu+MxnPjPls5kzZ075d2trKw499FD8+Mc/xqGHHvp3XzNChH8UIiM8wk5DtVpFPB5/p5vxtoJSCtu2YVnWdo/RNA1HH330lM8+//nP44c//CH++7//GxdffDG+853viO8Mw/hHNfddBcMwsOeee+K3v/3tVkb4vffei/e///148MEH35a21Go1xGKxXf7ZHHDAAVP+bZombr31Vuy///7v2IZ7zZo1+OlPf4rPfe5zuPbaa7f6/sUXX8Rvf/tbfPGLX8Rpp50GAPiXf/kXHHXUUbjqqqumGHtXX3010uk0br31VrFx7e7uxsUXX4zHHnsMBx544Bu2h1KKe+65B0cddRT6+/tx9913/9Ma4f8I3Hnnnejq6sJ73vOe1z3uL3/5C/7t3/4NM2bM2GkGOAC0t7dvNZ9uC0cccQTOP/98bN68GT09PTvl2hEi7GxEdJR/Ilx44YVYunQphoeH8bnPfQ5Lly7Fvvvui29+85vwfR8A0N/fj/322w8AcP31128zPL1u3TrhpV20aBGOPfbYrcJ6IR3mL3/5Cy655BLst99+OPjgg1+3fevWrcP555+PfffdF4sXL8aHPvQhfPvb337d32wvdH7IIYfgwgsvFP92XRfXX389PvjBD2LRokVYtmwZTj75ZBHSvvDCC3HbbbeJc4b/hQiCADfffDOOPPJILFq0CPvvvz++/OUvo1AobHXdM888E3/+859x7LHHYvHixTvkEdoW/vVf/xUHHnggHnjgAWzYsEF8vi3e8a233oojjzwSS5Yswd57741jjz0W99xzDwDguuuuw5VXXgkAOPTQQ8W9hdz6O++8E6eeeir2228/LFy4EB/5yEdw++23b7NPzzzzTDz77LM4/vjjsWjRIhx66KH49a9/vdWxxWIRl19+OQ455BAsXLgQBx10EL74xS9iYmJCHOM4Dq699locfvjhWLhwIQ4++GBceeWVcBxnh/voqKOOwp/+9KcplIEXX3wRGzdu3GboO5/P45vf/CY++tGPYunSpdhzzz1x+umnY9WqVeKYp59+WniLL7roItFfv/rVrwDIsPrLL7+MT37yk1iyZAmuvvpq8V3js7nggguwaNEirFu3bko7TjvtNOy9994YHh7e4Xt9O3HbbbfhyCOPxMKFC3HggQfi0ksv3YqW0dgPH//4x7F48WIccsgh+OlPf/qmrvX1r38dhx122BSPdiMeeOABqKqKk046SXxmmiaOP/54/PWvf8Xg4CAAoFwu44knnsDHPvaxKZGjo48+GvF4HPfff/8Otee5557DwMAAPvKRj+AjH/kInn32WQwNDW11XBAEuOWWW/DRj34UixYtwr777ovTTjttKwrgb37zGxx//PHi3fzkJz+Jxx57bMoxO9Lfr53TQrx2zD399NOYP38+7rvvPnzve9/DQQcdhEWLFuFTn/oUNm3aNOV3jz76KAYGBsQYP+SQQ8T3rzenvB4efvhh7LvvviCEbPeYZ599FmeeeSZ6e3tx0003oamp6Q3P+2bgOA6q1errHrP//vuL9kaIsKsi8oT/k8H3fZx22mlYvHgxvvjFL+LJJ5/Ej3/8Y/T09OATn/gEcrkcLrnkElxyySU4/PDDcfjhhwOQ4em1a9fi5JNPRnt7O8444wyxuJ199tm47rrrxPEhLr30UuRyOZx99tmvOymuWrUKn/zkJ6FpGk466SRMmzYNfX19eOSRR/D5z3/+777v66+/Hj/4wQ9wwgknYPHixSiXy3j55ZexYsUKHHDAATjppJMwMjKCxx9/XBisjfjyl7+Mu+66C8ceeyyWL1+O/v5+3HbbbXjllVfw05/+FLqui2M3bNiA//iP/8BJJ52EE088catQ6JvBxz72MTz22GN44okntnueX/ziF7jsssvwoQ99CKeeeips28bq1avxwgsv4KMf/SgOP/xwbNy4Effeey8uuugiseDlcjkAwE9/+lPMnTsXhxxyCDRNwx/+8AdceumloJTik5/85JRrbdq0Ceeffz6OP/54HHPMMbjzzjtx4YUXYo899sDcuXMBAJVKBZ/85Cexbt06HHfccdh9990xOTmJRx55BMPDw8jlcgiCAP/2b/+G5557DieeeCJmz56NNWvW4JZbbsHGjRvx3e9+d4f65/DDD8dXvvIVPPTQQ8JwvvfeezFr1izsvvvuWx2/efNm/P73v8eHP/xhdHd3Y2xsDD//+c9xyimn4Le//S3a29sxe/ZsnHfeebj22mtx0kknYa+99gIA7LnnnuI8+XweZ5xxBo488kh87GMfQ3Nz8zbb96UvfQlPPfUULrjgAvz85z+Hqqr42c9+hsceewxXXnkl2tvbd+g+305cd911uP7667H//vvj5JNPxoYNG/DTn/4UL7300lZjvVAo4F//9V9xxBFH4Mgjj8T999+PSy65BLquT6G9bA/3338//vrXv+K+++7bbhLfypUrMWPGjCmGNQAsXrxYfN/Z2YnVq1fD8zwsXLhwynGGYWC33XbbYX7/Pffcg97eXixevBjz5s2DZVm49957cfrpp0857ktf+hJ+9atf4aCDDsLxxx8P3/fx7LP
"text/plain": [
"<Figure size 800x700 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# 6a. Account selection — AUM > €5M as of October 2025\n",
"ref_date = pd.Timestamp(\"2025-10-01\")\n",
"df_ref = df_aum[df_aum[\"month\"] == ref_date]\n",
"\n",
"aum_account = (\n",
" df_ref.groupby(ID_COL)[AUM_VAL_COL].sum()\n",
" .reset_index().sort_values(AUM_VAL_COL, ascending=False)\n",
")\n",
"aum_account = aum_account[aum_account[AUM_VAL_COL] > 5_000_000]\n",
"selected_accounts = aum_account[ID_COL]\n",
"print(f\"Selected accounts (AUM > €5M): {len(selected_accounts)}\")\n",
"\n",
"df_month_top400 = df_month[df_month[ID_COL].isin(selected_accounts)].copy()\n",
"\n",
"# ── 6b. Feature engineering ───────────────────────────────────────────────\n",
"dfc_top400 = df_client_base[df_client_base[ID_COL].isin(selected_accounts)].copy()\n",
"\n",
2026-04-11 10:11:35 +02:00
"dfc_top400[\"aum_qty_mean\"] = np.log1p(dfc_top400[\"aum_qty_mean\"].clip(lower=0))\n",
2026-04-07 20:26:19 +02:00
"dfc_top400[\"log_gross_flow_qty_mean\"] = np.log1p(dfc_top400[\"gross_flow_qty_mean\"].clip(lower=0))\n",
"dfc_top400[\"gross_flow_to_aum\"] = dfc_top400[\"gross_flow_qty_sum\"] / (dfc_top400[\"aum_qty_mean\"].abs() + EPS)\n",
"dfc_top400[\"flow_direction_balance\"] = np.where(\n",
" dfc_top400[\"gross_flow_qty_sum\"] > 0,\n",
" dfc_top400[\"net_flow_qty_sum\"] / dfc_top400[\"gross_flow_qty_sum\"], np.nan\n",
")\n",
"dfc_top400[\"exit_rate_per_isin\"] = np.where(\n",
" dfc_top400[\"n_isin_total\"] > 0,\n",
" dfc_top400[\"full_exit_count\"] / dfc_top400[\"n_isin_total\"], np.nan\n",
")\n",
"dfc_top400[\"aum_drawdown_last\"] = dfc_top400[\"aum_drawdown_last\"].clip(0, 1)\n",
"dfc_top400[\"aum_final_to_peak\"] = np.where(\n",
" dfc_top400[\"aum_qty_max\"] > 0,\n",
" (dfc_top400[\"aum_qty_last\"] / dfc_top400[\"aum_qty_max\"]).clip(0, 1), np.nan\n",
")\n",
"\n",
"# Performance reactivity features — viable on large accounts (denser time series)\n",
"def corr_lag(x, y, lag):\n",
" x = np.asarray(x, dtype=float)\n",
" y = np.asarray(y, dtype=float)\n",
" mask = np.isfinite(x) & np.isfinite(y)\n",
" x, y = x[mask], y[mask]\n",
" if len(x) <= lag + 3:\n",
" return np.nan\n",
" return pd.Series(x[lag:]).corr(pd.Series(y[:-lag]))\n",
"\n",
"rows_corr = []\n",
"for acc, g in df_month_top400.groupby(ID_COL):\n",
" g = g.sort_values(\"month\")\n",
" rows_corr.append({\n",
" ID_COL: acc,\n",
" \"corr_flow_fund_lag3\": corr_lag(g[\"flow_to_aum_m\"].values, g[\"ret_fund_m\"].values, 3),\n",
" \"corr_flow_fund_lag6\": corr_lag(g[\"flow_to_aum_m\"].values, g[\"ret_fund_m\"].values, 6),\n",
" \"corr_flow_rate_lag3\": corr_lag(g[\"flow_to_aum_m\"].values, g[\"delta_rate_m\"].values, 3),\n",
" })\n",
"df_corr_top400 = pd.DataFrame(rows_corr)\n",
"dfc_top400 = dfc_top400.merge(df_corr_top400, on=ID_COL, how=\"left\")\n",
"\n",
"# Recency feature\n",
"dfc_top400 = add_months_since_last_tx(dfc_top400, df_month_top400, ID_COL)\n",
"\n",
"# Quality filters\n",
"dfc_top400 = dfc_top400[\n",
" (dfc_top400[\"n_months\"] >= 6) & (dfc_top400[\"aum_qty_mean\"] > 0)\n",
"].copy()\n",
"\n",
"# Geographic grouping\n",
"top_countries_t = dfc_top400[\"country\"].fillna(\"Unknown\").value_counts().head(10).index\n",
"top_regions_t = dfc_top400[\"region\"].fillna(\"Unknown\").value_counts().head(10).index\n",
"dfc_top400[\"country_grp\"] = np.where(dfc_top400[\"country\"].isin(top_countries_t), dfc_top400[\"country\"], \"Other\")\n",
"dfc_top400[\"region_grp\"] = np.where(dfc_top400[\"region\"].isin(top_regions_t), dfc_top400[\"region\"], \"Other\")\n",
"\n",
"print(f\"Accounts after quality filters: {len(dfc_top400)}\")\n",
"\n",
"# ── 6c. Feature selection ─────────────────────────────────────────────────\n",
"# Removed vs initial set:\n",
"# flow_to_aum_vol : EXTREME values (max 2.5e13), ratio~0 — no discriminant power\n",
"# activity_intensity : highly redundant with n_tx_total (corr=0.98)\n",
"# avg_n_isin_held : highly redundant with n_isin_total (corr=0.91)\n",
"base_features_top400 = [\n",
2026-04-11 10:11:35 +02:00
" \"aum_qty_mean\",\n",
2026-04-07 20:26:19 +02:00
" \"flow_freq\",\n",
" \"gross_flow_to_aum\",\n",
" \"n_tx_total\",\n",
" \"n_isin_total\",\n",
" \"avg_holding_months_per_isin\",\n",
" \"exit_rate_per_isin\",\n",
" \"flow_direction_balance\",\n",
" \"aum_drawdown_last\",\n",
" \"months_since_last_tx\",\n",
" \"corr_flow_fund_lag3\",\n",
" \"corr_flow_fund_lag6\",\n",
" \"corr_flow_rate_lag3\",\n",
"]\n",
"all_features_top400 = [c for c in base_features_top400 if c in dfc_top400.columns]\n",
"print(f\"Feature set: {len(all_features_top400)} features\")\n",
"print(all_features_top400)\n",
"\n",
"# ── 6d. Preprocessing — MAD winsorization + RobustScaler ─────────────────\n",
"dfc_top400_clean = dfc_top400.copy()\n",
"\n",
"# Impute NaN with 0 (absence of activity = neutral)\n",
"for col in [\"flow_direction_balance\", \"months_since_last_tx\",\n",
" \"corr_flow_fund_lag3\", \"corr_flow_fund_lag6\", \"corr_flow_rate_lag3\"]:\n",
" if col in dfc_top400_clean.columns:\n",
" dfc_top400_clean[col] = dfc_top400_clean[col].fillna(0)\n",
"\n",
"# MAD 3-sigma clip\n",
"for col in [\"n_isin_total\", \"exit_rate_per_isin\", \"avg_holding_months_per_isin\",\n",
" \"aum_drawdown_last\", \"aum_final_to_peak\"]:\n",
" if col in dfc_top400_clean.columns:\n",
" dfc_top400_clean[col] = winsorize_mad(dfc_top400_clean[col], n_sigma=3)\n",
"\n",
"# Clip p90 + log-transform (heavy right tails / asymmetric distributions)\n",
"for col, p_clip in [\n",
" (\"gross_flow_to_aum\", 90),\n",
"]:\n",
" if col in dfc_top400_clean.columns:\n",
" vals = dfc_top400_clean[col].to_numpy(dtype=float)\n",
" dfc_top400_clean[col] = np.log1p(np.clip(vals, 0, np.nanpercentile(vals, p_clip)))\n",
"\n",
"for col in [\"flow_freq\", \"n_tx_total\"]:\n",
" if col in dfc_top400_clean.columns:\n",
" vals = dfc_top400_clean[col].to_numpy(dtype=float)\n",
" dfc_top400_clean[col] = np.log1p(np.clip(vals, 0, None))\n",
"\n",
"# months_since_last_tx : MAD~0 (64% zeros) → log-transform before scaling\n",
"# so RobustScaler can normalize the right tail (values 1-54)\n",
"col = \"months_since_last_tx\"\n",
"if col in dfc_top400_clean.columns:\n",
" vals = dfc_top400_clean[col].to_numpy(dtype=float)\n",
" dfc_top400_clean[col] = np.log1p(np.clip(vals, 0, None))\n",
"\n",
"# MAD 3-sigma clip for log-scale variables\n",
2026-04-11 10:11:35 +02:00
"for col in [\"aum_qty_mean\"]:\n",
2026-04-07 20:26:19 +02:00
" if col in dfc_top400_clean.columns:\n",
" dfc_top400_clean[col] = winsorize_mad(dfc_top400_clean[col], n_sigma=3)\n",
"\n",
"# Build X\n",
"X_top400 = dfc_top400_clean[all_features_top400].copy()\n",
"X_top400 = X_top400.loc[:, ~X_top400.columns.duplicated()]\n",
"X_top400 = X_top400.replace([np.inf, -np.inf], np.nan).fillna(X_top400.median())\n",
"\n",
"scaler_top400 = RobustScaler()\n",
"X_top400_scaled = scaler_top400.fit_transform(X_top400)\n",
"\n",
"# Scaling diagnostic\n",
"X_df_t = pd.DataFrame(X_top400_scaled, columns=X_top400.columns)\n",
"extreme = (X_df_t.abs() > 5).any(axis=1).sum()\n",
"print(f\"Accounts: {X_top400.shape[0]} | Features: {X_top400.shape[1]}\")\n",
"print(f\"Points > 5 std after scaling: {extreme} ({extreme/len(X_df_t):.1%})\")\n",
"\n",
"extreme_by_feat = (X_df_t.abs() > 5).sum().sort_values(ascending=False)\n",
"if extreme_by_feat[extreme_by_feat > 0].shape[0] > 0:\n",
" print(\"Features with extreme values after scaling:\")\n",
" print(extreme_by_feat[extreme_by_feat > 0].to_string())\n",
"else:\n",
" print(\"All features clean after scaling.\")\n",
"\n",
"# ── 6e. K-selection ───────────────────────────────────────────────────────\n",
"rows_k = []\n",
"for k in range(2, 11):\n",
" km = KMeans(n_clusters=k, n_init=30, random_state=RANDOM_STATE)\n",
" labels = km.fit_predict(X_top400_scaled)\n",
" rows_k.append({\n",
" \"k\": k, \"inertia\": km.inertia_,\n",
" \"silhouette\": silhouette_score(X_top400_scaled, labels),\n",
" \"davies_bouldin\": davies_bouldin_score(X_top400_scaled, labels),\n",
" })\n",
"df_kdiag_top400 = pd.DataFrame(rows_k)\n",
"print(df_kdiag_top400.to_string(index=False))\n",
"\n",
"fig, axes = plt.subplots(1, 3, figsize=(15, 4))\n",
"for ax, col, title in zip(axes,\n",
" [\"inertia\", \"silhouette\", \"davies_bouldin\"],\n",
" [\"Elbow / Inertia\", \"Silhouette (higher=better)\", \"Davies-Bouldin (lower=better)\"]):\n",
" ax.plot(df_kdiag_top400[\"k\"], df_kdiag_top400[col], marker=\"o\")\n",
" ax.set_title(title)\n",
" ax.set_xlabel(\"K\")\n",
"plt.suptitle(\"K-selection — Top 400 Accounts\")\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"# ── 6f. Final clustering K=2, 4, 5 ───────────────────────────────────────\n",
"RESULTS_TOP400 = {}\n",
"for k in [2, 4, 5]:\n",
" km = KMeans(n_clusters=k, n_init=50, random_state=RANDOM_STATE)\n",
" dfc_top400[f\"cluster_k{k}\"] = km.fit_predict(X_top400_scaled)\n",
" RESULTS_TOP400[k] = {\n",
" \"model\": km,\n",
" \"silhouette\": silhouette_score(X_top400_scaled, dfc_top400[f\"cluster_k{k}\"]),\n",
" \"davies_bouldin\": davies_bouldin_score(X_top400_scaled, dfc_top400[f\"cluster_k{k}\"]),\n",
" }\n",
" print(f\"K={k} | sil={RESULTS_TOP400[k]['silhouette']:.4f} \"\n",
" f\"| db={RESULTS_TOP400[k]['davies_bouldin']:.4f}\")\n",
" counts = dfc_top400[f\"cluster_k{k}\"].value_counts().sort_index()\n",
" props = counts / counts.sum() * 100\n",
" print(pd.DataFrame({\"n_accounts\": counts, \"pct\": props.round(1)}))\n",
" print()\n",
"\n",
"# ── 6g. Profile variables for visualization ───────────────────────────────\n",
"profile_vars_top400 = [\n",
2026-04-11 10:11:35 +02:00
" \"aum_qty_mean\",\n",
2026-04-07 20:26:19 +02:00
" \"gross_flow_to_aum\",\n",
" \"flow_freq\",\n",
" \"n_tx_total\",\n",
" \"n_isin_total\",\n",
" \"avg_holding_months_per_isin\",\n",
" \"exit_rate_per_isin\",\n",
" \"flow_direction_balance\",\n",
" \"aum_drawdown_last\",\n",
" \"aum_final_to_peak\",\n",
" \"months_since_last_tx\",\n",
" \"corr_flow_fund_lag3\",\n",
" \"corr_flow_fund_lag6\",\n",
" \"corr_flow_rate_lag3\",\n",
"]\n",
"profile_vars_top400 = [c for c in profile_vars_top400 if c in dfc_top400.columns]\n",
"\n",
"for k in [2, 5]:\n",
" prof = plot_heatmap(\n",
" dfc_top400, profile_vars_top400, f\"cluster_k{k}\",\n",
" title=f\"Cluster Signatures — Top 400 Accounts (K={k}, robust z-score)\",\n",
" figsize=(16, 4)\n",
" )\n",
" print(f\"\\n=== Cluster medians — K={k} ===\")\n",
" print(prof.round(3).to_string())\n",
"\n",
"# ── 6h. 2D behavioral segmentation ───────────────────────────────────────\n",
"thr_int = dfc_top400[\"gross_flow_to_aum\"].median()\n",
"thr_freq = dfc_top400[\"flow_freq\"].median()\n",
"\n",
"def quadrant(row):\n",
" low_int = row[\"gross_flow_to_aum\"] < thr_int\n",
" low_frq = row[\"flow_freq\"] < thr_freq\n",
" if low_int and low_frq: return \"Dormant\"\n",
" if low_int and not low_frq: return \"Small rebalancers\"\n",
" if not low_int and low_frq: return \"Occasional large movers\"\n",
" return \"Highly active\"\n",
"\n",
"dfc_top400[\"seg_2D\"] = dfc_top400.apply(quadrant, axis=1)\n",
"print(dfc_top400[\"seg_2D\"].value_counts())\n",
"\n",
"plt.figure(figsize=(8, 5))\n",
"for name, g in dfc_top400.groupby(\"seg_2D\"):\n",
" plt.scatter(g[\"flow_freq\"], g[\"gross_flow_to_aum\"], s=15, label=name)\n",
"plt.yscale(\"log\")\n",
"plt.axvline(thr_freq, linestyle=\"--\", color=\"gray\")\n",
"plt.axhline(thr_int, linestyle=\"--\", color=\"gray\")\n",
"plt.xlabel(\"Activity frequency (share of active months)\")\n",
"plt.ylabel(\"Gross flow / mean AUM [log scale]\")\n",
"plt.title(\"2D Behavioral Segmentation — Top 400 Accounts\")\n",
"plt.legend(markerscale=2)\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"# ── 6i. Churn analysis ────────────────────────────────────────────────────\n",
"dfc_top400[\"churn_hard\"] = (dfc_top400[\"aum_final_to_peak\"] < 0.10).astype(int)\n",
"dfc_top400[\"churn_soft\"] = (\n",
" (dfc_top400[\"aum_final_to_peak\"] < 0.40) &\n",
" (dfc_top400[\"aum_drawdown_last\"] > 0.40)\n",
").astype(int)\n",
"dfc_top400[\"churn_warning\"] = (\n",
" (dfc_top400[\"flow_direction_balance\"] < 0) &\n",
" (dfc_top400[\"aum_drawdown_last\"] > 0.20)\n",
").astype(int)\n",
"\n",
"print(\"\\n=== Overall churn rates ===\")\n",
"print(dfc_top400[[\"churn_hard\", \"churn_soft\", \"churn_warning\"]].mean().round(3))\n",
"\n",
"for k in [2, 5]:\n",
" churn_profile = (\n",
" dfc_top400.groupby(f\"cluster_k{k}\")\n",
" .agg(\n",
" n_accounts = (ID_COL, \"count\"),\n",
" churn_hard = (\"churn_hard\", \"mean\"),\n",
" churn_soft = (\"churn_soft\", \"mean\"),\n",
" churn_warning = (\"churn_warning\", \"mean\"),\n",
" )\n",
" )\n",
" print(f\"\\n=== Churn rates by cluster — K={k} ===\")\n",
" print(churn_profile.round(3).to_string())\n",
"\n",
" churn_profile[[\"churn_hard\", \"churn_soft\", \"churn_warning\"]].plot(\n",
" kind=\"bar\", figsize=(8, 4),\n",
" color=[\"#d62728\", \"#ff7f0e\", \"#ffbb78\"]\n",
" )\n",
" plt.title(f\"Churn Rates by Cluster — Top 400 Accounts (K={k})\")\n",
" plt.ylabel(\"Rate\")\n",
" plt.xlabel(\"Cluster\")\n",
" plt.xticks(rotation=0)\n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
"# ── 6j. Full cluster profile table ────────────────────────────────────────\n",
"for k in [2, 5]:\n",
" print(f\"\\n===== Cluster profile — K={k} =====\")\n",
" prof = (\n",
" dfc_top400.groupby(f\"cluster_k{k}\")\n",
" .agg(\n",
" n_accounts = (ID_COL, \"count\"),\n",
" aum_mean_med = (\"aum_qty_mean\", \"median\"),\n",
" freq_med = (\"flow_freq\", \"median\"),\n",
" gross_flow_to_aum_med = (\"gross_flow_to_aum\", \"median\"),\n",
" n_tx_med = (\"n_tx_total\", \"median\"),\n",
" holding_med = (\"avg_holding_months_per_isin\",\"median\"),\n",
" exit_rate_med = (\"exit_rate_per_isin\", \"median\"),\n",
" flow_dir_med = (\"flow_direction_balance\", \"median\"),\n",
" drawdown_med = (\"aum_drawdown_last\", \"median\"),\n",
" months_inactive_med = (\"months_since_last_tx\", \"median\"),\n",
" corr_fund_lag3_med = (\"corr_flow_fund_lag3\", \"median\"),\n",
" corr_rate_lag3_med = (\"corr_flow_rate_lag3\", \"median\"),\n",
" )\n",
" .sort_values(\"n_accounts\", ascending=False)\n",
" )\n",
" print(prof.round(3).to_string())\n",
"\n",
"# ── 6k. Inter-cluster distance matrix ─────────────────────────────────────\n",
"def plot_distance_matrix(X_scaled, labels, max_points=400, title=\"Distance matrix\"):\n",
" n = X_scaled.shape[0]\n",
" idx = np.arange(n)\n",
" if n > max_points:\n",
" idx = np.random.default_rng(42).choice(idx, size=max_points, replace=False)\n",
" X_sub = X_scaled[idx]\n",
" labels_sub = np.asarray(labels)[idx]\n",
" order = np.lexsort((np.arange(len(labels_sub)), labels_sub))\n",
" X_sub = X_sub[order]\n",
" labels_sub = labels_sub[order]\n",
" D = pairwise_distances(X_sub)\n",
" plt.figure(figsize=(8, 7))\n",
" sns.heatmap(D, cmap=\"viridis\")\n",
" for b in np.cumsum(np.unique(labels_sub, return_counts=True)[1])[:-1]:\n",
" plt.axhline(b, color=\"red\", linewidth=2)\n",
" plt.axvline(b, color=\"red\", linewidth=2)\n",
" plt.title(title)\n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
"plot_distance_matrix(\n",
" X_top400_scaled,\n",
" dfc_top400[\"cluster_k5\"].values,\n",
" title=\"Inter-cluster Distance Matrix — Top 400 Accounts (K=5)\"\n",
")"
]
},
{
"cell_type": "code",
2026-04-10 11:05:13 +02:00
"execution_count": 61,
2026-04-07 20:26:19 +02:00
"id": "b394752d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Selected accounts (AUM > €5M): 431\n"
]
}
],
"source": [
"ref_date = pd.Timestamp(\"2025-10-01\") # first day of month (panel convention)\n",
"df_ref = df_aum[df_aum[\"month\"] == ref_date]\n",
"\n",
"aum_account = (\n",
" df_ref.groupby(ID_COL)[AUM_VAL_COL].sum()\n",
" .reset_index().sort_values(AUM_VAL_COL, ascending=False)\n",
")\n",
"aum_account = aum_account[aum_account[AUM_VAL_COL] > 5_000_000]\n",
"selected_accounts = aum_account[ID_COL]\n",
"print(f\"Selected accounts (AUM > €5M): {len(selected_accounts)}\")"
]
},
{
"cell_type": "markdown",
"id": "078c2442",
"metadata": {},
"source": [
"---\n",
"### 6h. Visualization — Top 400 Accounts\n",
"\n",
"The 2D intensity-frequency space provides an intuitive view of behavioral profiles. The churn analysis links clusters to concrete retention risk signals.\n"
]
},
{
"cell_type": "code",
2026-04-10 11:05:13 +02:00
"execution_count": 62,
2026-04-07 20:26:19 +02:00
"id": "715c7165",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"seg_2D\n",
"Highly active (high int, high freq) 137\n",
"Dormant (low int, low freq) 136\n",
"Small rebalancers (low int, high freq) 77\n",
"Occasional large movers (high int, low freq) 77\n",
"Name: count, dtype: int64\n",
"thr_int: 4.0037 | thr_freq: 0.7231\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA3kAAAHqCAYAAAC5nYcRAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAA63lJREFUeJzs3Xd8E/X/B/BXmrbQMgod7D067GbvMpW9h8qQJUMcgCggKvIVLCqiIigqUwREQKYMAWUoyLK0jDJklSJgKWW20DbJ74/+cjbNvOSSXJLX8/HgQXO53L1z97nLve/zuc9HodFoNCAiIiIiIiK34OXsAIiIiIiIiEg6TPKIiIiIiIjcCJM8IiIiIiIiN8Ikj4iIiIiIyI0wySMiIiIiInIjTPKIiIiIiIjcCJM8IiIiIiIiN8Ikj4iIiIiIyI0wySMiIiIiInIjTPKIyKOlp6cjLCwMixcvdvi6v/jiC4SFhTl8vVqDBw/G4MGDnbZ+0ufsMkFERO7B29kBEJFrSklJwcaNG3H48GFcv34dZcqUQWxsLMaPH4+aNWvqzDt48GAcOXIEAKBQKODv74+QkBDExMSgZ8+eaN68uUXrnDJlCjZs2CC8ViqVCAkJQb169TBu3DjUqVNHui9IgtzcXKxevRobNmxAWloavLy8UL58edSrVw9Dhw5F7dq1nR2iQ23ZsgWZmZkYOnSoVZ/PycnBokWL0KhRIzRu3Fja4GSk8HFvyssvv4xXXnnFAREVuH//Pp555hncuXMHn3/+OTp27Kjzfm5uLj7//HNs2rQJ9+/fR1hYGMaPH2/wPPXXX3/h448/xpkzZ1CyZEl06tQJEyZMQIkSJSyO5+LFi+jcuTN8fX3xxx9/oHTp0jZ/Rzmy9bghInGY5BGRVRYtWoS//voLHTt2RFhYGDIyMrBy5Ur07t0ba9asQWhoqM78FSpUwMSJEwEUXORevXoVu3btwubNm9GpUyd8/PHH8PHxMbteX19fzJw5EwCgUqmQlpaGH374AQcOHMDPP/+M8uXLS/9l7WTs2LEYNWqUs8Mw69VXX8X+/fvRpUsX9OvXD/n5+bh06RL27t2L+Ph4j0vytm7digsXLtiU5M2fPx8vv/yyXpLnKmXCEmPGjEHfvn2F1ydPnsSKFSswZswY1KpVS5ju6JrLefPm4fHjx0bfnzJlCnbu3IkhQ4agRo0a2LBhA0aNGoXly5ejQYMGwnypqanCTY4pU6bg5s2bWLJkCa5cuYJFixZZHM/mzZsREhKCe/fuYefOnejXr59N30+ubD1uiEgcJnlEZJWhQ4dizpw58PX1FaZ17twZ3bp1wzfffIM5c+bozF+qVCn06NFDZ9qkSZMwc+ZMrFq1CpUrV8Ybb7xhdr3e3t56y4mLi8Po0aOxb98+9O/f34Zv5Vje3t7w9pbmNKzRaPDkyRMUL15ckuVppaSk4LfffsOECRMwZswYnfdUKhXu378v6fo8nZRlwtmK1nwVK1YMK1asQLNmzZxWg3n+/HmsXr0aL730EubNm6f3fkpKCn7++We8+eabGDFiBACgZ8+e6Nq1K+bMmYMffvhBmHfu3LkoXbo0VqxYgZIlSwIAqlSpgrfffhu///47WrRoYTYejUaDLVu2oGvXrkhPT8fmzZvdNskjIsfiM3lEZJV69erpJHgAUKNGDdStWxeXLl2yaBlKpRJvv/026tSpg5UrV+LBgwdWxRIcHCwsr7D79+9j1qxZSEhIQFRUFDp06IBvvvkGarXa4HLWrFmD9u3bIyoqCn369EFKSorO+2fPnsWUKVPQrl07REdHo3nz5pg6dSqysrKEeXbs2IGwsDCDzdR++OEHhIWF4fz58wAMP3+Vn5+PBQsWCHG0bdsWc+fORW5urs58bdu2xejRo3HgwAH07t0bMTExwgXo+vXrMWTIEDRt2hRRUVHo3LkzVq1aZcmm1HPt2jUABfu7KKVSibJly+pMu3XrFqZOnYpmzZohKioKXbp0wbp16/Q+e/36dYwZMwZxcXFo2rQpPvjgAxw4cABhYWE4fPiwMN/gwYPRtWtXnD17FoMGDUJsbCw6dOiAHTt2AACOHDmCfv36ISYmBs888wwOHjyoty5LYjp8+DDCwsKwbds2fPXVV2jVqhWio6Pxwgsv4OrVqzrx7N27F9evX0dYWBjCwsLQtm1bAP818+vduzfq16+PuLg4PP/88/jzzz+Fz6enp6Np06YAgPnz5wvL+OKLLwBIUyaOHTuGvn37Ijo6Gu3atcPGjRv1tomcrFy5El26dEFUVBRatGiBGTNm6N080JaDU6dO4dlnn0VMTAzatm2L1atXi1rXrFmz0L59e50aucJ27NgBpVKJAQMGCNOKFSuGvn37IikpCTdu3AAAPHz4EAcPHkT37t2FBA8AevToAX9/f2zfvt2ieI4fP47r16+jc+fO6Ny5M44dO4abN2/qzadWq7F8+XJ069YN0dHRaNKkCUaMGIGTJ0/qzLdp0yb07dsXsbGxaNiwIQYOHIjff/9dZx5Ltnfbtm0xZcoUvTiKPscrxXEDACtWrECXLl2EuHv37o0tW7ZYtA2JyDD3uF1IRLKg0Whw+/Zt1K1b1+LPKJVKdOnSBZ9//jmOHz+O1q1bm/3MnTt3ABRc+Fy7dg1z5sxBmTJl0KZNG2GenJwcDBo0CLdu3cKzzz6LihUrIikpCXPnzkVGRgamTZums8ytW7fi0aNHGDBgABQKBRYtWoRXXnkFu3fvFpqRHjx4ENeuXUPv3r0REhKCCxcu4Mcff8Tff/+NH3/8EQqFAq1btxYu8ho1aqSzjm3btqFu3bp6TVkLe/vtt7FhwwY888wzGDZsGFJSUvD111/j4sWLWLBggc68ly9fxuuvv44BAwagf//+wrOQq1evRt26ddG2bVt4e3vjt99+w4wZM6DRaDBw4ECz27ewSpUqASh4nqZevXoma5lu376N/v37Q6FQYODAgQgMDMT+/fsxbdo0PHz4UGimlZ2djRdeeAEZGRkYMmQIgoODsXXrVp3krrB79+5hzJgx6Ny5Mzp27IjVq1dj4sSJUKvV+OCDD/Dss8+ia9euWLx4MV599VXs3btXuPC2NCatb7/9FgqFAsOHD8fDhw+xaNEiTJo0CWvXrgVQ0ATxwYMHuHnzJqZOnQoAwvNXDx8+xNq1a9G1a1f069cPjx49wrp16zBy5EisXbsWERERCAwMxHvvvYf33nsPHTp0QIcOHQCYbrIopkxcvXoVr732Gvr27YtevXph/fr1mDJlCiIjI0Udl47yxRdfYP78+WjWrBmee+45XL58GatXr8bJkyexevVqnSbc9+7dw6hRo9CpUyd06dIF27dvx3vvvQcfHx+dZqHGbN++HUlJSdi2bRuuX79ucJ7U1FTUqFFDJ3EDgJiYGOH9ihUr4ty5c8jPz0dUVJTOfL6+voiIiEBqaqpF33/Lli2oVq0aYmJiEBoaiuLFi2Pr1q0YOXKkznzTpk3DTz/9hFatWqFv375QqVQ4duwYkpOTER0dDaDgpsEXX3yB+Ph4vPrqq/Dx8UFycjL+/PNPoVZRzPYWw5bj5scff8TMmTPxzDPPYMiQIXjy5AnOnTuH5ORkdOvWzap4iAiAhohIIhs3btSEhoZq1q5dqzN90KBBmi5duhj93K5duzShoaGa5cuXm1z+5MmTNaGhoXr/WrZsqTl16pTOvAsWLNDExcVpLl++rDN9zpw5moiICM0///yj0Wg0mmvXrmlCQ0M1jRo10ty9e1eYb/fu3ZrQ0FDNr7/+KkzLycnRi2nr1q2a0NBQzdGjR4VpEydO1DRt2lSTn58vTPv333814eHhmvnz5wvT5s2bpwkNDRVep6amakJDQzXTpk3TWcfs2bM1oaGhmkOHDgnT2rRpowkNDdXs379fLyZDcQ4fPlzTrl07nWmDBg3SDBo0SG/ewtRqtWbQoEGa0NBQTbNmzTQTJ07UfP/995rr16/rzfvWW29pmjdvrrlz547O9AkTJmjq168vxLVkyRJNaGioZteuXcI8jx8/1nTs2FETGhqq+fPPP3ViDA0N1WzZskWYdvHiRU1
"text/plain": [
"<Figure size 900x500 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 900x500 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1400x500 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1600x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== Medians K=5 (Top400) ===\n",
" log_aum_qty_mean gross_flow_to_aum flow_freq n_tx_total n_isin_total avg_holding_months_per_isin exit_rate_per_isin flow_direction_balance aum_drawdown_last aum_final_to_peak months_since_last_tx corr_flow_fund_lag3 corr_flow_fund_lag6 corr_flow_rate_lag3\n",
"cluster_k5 \n",
"0 10.975 1.488 0.557 819.0 25.0 52.905 0.567 -0.487 1.000 0.000 0.0 0.002 0.016 -0.024\n",
"1 11.174 1.389 0.043 4.0 2.0 42.429 0.250 0.557 0.303 0.697 19.0 -0.000 -0.007 -0.012\n",
"2 10.357 4.383 0.372 90.5 12.5 32.149 0.434 0.287 0.077 0.923 1.0 0.042 0.025 -0.034\n",
"3 11.045 5.471 0.777 1448.0 24.0 40.857 0.688 0.245 1.000 0.000 0.0 0.009 -0.008 0.005\n",
"4 11.994 5.155 0.926 4935.5 47.5 57.100 0.620 0.037 1.000 0.000 0.0 0.158 0.130 -0.140\n",
"\n",
"=== Overall churn rates ===\n",
"churn_hard 0.684\n",
"churn_soft 0.775\n",
"churn_warning 0.321\n",
"dtype: float64\n",
"\n",
"=== Churn per cluster K=2 ===\n",
" n_clients churn_hard churn_soft churn_warning\n",
"cluster_k2 \n",
"0 325 0.883 0.969 0.385\n",
"1 102 0.049 0.157 0.118\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 800x400 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== Churn per cluster K=5 ===\n",
" n_clients churn_hard churn_soft churn_warning\n",
"cluster_k5 \n",
"0 67 0.612 0.925 0.955\n",
"1 37 0.108 0.297 0.108\n",
"2 62 0.000 0.032 0.048\n",
"3 137 0.964 0.978 0.117\n",
"4 124 0.927 0.984 0.403\n"
]
},
{
"data": {
"image/png": "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idTYu3evvv32W3Xt2lX58+dX8eLFdfPmTUnS2LFjlSdPHr399tu6cOGCFi1apDFjxmjatGkPbTcuLs72o3TYsGHy8PCQJG3ZskXR0dHq0qWL8uXLp0OHDmnp0qX6999/bXtkO3XqpCtXriQ6nEWSRowYoXXr1ql9+/bq3r27zp8/r5CQEP35559avny53NzcFB4erj59+ih//vx64403lCdPHp0/f17fffddirbL6dOnNXjwYHXu3Fnt2rXTmjVr9O6772r+/Pl69tlnba/dhg0b1LNnT7tlN27cqFy5cqlJkyZJtm82m9W/f3+FhoaqZcuW6tGjh+7cuaPdu3fr77//VsmSJVNUZ1KOHz+u/v37y2QyadCgQXJ3d9eZM2f022+/Sbr3I3bQoEEKCgpSp06d9Mwzz0iSrT+GhoaqX79+8vX11dtvvy0XFxetXbtWr7/+upYtW2b3g1iS3n33XZUqVUqDBw9O8GPd2axWqwYMGGALUxUrVtSuXbs0ceJEXb58Wf/73//s5t+/f782b96s7t27y93dXcuXL1ffvn21evVqGY3Gh67v888/V6FChdS5c2d98cUXCR63WCw6duyYOnTokOAxPz8//fzzz7p9+7a8vb31559/SpJ8fX3t5qtcubJcXV119OjRZPdmh4WF6eDBg2rZsqWKFi2qCxcuaPny5erRo4e++eYbeXp6Sro3NKtr1646ceKEOnTooEqVKun69evasWOHLl++rAIFCqSoz6Z2W6fGe++9pyeffFLvv/++/vzzT61evVoFChTQhx9+KOnekLiPPvpIVapU0auvvipJtvfRyJEjtXXrVnXr1k3lypXTjRs3dODAAZ04cUKVK1dOcp0HDx6UdO+oQVIWLVqkCRMmqFWrVpowYUKioSIlO2QkydvbW+7u7nbTZsyYoYkTJ8rFxUWVK1fW4MGDVb9+/QTLxveR3377Ldl6AaewAsgwt27dshqNRuuAAQNSvIzRaLRWrlzZeubMGdu0o0ePWo1Go3XJkiW2acOGDbM+//zzCZYPCgqyGo3GBG1WqFDBevz4cbvpa9assRqNRmvPnj2tFovFNn38+PHWihUrWm/evJlsrcOGDbMajUbr5MmTEzwWFRWVYNqcOXOsJpPJeuHCBdu00aNHJ6jXarVa9+/fbzUajdYNGzbYTf/pp5/spn/33XdWo9FoPXToULK1Jub555+3Go1G69atW23Tbt26ZX322Wetbdu2tU1bsWKF1Wg0Wv/55x/btJiYGGvt2rWtw4YNS3YdX331ldVoNFqDg4MTPHb/NjcajdagoCDb3yl9fYODg61Go9EaHh6eZA2HDh2yGo1G65o1axKsv1mzZtbevXvb1RIVFWVt3LixtVevXgnW+/777yf7fDPTg30nvi988cUXdvO98847VpPJZPeeMhqNVqPRaA0LC7NNu3DhgtXPz886cODAh6776NGj1ooVK1p37dpltVr/b/vc/zqEh4dbjUajdcaMGQmWX7p0qdVoNFpPnDhhey4VK1ZMdF116tSxDh48ONl6Enu/HTx40Go0Gq3r1q2zTfv888+tRqPRum3btgTzx/eBlPTZlG7rc+fOJdr3rNaEfT5+GwYEBNjNN3DgQGutWrXsplWtWjXR994zzzxjHT16dILpDzN16lSr0Wi03r59O9E
"text/plain": [
"<Figure size 800x400 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAuEAAAKyCAYAAAB7WgDLAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsvXecZUWZPv7UyTffvp17untyACY4IAxJUIKKoCxZlMEALKyk1d0V+MpPQREUWZRkZAUWwQSigCQFUYkSlDBMYlJP93TuvvneE+v3R9Wpus3MwIAjDO55Ph8+9Nx77jl16tSpeut9n/d5CaWUIkKECBEiRIgQIUKECG8blHe6AREiRIgQIUKECBEi/F9DZIRHiBAhQoQIESJEiPA2IzLCI0SIECFChAgRIkR4mxEZ4REiRIgQIUKECBEivM2IjPAIESJEiBAhQoQIEd5mREZ4hAgRIkSIECFChAhvMyIjPEKECBEiRIgQIUKEtxmRER4hQoQIESJEiBAhwtuMyAiPECFChAgRIkSIEOFtRmSER9hl8Ktf/Qrz589Hf3//O92UdxTLly/H8uXL3+lmRNgGomcTIQLw4osvYuHChRgYGHinm7JNXHXVVTjhhBPe6WZEiPCGiIzwdylCg/Wll15607+t1Wq47rrr8PTTT/8DWrbr4o9//COuu+66t/WaF154IebPny/+W7p0KQ499FCcd955ePDBBxEEwU65zvPPP4/rrrsOxWJxp5zvnUbYb3vuuSfq9fpW32/cuFH06f/8z/+86fMPDw/juuuuw8qVK3dGc3cZLF++fMp4295/b/d7UCwWsd9++2H+/Pl44IEHtvrecRx861vfwoEHHojFixfjhBNOwOOPP77Ncz3//PM4+eSTsWTJEhxwwAG47LLLUKlU3lR71q1bh/nz52PRokX/NO/MtnDPPffg5ptv3unn/fa3v40jjzwS06ZNE58tX74cRx111FbHPvnkk1iyZAmOOeYY5PP5v+u6hxxyyDbH85e//OUpx33qU5/CqlWr8PDDD/9d14sQ4R8N7Z1uQIS3H7VaDddffz3OOeccLFu27J1uztuGP/7xj7jttttw7rnnvq3XNQwDl112GQDAtm0MDAzgD3/4A8477zzss88++N73vodkMimOfytG5V//+ldcf/31OOaYY5BOp3da299JaJqGer2ORx55BB/5yEemfHfPPffANE3Ytv2Wzj0yMoLrr78e06ZNw2677bbDv3srz+btxFlnnYXjjz9e/Pull17CrbfeirPOOguzZs0Sn8+fP/9tbde11167zc1UiAsvvBAPPvggTj31VMyYMQN33XUX/vVf/xW33HIL3vve94rjVq5ciU9/+tOYPXs2LrzwQgwNDeHHP/4xNm7ciBtvvHGH23P33XejtbUVhUIBDz744D+t1/Tee+/F2rVr8elPf3qnnXPlypV44okn8LOf/ewNj33yySdx1llnYebMmbjpppuQzWb/7uvvtttu+MxnPjPls5kzZ075d2trKw499FD8+Mc/xqGHHvp3XzNChH8UIiM8wk5DtVpFPB5/p5vxtoJSCtu2YVnWdo/RNA1HH330lM8+//nP44c//CH++7//GxdffDG+853viO8Mw/hHNfddBcMwsOeee+K3v/3tVkb4vffei/e///148MEH35a21Go1xGKxXf7ZHHDAAVP+bZombr31Vuy///7v2IZ7zZo1+OlPf4rPfe5zuPbaa7f6/sUXX8Rvf/tbfPGLX8Rpp50GAPiXf/kXHHXUUbjqqqumGHtXX3010uk0br31VrFx7e7uxsUXX4zHHnsMBx544Bu2h1KKe+65B0cddRT6+/tx9913/9Ma4f8I3Hnnnejq6sJ73vOe1z3uL3/5C/7t3/4NM2bM2GkGOAC0t7dvNZ9uC0cccQTOP/98bN68GT09PTvl2hEi7GxEdJR/Ilx44YVYunQphoeH8bnPfQ5Lly7Fvvvui29+85vwfR8A0N/fj/322w8AcP31128zPL1u3TrhpV20aBGOPfbYrcJ6IR3mL3/5Cy655BLst99+OPjgg1+3fevWrcP555+PfffdF4sXL8aHPvQhfPvb337d32wvdH7IIYfgwgsvFP92XRfXX389PvjBD2LRokVYtmwZTj75ZBHSvvDCC3HbbbeJc4b/hQiCADfffDOOPPJILFq0CPvvvz++/OUvo1AobHXdM888E3/+859x7LHHYvHixTvkEdoW/vVf/xUHHnggHnjgAWzYsEF8vi3e8a233oojjzwSS5Yswd57741jjz0W99xzDwDguuuuw5VXXgkAOPTQQ8W9hdz6O++8E6eeeir2228/LFy4EB/5yEdw++23b7NPzzzzTDz77LM4/vjjsWjRIhx66KH49a9/vdWxxWIRl19+OQ455BAsXLgQBx10EL74xS9iYmJCHOM4Dq699locfvjhWLhwIQ4++GBceeWVcBxnh/voqKOOwp/+9KcplIEXX3wRGzdu3GboO5/P45vf/CY++tGPYunSpdhzzz1x+umnY9WqVeKYp59+WniLL7roItFfv/rVrwDIsPrLL7+MT37yk1iyZAmuvvpq8V3js7nggguwaNEirFu3bko7TjvtNOy9994YHh7e4Xt9O3HbbbfhyCOPxMKFC3HggQfi0ksv3YqW0dgPH//4x7F48WIccsgh+OlPf/qmrvX1r38dhx122BSPdiMeeOABqKqKk046SXxmmiaOP/54/PWvf8Xg4CAAoFwu44knnsDHPvaxKZGjo48+GvF4HPfff/8Otee5557DwMAAPvKRj+AjH/kInn32WQwNDW11XBAEuOWWW/DRj34UixYtwr777ovTTjttKwrgb37zGxx//PHi3fzkJz+Jxx57bMoxO9Lfr53TQrx2zD399NOYP38+7rvvPnzve9/DQQcdhEWLFuFTn/oUNm3aNOV3jz76KAYGBsQYP+SQQ8T3rzenvB4efvhh7LvvviCEbPeYZ599FmeeeSZ6e3tx0003oamp6Q3P+2bgOA6q1errHrP//vuL9kaIsKsi8oT/k8H3fZx22mlYvHgxvvjFL+LJJ5/Ej3/8Y/T09OATn/gEcrkcLrnkElxyySU4/PDDcfjhhwOQ4em1a9fi5JNPRnt7O8444wyxuJ199tm47rrrxPEhLr30UuRyOZx99tmvOymuWrUKn/zkJ6FpGk466SRMmzYNfX19eOSRR/D5z3/+777v66+/Hj/4wQ9wwgknYPHixSiXy3j55ZexYsUKHHDAATjppJMwMjKCxx9/XBisjfjyl7+Mu+66C8ceeyyWL1+O/v5+3HbbbXjllVfw05/+FLqui2M3bNiA//iP/8BJJ52EE088catQ6JvBxz72MTz22GN44okntnueX/ziF7jsssvwoQ99CKeeeips28bq1avxwgsv4KMf/SgOP/xwbNy4Effeey8uuugiseDlcjkAwE9/+lPMnTsXhxxyCDRNwx/+8AdceumloJTik5/85JRrbdq0Ceeffz6OP/54HHPMMbjzzjtx4YUXYo899sDcuXMBAJVKBZ/85Cexbt06HHfccdh9990xOTmJRx55BMPDw8jlcgiCAP/2b/+G5557DieeeCJmz56NNWvW4JZbbsHGjRvx3e9+d4f65/DDD8dXvvIVPPTQQ8JwvvfeezFr1izsvvvuWx2/efNm/P73v8eHP/xhdHd3Y2xsDD//+c9xyimn4Le//S3a29sxe/ZsnHfeebj22mtx0kknYa+99gIA7LnnnuI8+XweZ5xxBo488kh87GMfQ3Nz8zbb96UvfQlPPfUULrjgAvz85z+Hqqr42c9+hsceewxXXnkl2tvbd+g+305cd911uP7667H//vvj5JNPxoYNG/DTn/4UL7300lZjvVAo4F//9V9xxBFH4Mgjj8T999+PSy65BLquT6G9bA/3338//vrXv+K+++7bbhLfypUrMWPGjCmGNQAsXrxYfN/Z2YnVq1fD8zwsXLhwynGGYWC33XbbYX7/Pffcg97eXixevBjz5s2DZVm49957cfrpp0857ktf+hJ+9atf4aCDDsLxxx8P3/fx7LP
"text/plain": [
"<Figure size 800x700 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"===== K=2 =====\n",
" n_clients aum_qty_mean_med freq_med gross_flow_to_aum_med n_tx_med holding_med exit_rate_med flow_dir_med drawdown_med months_inactive_med corr_fund_lag3_med corr_rate_lag3_med\n",
"cluster_k2 \n",
"0 325 91586.099 0.786 4.452 1872.0 48.347 0.637 0.073 1.00 0.0 0.054 -0.041\n",
"1 102 47913.297 0.154 2.652 24.0 35.523 0.381 0.347 0.17 2.0 0.024 -0.025\n",
"\n",
"===== K=5 =====\n",
" n_clients aum_qty_mean_med freq_med gross_flow_to_aum_med n_tx_med holding_med exit_rate_med flow_dir_med drawdown_med months_inactive_med corr_fund_lag3_med corr_rate_lag3_med\n",
"cluster_k5 \n",
"3 137 62616.679 0.777 5.471 1448.0 40.857 0.688 0.245 1.000 0.0 0.009 0.005\n",
"4 124 161746.356 0.926 5.155 4935.5 57.100 0.620 0.037 1.000 0.0 0.158 -0.140\n",
"0 67 58391.143 0.557 1.488 819.0 52.905 0.567 -0.487 1.000 0.0 0.002 -0.024\n",
"2 62 31466.909 0.372 4.383 90.5 32.149 0.434 0.287 0.077 1.0 0.042 -0.034\n",
"1 37 71234.484 0.043 1.389 4.0 42.429 0.250 0.557 0.303 19.0 -0.000 -0.012\n"
]
}
],
"source": [
"# 1. 2D behavioral segmentation: relative intensity vs activity frequency\n",
"thr_int = dfc_top400[\"gross_flow_to_aum\"].median()\n",
"thr_freq = dfc_top400[\"flow_freq\"].median()\n",
"\n",
"def quadrant(row):\n",
" low_int = row[\"gross_flow_to_aum\"] < thr_int\n",
" low_frq = row[\"flow_freq\"] < thr_freq\n",
" if low_int and low_frq: return \"Dormant (low int, low freq)\"\n",
" if low_int and not low_frq: return \"Small rebalancers (low int, high freq)\"\n",
" if not low_int and low_frq: return \"Occasional large movers (high int, low freq)\"\n",
" return \"Highly active (high int, high freq)\"\n",
"\n",
"dfc_top400[\"seg_2D\"] = dfc_top400.apply(quadrant, axis=1)\n",
"print(dfc_top400[\"seg_2D\"].value_counts())\n",
"print(f\"thr_int: {thr_int:.4f} | thr_freq: {thr_freq:.4f}\")\n",
"\n",
"plt.figure(figsize=(9, 5))\n",
"for name, g in dfc_top400.groupby(\"seg_2D\"):\n",
" plt.scatter(g[\"flow_freq\"], g[\"gross_flow_to_aum\"], s=10, label=name)\n",
"plt.yscale(\"log\")\n",
"plt.axvline(thr_freq, linestyle=\"--\", color=\"gray\")\n",
"plt.axhline(thr_int, linestyle=\"--\", color=\"gray\")\n",
"plt.xlabel(\"Activity frequency (share of active months)\")\n",
"plt.ylabel(\"Gross flow / mean AUM [log scale]\")\n",
"plt.title(\"2D Behavioral Segmentation — Top 400 Accounts\")\n",
"plt.legend(markerscale=2)\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"# 2. K=5 clusters in the 2D intensity-frequency space\n",
"labels_map = {\n",
" 0: \"Cluster 0: Large & highly active movers\",\n",
" 1: \"Cluster 1: Occasional large movers\",\n",
" 3: \"Cluster 3: Dormant profiles\",\n",
" 4: \"Cluster 4: Loyal clients\"\n",
"}\n",
"\n",
"plt.figure(figsize=(9, 5))\n",
"for name, g in dfc_top400[~dfc_top400[\"cluster_k5\"].isin([2])].groupby(\"cluster_k5\"):\n",
" plt.scatter(\n",
" g[\"flow_freq\"], g[\"gross_flow_to_aum\"],\n",
" s=10, label=labels_map.get(int(name), f\"Cluster {int(name)}\")\n",
" )\n",
"plt.yscale(\"log\")\n",
"plt.axvline(thr_freq, linestyle=\"--\", color=\"gray\")\n",
"plt.axhline(thr_int, linestyle=\"--\", color=\"gray\")\n",
"plt.xlabel(\"Activity frequency (share of active months)\")\n",
"plt.ylabel(\"Gross flow / mean AUM [log scale]\")\n",
"plt.title(\"K=5 Clusters — Intensity / Frequency Space (excluding extreme outlier C2)\")\n",
"plt.ylim(0.1, 1000)\n",
"plt.legend(markerscale=2)\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"# 3. Dual view: trading intensity and churn/loyalty dimensions\n",
"if \"log_n_tx_total\" not in dfc_top400.columns:\n",
" dfc_top400[\"log_n_tx_total\"] = np.log1p(dfc_top400[\"n_tx_total\"].clip(lower=0))\n",
"\n",
"thr_log_tx = dfc_top400[\"log_n_tx_total\"].median()\n",
"thr_churn = dfc_top400[\"aum_drawdown_last\"].median()\n",
"thr_hold = dfc_top400[\"avg_holding_months_per_isin\"].median()\n",
"\n",
"color_map = {1: \"#ff7f0e\", 4: \"red\"}\n",
"\n",
"fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n",
"\n",
"# Graphique 1 : log_n_tx_total vs gross_flow_to_aum\n",
"for name, g in dfc_top400[~dfc_top400[\"cluster_k5\"].isin([2, 4])].groupby(\"cluster_k5\"):\n",
" axes[0].scatter(\n",
" g[\"log_n_tx_total\"], g[\"gross_flow_to_aum\"],\n",
" s=10, label=labels_map.get(int(name), f\"Cluster {int(name)}\")\n",
" )\n",
"axes[0].set_yscale(\"log\")\n",
"axes[0].axvline(thr_log_tx, linestyle=\"--\", color=\"gray\")\n",
"axes[0].axhline(thr_int, linestyle=\"--\", color=\"gray\")\n",
"axes[0].set_xlabel(\"Activity frequency (log n_tx_total)\")\n",
"axes[0].set_ylabel(\"Gross flow / mean AUM\")\n",
"axes[0].set_title(\"Trading intensity vs. frequency (log transactions)\")\n",
"axes[0].set_ylim(0.1, 1000)\n",
"axes[0].legend(markerscale=2)\n",
"\n",
"# Graphique 2 : avg_holding_months_per_isin vs aum_drawdown_last\n",
"for name, g in dfc_top400[~dfc_top400[\"cluster_k5\"].isin([0, 2, 3])].groupby(\"cluster_k5\"):\n",
" axes[1].scatter(\n",
" g[\"avg_holding_months_per_isin\"], g[\"aum_drawdown_last\"],\n",
" s=10,\n",
" color=color_map.get(int(name), \"gray\"),\n",
" label=labels_map.get(int(name), f\"Cluster {int(name)}\")\n",
" )\n",
"axes[1].set_yscale(\"log\")\n",
"axes[1].axvline(thr_hold, linestyle=\"--\", color=\"gray\")\n",
"axes[1].axhline(thr_churn, linestyle=\"--\", color=\"gray\")\n",
"axes[1].set_xlabel(\"avg_holding_months_per_isin\")\n",
"axes[1].set_ylabel(\"aum_drawdown_last\")\n",
"axes[1].set_title(\"Churn risk vs. loyalty (clusters 1 and 4)\")\n",
"axes[1].set_ylim(0.001, 1.3)\n",
"axes[1].legend(markerscale=2)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"# 4. Cluster signature heatmap — K=5\n",
"prof_louis_k5 = plot_heatmap(\n",
" dfc_top400, profile_vars_top400, \"cluster_k5\",\n",
" title=\"Cluster signatures — 400 top accounts K=5 (robust z-score)\",\n",
" figsize=(16, 4)\n",
")\n",
"print(\"\\n=== Medians K=5 (Top400) ===\")\n",
"print(prof_louis_k5.round(3).to_string())\n",
"\n",
"# 5. Churn analysis\n",
"dfc_top400[\"churn_hard\"] = (dfc_top400[\"aum_final_to_peak\"] < 0.10).astype(int)\n",
"dfc_top400[\"churn_soft\"] = (\n",
" (dfc_top400[\"aum_final_to_peak\"] < 0.40) &\n",
" (dfc_top400[\"aum_drawdown_last\"] > 0.40)\n",
").astype(int)\n",
"dfc_top400[\"churn_warning\"] = (\n",
" (dfc_top400[\"flow_direction_balance\"] < 0) &\n",
" (dfc_top400[\"aum_drawdown_last\"] > 0.20)\n",
").astype(int)\n",
"\n",
"print(\"\\n=== Overall churn rates ===\")\n",
"print(dfc_top400[[\"churn_hard\", \"churn_soft\", \"churn_warning\"]].mean().round(3))\n",
"\n",
"for k in [2, 5]:\n",
" churn_profile = (\n",
" dfc_top400.groupby(f\"cluster_k{k}\")\n",
" .agg(\n",
" n_clients = (ID_COL, \"count\"),\n",
" churn_hard = (\"churn_hard\", \"mean\"),\n",
" churn_soft = (\"churn_soft\", \"mean\"),\n",
" churn_warning = (\"churn_warning\", \"mean\"),\n",
" )\n",
" )\n",
" print(f\"\\n=== Churn per cluster K={k} ===\")\n",
" print(churn_profile.round(3).to_string())\n",
"\n",
" churn_profile[[\"churn_hard\", \"churn_soft\", \"churn_warning\"]].plot(\n",
" kind=\"bar\", figsize=(8, 4),\n",
" color=[\"#d62728\", \"#ff7f0e\", \"#ffbb78\"]\n",
" )\n",
" plt.title(f\"Churn rates by cluster — Top 400 accounts (K={k})\")\n",
" plt.ylabel(\"Rate\")\n",
" plt.xlabel(\"Cluster\")\n",
" plt.xticks(rotation=0)\n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
"# 6. Inter-cluster distance matrix\n",
"def plot_distance_matrix(X_scaled, labels, max_points=400,\n",
" title=\"Distance matrix\"):\n",
" n = X_scaled.shape[0]\n",
" idx = np.arange(n)\n",
" if n > max_points:\n",
" rng = np.random.default_rng(42)\n",
" idx = rng.choice(idx, size=max_points, replace=False)\n",
" X_sub = X_scaled[idx]\n",
" labels_sub = np.asarray(labels)[idx]\n",
" order = np.lexsort((np.arange(len(labels_sub)), labels_sub))\n",
" X_sub = X_sub[order]\n",
" labels_sub = labels_sub[order]\n",
" D = pairwise_distances(X_sub)\n",
" plt.figure(figsize=(8, 7))\n",
" sns.heatmap(D, cmap=\"viridis\")\n",
" unique_labels, counts = np.unique(labels_sub, return_counts=True)\n",
" for b in np.cumsum(counts)[:-1]:\n",
" plt.axhline(b, color=\"red\", linewidth=2)\n",
" plt.axvline(b, color=\"red\", linewidth=2)\n",
" plt.title(title)\n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
"plot_distance_matrix(\n",
" X_top400_scaled,\n",
" dfc_top400[\"cluster_k5\"].values,\n",
" title=\"Inter-cluster Distance Matrix — Top 400 Accounts (K=5)\"\n",
")\n",
"\n",
"# 7. Full cluster profile table\n",
"for k in [2, 5]:\n",
" print(f\"\\n===== K={k} =====\")\n",
" prof = (\n",
" dfc_top400.groupby(f\"cluster_k{k}\")\n",
" .agg(\n",
" n_clients = (ID_COL, \"count\"),\n",
" aum_qty_mean_med = (\"aum_qty_mean\", \"median\"),\n",
" freq_med = (\"flow_freq\", \"median\"),\n",
" gross_flow_to_aum_med = (\"gross_flow_to_aum\", \"median\"),\n",
" n_tx_med = (\"n_tx_total\", \"median\"),\n",
" holding_med = (\"avg_holding_months_per_isin\",\"median\"),\n",
" exit_rate_med = (\"exit_rate_per_isin\", \"median\"),\n",
" flow_dir_med = (\"flow_direction_balance\", \"median\"),\n",
" drawdown_med = (\"aum_drawdown_last\", \"median\"),\n",
" months_inactive_med = (\"months_since_last_tx\", \"median\"),\n",
" corr_fund_lag3_med = (\"corr_flow_fund_lag3\", \"median\"),\n",
" corr_rate_lag3_med = (\"corr_flow_rate_lag3\", \"median\"),\n",
" )\n",
" .sort_values(\"n_clients\", ascending=False)\n",
" )\n",
" print(prof.round(3).to_string())"
]
},
{
"cell_type": "markdown",
"id": "c97f67e5",
"metadata": {},
"source": [
"---\n",
"## 7. Cross-Analysis: Global vs Top 400 Accounts\n",
"\n",
"The Adjusted Rand Index (ARI) measures whether the two segmentations agree on the accounts they share. An ARI close to 0 means the two clusterings are independent — which is expected and desirable, as they are built on different feature sets and objectives.\n"
]
},
{
"cell_type": "code",
2026-04-10 11:05:13 +02:00
"execution_count": 63,
2026-04-07 20:26:19 +02:00
"id": "b2716808",
"metadata": {},
"outputs": [
{
2026-04-10 11:05:13 +02:00
"name": "stdout",
"output_type": "stream",
"text": [
"Accounts present in both analyses: 293\n",
"\n",
"Croisement K=4 x K=5 (n=293) :\n",
" Top 400 Accounts C0 Top 400 Accounts C1 Top 400 Accounts C2 Top 400 Accounts C3 Top 400 Accounts C4\n",
"Global C1 20.0 39.0 30.0 9.0 2.0\n",
"Global C2 0.0 100.0 0.0 0.0 0.0\n",
"Global C3 20.0 0.0 15.0 41.0 23.0\n",
"Global C4 4.0 59.0 30.0 7.0 0.0\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 800x500 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Adjusted Rand Index Global x Top 400 Accounts : 0.1341\n",
"(0 = independent segmentations, 1 = identical)\n"
]
}
],
"source": [
"# Comptes communs\n",
"common_ids = set(dfc[ID_COL]).intersection(set(dfc_top400[ID_COL]))\n",
"print(f\"Accounts present in both analyses: {len(common_ids)}\")\n",
"\n",
"# Croisement des clusterings sur les shared accounts\n",
"dfc_compare = (\n",
" dfc[dfc[ID_COL].isin(common_ids)][[ID_COL, \"cluster_k4\"]]\n",
" .rename(columns={\"cluster_k4\": \"cluster_global\"})\n",
" .merge(\n",
" dfc_top400[dfc_top400[ID_COL].isin(common_ids)][[ID_COL, \"cluster_k5\"]]\n",
" .rename(columns={\"cluster_k5\": \"cluster_400_accounts\"}),\n",
" on=ID_COL\n",
" )\n",
")\n",
"\n",
"print(f\"\\nCroisement K=4 x K=5 (n={len(dfc_compare)}) :\")\n",
"ct = pd.crosstab(\n",
" dfc_compare[\"cluster_global\"],\n",
" dfc_compare[\"cluster_400_accounts\"],\n",
" normalize=\"index\"\n",
").round(2) * 100\n",
"ct.index = [f\"Global C{i}\" for i in ct.index]\n",
"ct.columns = [f\"Top 400 Accounts C{i}\" for i in ct.columns]\n",
"print(ct.to_string())\n",
"\n",
"plt.figure(figsize=(8, 5))\n",
"sns.heatmap(ct, cmap=\"Blues\", annot=True, fmt=\".1f\",\n",
" cbar_kws={\"label\": \"%\"})\n",
"plt.title(\"Global clustering (K=4) x 400 top accounts (K=5)\")\n",
"plt.tight_layout(); plt.show()\n",
"\n",
"ari = adjusted_rand_score(\n",
" dfc_compare[\"cluster_global\"].values,\n",
" dfc_compare[\"cluster_400_accounts\"].values\n",
")\n",
"print(f\"\\nAdjusted Rand Index Global x Top 400 Accounts : {ari:.4f}\")\n",
"print(\"(0 = independent segmentations, 1 = identical)\")"
]
},
{
"cell_type": "code",
"execution_count": 64,
"id": "5a3ec2e8-d19f-43d5-80c7-5deb19c33197",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1600x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABO8AAAGGCAYAAAAjJaUdAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAA/aNJREFUeJzs3XdYU9cbwPFvWCooMl04EJShiKIo4hb3Vtwi7q11j2KrdVWtiraOuvdWFPe2jlqruFoXLhwVtA5AhqCs/P7gRzQmICAKbd/P8+RRTs49973JvRlvzlAolUolQgghhBBCCCGEEEKIHEcnuwMQQgghhBBCCCGEEEJoJ8k7IYQQQgghhBBCCCFyKEneCSGEEEIIIYQQQgiRQ0nyTgghhBBCCCGEEEKIHEqSd0IIIYQQQgghhBBC5FCSvBNCCCGEEEIIIYQQIoeS5J0QQgghhBBCCCGEEDmUJO+EEEIIIYQQQgghhMihJHknhBBCCCGEEEIIIUQOJck7IYQQ/xrnz5/H3t6enTt3ZncoWcLDwwNvb2+1Mm9vbzw8PLIpooz7+uuvsbe3z9YYFixYgL29PcHBwdkaR1rs7e35+uuvszuM/5zg4GDs7e1ZsGBBtmyflp07d2Jvb8/58+ezvG34d51zOekaVyqVdOzYkVGjRmV3KOkyePBgjfcZIYQQOY8k74QQQnySlITZ+zcXFxc8PT1Zu3YtiYmJ2R1ilouMjGTBggWZ/lIdERGBs7Mz9vb27Nq1K2uDywY7d+5kzZo12R1GjvPs2TNmzZpFixYtcHFxwcnJCQ8PD0aPHs3vv//+xeMJDg5mwYIFBAYGfvF9f0m//PILQ4YMoVatWjg5OeHi4kKTJk0YP358tjzuX5Kcc9lv3759XL9+na+++kqt3MPDg+bNm2vUj46OxsvLC3t7e5YtW5bl8SQlJdGxY0fs7e3p37+/xv1fffUVFy5c4Pjx41m+byGEEFlHL7sDEEII8e/QvHlzatWqhVKp5Pnz5/j7+zN9+nTu3bvH1KlTszu8LBUZGcnChQsZMmQIbm5uGd5+7969xMXFUbRoUXbs2EHr1q2zPsgvyN/fn5CQEHr06KFx39SpU5k8efKXDyqbnTx5kpEjRxIXF0fjxo3p2LEjuXLlIiQkhOPHj9OjRw+WLVtG7dq1v1hMISEhLFy4ECsrKxwdHb/Yfr+UN2/eMGrUKI4dO0bJkiVp3bo1xYoVIzExkYcPH3LixAl27NiBr6+v1iTKP91/+ZwbOHAg/fr1w8DA4LPtI70WLVpEnTp1sLa2/mjdsLAwevfuza1bt5g6dSodOnTI8ng2bdrEnTt3Ur3fwcGBKlWq8PPPP1OvXr0s378QQoisIck7IYQQWaJMmTK0atVK9XeXLl1o0qQJ27dvZ9iwYVhYWGjdLjo6mrx5836pMHMEPz8/3NzcqFevHtOnT+fx48cUK1Ysu8P6LPT19bM7hC/u7t27DBs2jPz587N69WpsbW3V7h82bBh79uzJEYmGrJTd1/KkSZM4duwYvXv3ZvTo0ejoqA8wGTduHEePHiV37tzZFOHn818/5/T09NDTy/6vNb///jsPHjxI15DZp0+f0rNnT4KDg/H19aVp06ZZHs/ff//N3LlzGTp0KDNnzky1XqtWrRg/fjw3btygbNmyWR6HEEKITyfDZoUQQnwWefPmxcXFBaVSyePHj4F3c7jdvHmT3r17U6lSJVq2bKna5sKFC/Ts2ZNKlSrh7OxMmzZt2L59u9b2jx07RuvWrSlXrhy1a9fmxx9/JCEhQaNeWnMhaZtTDuDcuXP069cPNzc3ypUrR7169Rg/fjxhYWGcP39e1Tth4cKFqqHC6Z2H7saNGwQGBtKmTRuaN2+Onp4efn5+6do2LRl57B49eoSPj49qWGGNGjUYOHAg169fV9U5c+YMw4cPp169ejg7O+Pq6kqvXr0ICAhQa8vDw4OAgABCQkLUhk6nDClObc67W7duMXjwYNVj3LRpU5YvX64xzDpl+6ioKL777jvc3d0pV64cnTp14s8//8zQYxQbG8u0adOoXr06zs7OtG/fXm0oYVxcHFWrVqVTp05at1+xYgX29vZcuHAhzf3Mnz+fN2/eMG3aNI0kCoBCoaBVq1a4u7un2kZac6lpO6efPn2Kj48PdevWxcnJCXd3dzp16oS/vz+QPLS5W7duAPj4+Kiep/fPf6VSyaZNm/D09KR8+fK4uLjg7e3NuXPnUo3twIEDeHp64uzszLRp09J8XD6nW7du4e/vT8WKFRkzZoxG4g6SH/eGDRtSq1atj7aXkJDAsmXLaNq0KeXKlcPNzY3Bgwdz+/btVLfZt28fLVq0oFy5ctSpU4cFCxZovCYFBQUxadIkmjVrhouLC+XLl8fT0zPVazW9/uvnnLb4Usru37/P3LlzVa93LVu25NSpUxrHGBsby4wZM6hRowbOzs506NCB33//PUPzdh48eBBdXV2qV6+eZr0HDx7QpUsX/v77bxYvXvxZEncAkydPplixYqrnITUp18TBgwc/SxxCCCE+Xfb/RCWEEOJfSalU8ujRIwBMTU1V5U+ePKF79+40btyYhg0bEhMTA7ybp8rCwoKePXuSN29e9u/fz7fffktwcDAjRoxQtXH06FG++uorrKysGDx4MLq6uuzcuVPrF7KM2rJlC5MmTaJgwYJ06tQJKysrnjx5wokTJ3j27Bm2trb4+PgwY8YMGjRoQIMGDQAwMjJKV/t+fn4YGhrSsGFDDA0NqVOnDrt27WLYsGFaEw7pkZHH7tq1a/To0YOEhATatWtH6dKliYiIICAggCtXruDk5AQkD4WNiIigdevWFCpUiGfPnrF9+3Z69OjBunXrcHV1BWD8+PH4+voSHh6Oj4+Paj/aEgjvx+Dt7Y2enh5eXl5YWFhw4sQJ5syZw61bt/D19dXYpnfv3piZmTF48GBevXrF6tWr6devH8ePH093b69x48aho6ND3759iY6OZuvWrfTp04fly5dTrVo1DAwMaNOmDatWreL+/fvY2Niobb9jxw6sra2pXLlyqvt4+/YtJ0+epHDhwulKEmWFhIQEevbsybNnz+jSpQvW1tZER0dz+/ZtLl68SJs2bahcuTIDBgxgyZIldOzYkUqVKgGo9YgdM2YM+/fvp1GjRnh6ehIXF8fevXvp1asXCxYs0BhSd+zYMdavX0/nzp3p1KlTtva6O3LkCADt2rVDoVB8cnujR4/m4MGDVK9enc6dO/Py5Us2btxIp06d2LhxI2XKlFGr/8svv/D48WPV+fzLL7+wcOFCnjx5wowZM1T1AgICuHjxInXq1KFo0aLExsZy6NAhvv32W8LCwrTOSfYxcs6l7euvv0ZPT49evXoRHx/P2rVrGTx4MIcOHaJo0aKqesOGDePUqVPUr1+fatWqERwczODBg9XqfMyFCxcoVaoUhoaGqdYJDAykd+/exMfHs2rVKipWrKi1XlhYWLr3my9fPo1ezocOHeLEiRNs2bIFXV3dNLe3tLTEyspK48cZIYQQOYck74QQQmSJ2NhY1ZeN58+fs2HDBm7dukWFChXU5v4JDg5m2rRptG/fXlWWmJjI1KlTMTQ0ZPv27RQsWBBIHnrbrVs3li1bRps2bbC2tiYxMZHvv/+e/Pnzs337dszMzADo1KmTWi++zPj777+ZNm0aNjY2bNmyBWNjY9V9w4cPJykpCR0dHerXr8+MGTOwt7dXGyr8MW/fvmXfvn00atRI9eWudevWHD16lF9//TVTc1Fl5LFTKpX4+PgQFxfH9u3bcXBwULXTv39/kpKSVH+ntPm+Tp060axZM5YuXapK3tWvX5+1a9fy9u3bdD8W33//PXFxcWzZskUVQ9euXRk+fDj79u2jXbt2Gj2EypQpw6RJk1R/29raquqn1lPuQ7q6umzcuFE1dLBdu3Y0adKEqVOnqnqcdOjQgVWrVuHn58fYsWNV2166dIn79+8zevToNPfx8OFD4uLi1B7bz+3evXs8ePCA0aNH07dvX611ihUrRrVq1ViyZAkVKlTQeK6OHj3K3r17mTJlCh07dlSVd+vWjQ4
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1800x500 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def plot_heatmap_annotated(dfc, profile_vars, cluster_col, title, figsize=(16, 5)):\n",
" \"\"\"\n",
" Heatmap colored by robust z-score, annotated with actual cluster medians.\n",
" \"\"\"\n",
" # Real medians\n",
" prof_median = dfc.groupby(cluster_col)[profile_vars].median()\n",
"\n",
" # Robust z-scores for color\n",
" prof_z = prof_median.copy().astype(float)\n",
" for col in profile_vars:\n",
" vals = prof_median[col].values\n",
" med = np.median(vals)\n",
" mad = np.median(np.abs(vals - med)) * 1.4826\n",
" if mad > 0:\n",
" prof_z[col] = (vals - med) / mad\n",
" else:\n",
" prof_z[col] = np.zeros(len(vals))\n",
" prof_z = prof_z.clip(-3, 3)\n",
"\n",
" # Format annotations with actual medians\n",
" def fmt(val):\n",
" if abs(val) >= 1000:\n",
" return f\"{val:,.0f}\"\n",
" elif abs(val) >= 10:\n",
" return f\"{val:.1f}\"\n",
" elif abs(val) >= 0.01:\n",
" return f\"{val:.2f}\"\n",
" else:\n",
" return f\"{val:.3f}\"\n",
"\n",
" annot = prof_median.applymap(fmt)\n",
"\n",
" # Row labels: Cluster 1, 2, ... (1-indexed) with cluster size\n",
" cluster_sizes = dfc[cluster_col].value_counts().sort_index()\n",
" row_labels = [\n",
" f\"Cluster {i} (n={cluster_sizes.get(i, '?'):,})\"\n",
" for i in prof_median.index\n",
" ]\n",
"\n",
" fig, ax = plt.subplots(figsize=figsize)\n",
" sns.heatmap(\n",
" prof_z,\n",
" cmap=\"RdBu_r\",\n",
" center=0,\n",
" annot=annot,\n",
" fmt=\"\",\n",
" linewidths=0.5,\n",
" linecolor=\"white\",\n",
" ax=ax,\n",
" cbar_kws={\"label\": \"Robust z-score\", \"shrink\": 0.8},\n",
" xticklabels=profile_vars,\n",
" yticklabels=row_labels,\n",
" )\n",
" ax.set_title(title, fontsize=13, pad=12)\n",
" ax.tick_params(axis=\"x\", rotation=45, labelsize=9)\n",
" ax.tick_params(axis=\"y\", rotation=0, labelsize=9)\n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
" return prof_median\n",
"\n",
"\n",
"# ── Global clustering ──────────────────────────────────────────────────────\n",
"prof_global = plot_heatmap_annotated(\n",
" dfc,\n",
" profile_vars = profile_vars_behavior,\n",
" cluster_col = \"cluster_k4\",\n",
" title = \"Cluster Signatures — Global Clustering (K=4)\\n\"\n",
" \"Color: robust z-score | Values: cluster medians\",\n",
" figsize = (16, 4)\n",
")\n",
"\n",
"# ── Product allocation (post-clustering descriptor) ───────────────────────\n",
"prof_alloc = plot_heatmap_annotated(\n",
" dfc,\n",
" profile_vars = profile_vars_allocation,\n",
" cluster_col = \"cluster_k4\",\n",
" title = \"Product Allocation by Cluster — Global Clustering (K=4)\\n\"\n",
" \"Color: robust z-score | Values: cluster medians | Post-clustering descriptor\",\n",
" figsize = (14, 4)\n",
")\n",
"\n",
"# ── Top 400 accounts ──────────────────────────────────────────────────────\n",
"prof_top400 = plot_heatmap_annotated(\n",
" dfc_top400,\n",
" profile_vars = profile_vars_top400,\n",
" cluster_col = \"cluster_k5\",\n",
" title = \"Cluster Signatures — Top 400 Accounts (K=5)\\n\"\n",
" \"Color: robust z-score | Values: cluster medians\",\n",
" figsize = (18, 5)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "bcaac8cc-54bf-4edf-be3c-520ef1eeb464",
"metadata": {},
"source": [
"# VISUALISATION CLUSTERING — GLOBAL + TOP 400"
]
},
{
"cell_type": "code",
"execution_count": 65,
"id": "e42c50aa-0343-47b9-b562-78137d63d5e9",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"============================================================\n",
"GLOBAL CLUSTERING (K=4)\n",
"============================================================\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAArIAAAHqCAYAAAD4TK2HAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsnXd4FFXbh+/tLZveew9JCCX0rki39/LaESvYC5ZXP9HXXrGLBVFBRFAJIlKUJr23EBLSe08229v3x5KFZUMnFJ37urh0z5yZOXMyu/Ob5zxF5HQ6nQgICAgICAgICAicZ4jP9gAEBAQEBAQEBAQETgZByAoICAgICAgICJyXCEJWQEBAQEBAQEDgvEQQsgICAgICAgICAuclgpAVEBAQEBAQEBA4LxGErICAgICAgICAwHmJIGQFBAQEBAQEBATOSwQhKyAgICAgICAgcF4iCFkBAQEBAQEBAYHzEkHICggICJxnLFy4kL59+6LX68/2UDrkkUce4aGHHjrbwxAAJk+ezPDhw8/4ecvLy0lLS2PevHln/NwC/y4EISvwj6asrIwpU6YwevRounfvTvfu3Rk3bhwvvvgie/fu9ej7wQcfkJaWRmNj41GPabVamTFjBldffTU9e/akZ8+eXH311cyYMQOr1erVf/jw4aSlpbn/ZWVlMWrUKF5//XWam5uPeJ433niDtLQ0Hn744Q63tz8ovvzyy2POw5FYv349EydOZNCgQXTt2pUBAwZw7733snjx4hM6z/Dhw7nnnns63LZz584OH2ibNm3irrvuYsiQIWRlZXHBBRdw7733kpOTA7gewIfO25H+TZ48GYBbbrnliH3GjBnjPu+8efM8tmVkZDBkyBAmT55MTU2N1/hvueUWLrnkEq/rTUtL46WXXupwTtPS0li0aJHXthO5H4+E3W7ngw8+4Oabb0aj0XiMqaO/wS+//EJ6ejrjx4/HbDYf1zmOh02bNrnn8PDvzIQJE1i8ePFxX1NncejfuUuXLgwePJg777yT9evXe/W12+3MnTuXW265hb59+9K1a1eGDx/O008/zc6dOzs8/vfff09aWhrXXnttZ1/KOUtOTg7Tp08/28MQ+BcjPdsDEBDoLP766y8eeeQRJBIJl156KV26dEEsFlNYWMjixYuZNWsWy5YtIyoq6riPaTAYuOeee9iwYQMXXnghV111FSKRiFWrVvG///2PJUuW8Nlnn6FWqz32S09P54477gDAYrGwa9cuZsyYwcaNG/npp5+8zuN0Ovntt9+Iiorir7/+oq2tDR8fn1ObkMOYOnUqH330EfHx8Vx//fVERkbS3NzMihUrmDRpEm+99RaXXnrpaT1nO7///juPPPII6enp3Hrrrfj5+VFeXs7GjRv58ccfufTSS7n++usZMGCAe5/y8nKmTp3K9ddfT69evdztsbGx7v8PDw/n0Ucf9TqfVqv1anvwwQeJjo7GYrGwbds2fv75ZzZv3syCBQtQKBTHdR0//vgjd999N2FhYcfse7rux7/++ouioiKuv/76Y55z/vz5PP300wwcOJCPP/74uK/rWDgcDl5++WXUajUGg8Fre0ZGBl27duWrr77ijTfeOC3nPFkGDRrE5ZdfjtPppLy8nFmzZnHbbbfx2WefMWzYMABMJhMTJ05k1apV9OnTh3vuuQc/Pz8qKir4/fff+fnnn1m+fDnh4eEex87JySEqKoodO3ZQUlJCXFzc2bjEo/LSSy/hdDo77fgLFiwgPz+f22+/3aO9fV6kUkFmCHQuwh0m8I+ktLSURx99lMjISKZPn05oaKjH9scff5yZM2ciFp/YosRrr73Ghg0b+O9//8vNN9/sbr/pppv4/vvvmTJlCq+//jovvviix35hYWFcfvnl7s/XXnstarWar776iuLiYuLj4z36r1+/nurqar755hvuuusulixZwpVXXnlCYz0aixYt4qOPPmL06NG8/fbbyGQy97a77rqLVatWYbPZTtv5DufDDz8kOTmZ2bNnI5fLPbY1NDQAuK3d7ezcuZOpU6fSo0cPj7k8FK1We8RthzN06FCysrIA198jICCAadOmsWzZMsaNG3fM/VNSUigqKmLatGk899xzR+17Ou/HuXPnkp2dfUzx/NtvvzF58mT69+9/WkUswOzZs6mqquKaa65hxowZHfYZO3YsH3zwAXq93sNyfKaJj4/3uCdGjhzJZZddxowZM9xC9o033mDVqlU8/fTTXoJs4sSJHVocy8rK2Lp1Kx9++CHPP/88OTk5TJw4sTMvBXC9TB/+onw0Dv1un0lEItFpvecEBI6E4Fog8I/kiy++wGAw8Oqrr3qJBgCpVMqtt95KRETEcR+zurqan376if79+3uI2Hb+85//0K9fP3766Seqq6uPebyQkBAAJBKJ17acnBySk5Pp378/AwYMcC+3ny7ef/99/P39eeWVVzp80A0ZMoQLL7zwtJ7zUEpLS8nKyvISsQBBQUGddt6j0bt3b8AlUI6HqKgoLr/8cn788ccOXRIO5XTdj2azmVWrVjFw4MCj9lu4cCFPPPEEffv25ZNPPjmtgqK5uZn33nuPBx98EF9f3yP2GzhwIAaDgTVr1py2c58O0tLSCAgIoLy8HHB9r2fPns2gQYO8RCy4vp/jx4/v0Brr5+fHsGHDGD169Al9R9vdQFavXs3ll19OVlYW48aN83DpgYNuMBs2bOD//u//GDBggFt8g8u14eKLL6Zr164MHjyYF198kdbWVo9jdOQj63A4mD59OhdffDFZWVkMHDiQ559/npaWFq+xrlixgptvvpmePXuSnZ3N1Vdf7b7WW265heXLl1NRUeF24Wg/15F8ZNeuXctNN91Ejx496N27N/fddx/79+/36NPu5lVSUsLkyZPp3bs3vXr14umnn8ZoNB73PAv8OxCErMA/kr/++ou4uDi6d+9+2o65cuVK7HY7V1xxxRH7XHHFFdhsNlatWuXRbrPZaGxspLGxkerqav7880++/vpr+vTpQ0xMjEdfi8XC4sWLufjiiwG4+OKLWbduHXV1daflOoqLiyksLOSiiy467e4Kx0tkZCRr1649LsF/Itjtdvc8H/qvo+Xvw6moqAA4qjg7nPvuuw+73c60adOO2u903Y+7du3CarWSkZFxxD5//PEHTzzxBL179+bTTz9FqVR69Wlpaelwng7/15FoeP/99wkJCeGGG2446liTk5NRKpVs2bLlxC+0E2lpaaG1tRV/f3/A9b222WxcdtllJ3ScnJwcRo4ciVwu55JLLqG4uJgdO3Yc9/7FxcU88sgjDB06lMceewyJRMJDDz3E33//7dX3xRdfZP/+/TzwwANMmDABcIm9KVOmEBoayuTJkxk9ejSzZ8/mzjvv7NBX/1Cef/553nzzTbKzs3n22We56qqryMnJYfz48R77zps3j3vuuYeWlhbuueceHnvsMdLT092/b/feey/p6ekEBATwxhtv8MYbb/DMM88c8bxr1qzhrrvuoqGhgYkTJ3L77bezdetWbrzxRveLxaE8/PDD6PV6Hn30UcaOHcu8efP48MMPj2t+Bf49CK4FAv842traqK2tZcSIEV7bWltbPZbM1Wp1hw/6jigoKACgS5cuR+zTvu1wC8Pq1as9/D0BsrOz+eCDD7yO8ddff9Ha2uoWsiNGjOD555/nt99+69BidKK0jy01NfWUj3WyTJgwgWeffZYRI0aQnZ1Nr169GDRoENnZ2Sfs7nEohYWFXvMMcP311zNlyhSPtra2NhobG7FYLGzfvp0PP/wQuVx+QpbomJgYLrvsMrevbEfW1tN5PxYWFgIQHR3d4fY9e/awevVqsrOz+eyzz454rCuvvNIt3I/GxIkTmTRpkvvz3r17mT17Np9//nmHKwmHIpVKCQ8Pd39vzhZms9kdjFZeXs4777yD3W53BwC2fx/S0tKO+5i7du2isLCQ//73vwD06tWL8PBwcnJy6Nat23Edo7i4mA8++IBRo0YBcM011zBmzBjeeustBg0a5NHXz8+P6dOnu+e8sbGRzz77jMGDBzNt2jT3dyYxMZEpU6Ywf/5
"text/plain": [
"<Figure size 700x500 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABKYAAAHpCAYAAAC4KDT4AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3XmYHFW5P/DvqaW7Z59JMtkTCAkkaAJJQGMgIYoBBERviBKFAGJAEFkFMSIXDGDCLrIo+xJ2FAKCgAg/5AYJiwoiyJaEQEKW2dee7qo65/z+qO6a6cxMZutllu/nefJcM11ddXqZPLde3vd7hNZag4iIiIiIiIiIKMuMXC+AiIiIiIiIiIiGJhamiIiIiIiIiIgoJ1iYIiIiIiIiIiKinGBhioiIiIiIiIiIcoKFKSIiIiIiIiIiygkWpoiIiIiIiIiIKCdYmCIiIiIiIiIiopxgYYqIiIiIiIiIiHKChSkiIiIiIiIiIsoJFqaIiIho0HvmmWfw5S9/Gc3NzbleSofOPfdcnH322bleRr9z4403YurUqSk/O/jgg7F8+fJenW/q1Km48cYb07G0nHv99dcxdepUvP7668HPli9fjoMPPjiHqyIiIuo5FqaIiCjF5s2bcemll+Kwww7Dvvvui3333RdHHHEEVqxYgQ8++CDl2ORNY01NzS7P6bouVq9ejcWLF2PWrFmYNWsWFi9ejNWrV8N13XbHH3zwwZg6dWrwZ8aMGTj00ENx5ZVXoq6urtPrXHXVVZg6dSrOOeecDh/fsmULpk6dijvvvLPL96Ezr7/+Os444wwceOCBmD59OubOnYvTTjsNzz//fI+uc/DBB+PUU0/t8LH//Oc/mDp1Kh5//PGUn//jH//AySefjPnz52PGjBn46le/itNOOw1PPfUUAP+mtO371tmf5E398ccf3+kx3/jGN4LrPv744ymPfeELX8D8+fOxfPly7Nixo936jz/+eHzzm99s93qnTp2Kyy67rMP3dOrUqXjuuefaPdaT72NnpJS48cYbsXTpUhQUFKSsqaPP4IknnsDee++NZcuWIR6Pd+sa3fGPf/wjeA93/p055ZRT8Pzzz3f7NWVK28952rRpmDdvHn74wx+mFD9oaPvXv/6FG2+8EQ0NDbleChERDRJWrhdARET9x0svvYRzzz0XpmniqKOOwrRp02AYBjZu3Ijnn38eDz30EF588UWMGzeu2+eMRqM49dRT8cYbb+BrX/sajj76aAghsHbtWvz617/GX//6V9x6663Iz89Ped7ee++Nk046CQDgOA7effddrF69Gm+++Sb++Mc/truO1hp//vOfMW7cOLz00ktoampCYWFh396Qndxwww24+eabsfvuu2PJkiUYO3Ys6urq8PLLL+PMM8/ENddcg6OOOiqt10x69tlnce6552LvvffGCSecgJKSEmzZsgVvvvkmHn30URx11FFYsmQJ5s6dGzxny5YtuOGGG7BkyRLst99+wc8nTpwY/O/Ro0fjpz/9abvrFRUVtfvZWWedhfHjx8NxHLz99ttYs2YN/vnPf+Lpp59GOBzu1ut49NFH8aMf/QijRo3q8th0fR9feuklfPLJJ1iyZEmX1/zTn/6EX/ziFzjggAPwu9/9rtuvqytKKVx++eXIz89HNBpt9/gXvvAFTJ8+HXfddReuuuqqtFyztw488EB8+9vfhtYaW7ZswUMPPYQTTzwRt956KxYsWJDTtdGuXXbZZdBaZ/Qab731Fm666SYsWrQIxcXFGb0WERENDSxMERERAOCzzz7DT3/6U4wdOxb33HMPRo4cmfL4+eefjwcffBCG0bNm2yuuuAJvvPEG/vd//xdLly4Nfn7sscfigQcewKWXXoorr7wSK1asSHneqFGj8O1vfzv4+3e/+13k5+fjrrvuwqZNm7D77runHP/6669j+/btuPfee3HyySfjr3/9KxYtWtSjte7Kc889h5tvvhmHHXYYrr32Wti2HTx28sknY+3atfA8L23X29lNN92EKVOm4JFHHkEoFEp5rLq6GgCCbrSk//znP7jhhhswc+bMlPeyraKiok4f29lBBx2EGTNmAPA/j7KyMtx+++148cUXccQRR3T5/D333BOffPIJbr/9dlx00UW7PDad38fHHnsMs2fP7rIY9uc//xnLly/HV77ylbQWpQDgkUcewbZt2/Cd73wHq1ev7vCYww8/HDfeeCOam5tTOruybffdd0/5ThxyyCH41re+hdWrV/e5MBWNRtsVoaljWmvE43FEIpFuP6ftv0tEREQDBUf5iIgIAHDHHXcgGo1i1apV7YoAAGBZFk444QSMGTOm2+fcvn07/vjHP+IrX/lKSlEq6bjjjsOcOXPwxz/+Edu3b+/yfOXl5QAA0zTbPfbUU09hypQp+MpXvoK5c+cG423p8tvf/halpaVYuXJlhzd/8+fPx9e+9rW0XrOtzz77DDNmzGhXlAKA4cOHZ+y6u7L//vsD8MftumPcuHH49re/jUcffbTDEcC20vV9jMfjWLt2LQ444IBdHvfMM8/gZz/7Gb785S/j97//fVqLUnV1dbj++utx1lln7bLD5IADDkA0GsWrr76atmunw9SpU1FWVoYtW7YA8EcSzzrrLHz1q1/F9OnTsWDBAqxcuRKxWCzlecuXL8esWbPw2Wef4ZRTTsGsWbNw/vnn9+gc3dXQ0IBf//rXWLBgAaZPn45DDjkEt912G5RSvTpfPB7HjTfeiMMOOwwzZszAvHnzcMYZZ+Czzz4LjolGo7jiiiuCax522GG4884723UseZ6Hm2++GQsXLsT06dNx8MEH47rrroPjOCnHJUdL165di6OPPhr77LMPHn74YQD+v6Wnn346Zs6ciblz52LlypXtng+0z5hqO1b8yCOPBGtYvHgx3nnnnZTnfvDBB1i+fDm+/vWvY8aMGTjwwAPxi1/8ArW1tcExN954Y9DR9/Wvfz0Y+0x+NwDgySefDNb/5S9/Geeeey62bduWcq1NmzbhzDPPxIEHHogZM2bgoIMOwrnnnovGxsZufT5ERDS4sGOKiIgA+ONOu+22G/bdd9+0nfP//u//IKXE//zP/3R6zP/8z//g9ddfx9q1a/Hd7343+LnneUEOj+M4+O9//4u7774bX/rSlzBhwoSUcziOg+effz4Y/TvyyCNx4YUXorKyMihm9cWmTZuwceNGLF68OO3jgd01duxYrFu3Dtu3b8fo0aPTdl4pZYcZYZFIpMvOls8//xwAejTO8+Mf/xhPPvlkl11T6fo+vvvuu3BdF1/4whc6PeYvf/kLfvazn2H//ffHLbfc0mGHSn19PaSUXV4vLy8PeXl5KT/77W9/i/Lycnzve9/D7373u06fO2XKFEQiEfzrX//CIYcc0uW1sqW+vh4NDQ3YbbfdAPjdg7FYDN///vdRWlqKd955B/fffz+2b9+OG264IeW5nudh2bJl2G+//fDzn/88eG97co6utLS0YOnSpdixYwe+973vYcyYMXjrrbdw3XXXobKyEr/85S97dD4pJU499VSsW7cORx55JE444QQ0Nzfj73//Oz766CNMnDgRWmv8+Mc/xuuvv47vfOc72HvvvbF27VpcddVV2LFjBy688MLgfBdddBHWrFmDww47DCeddBLeeecd3HrrrdiwYQNuvvnmlGt/8sknOO+887BkyRIcc8wxmDRpEmKxGE488URs27YNxx9/PEaOHIknn3wSr732Wrdf09NPP43m5mYsWbIEQgjccccdOPPMM/HCCy8EhfZXX30VmzdvxtFHH43y8nJ8/PHHePTRR7F+/Xo8+uijEELgkEMOwaZNm/D000/jF7/4BcrKygAAw4YNAwD8/ve/x29/+1scfvjh+M53voOamhrcf//9OO644/DEE0+guLgYjuNg2bJlcBwHS5cuxYgRI7Bjxw787W9/Q0NDQ4djxERENLixMEVERGhqakJFRQUWLlzY7rGGhoaUEbX8/Pxuj5asX78eADBt2rROj0k+tmHDhpSfv/LKKyl5SQAwe/bsDnfUeumll9DQ0IAjjzwSALBw4UJcfPHF+PO
"text/plain": [
"<Figure size 1200x500 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAk0AAAGGCAYAAABmPbWyAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAT6hJREFUeJzt3Xl8DOfjB/BPdnMfglAk4qgQidwUiZBKU1otLeooooejtEJpHdUWQYOiVaEV9y1VSgXVoqpB6milQkkR6iYJGjnYZHd+f/ju/LKyyc5udrOb5PN+vfIiM/PM88yTyeazzzwzayUIggAiIiIiKpPM3A0gIiIiqgwYmoiIiIgkYGgiIiIikoChiYiIiEgChiYiIiIiCRiaiIiIiCRgaCIiIiKSgKGJiIiISAKGJiIiIiIJGJqIqMLs3r0bbdu2RV5enrmbotXYsWMxZswYczdDsvj4eHh7e2ssi4yMxKRJk8Tvv//+e3h7eyMtLa2im1cpSelTU7l27Rq8vb3x/fffi8smTZqE4OBgk9et5u3tjfj4+Aqrr7JhaLJQV69exfTp09G1a1cEBgYiMDAQ3bp1Q2xsLM6dO6exrfqX/O7du2Xus7CwEGvXrkXv3r0RHByM4OBg9O7dG2vXrkVhYWGJ7SMjI+Ht7S1++fv7o0uXLpgzZw7u379faj2ff/45vL298f7772tdr35hWLFihc5+KM3Ro0cxatQodOjQAX5+fggNDcWIESPw888/61VPZGQk3nnnHa3r0tLSSryAAcCJEycwdOhQdOzYEf7+/nj22WcxYsQIJCUlAXj8Ile830r7Ur8IR0dHl7rNCy+8INar/uOn/vL19UXHjh0xadIk3L59u0T7o6Oj8fLLL5c4Xm9vb8yYMUNrn3p7e2PPnj0l1ulzPpZGqVQiPj4egwYNgpOTk0abtP0Mtm/fDh8fHwwZMgSPHj2SVIcUJ06cEPvwyd+ZYcOG4eeff5Z8TKaSl5eHhQsX4uWXX0ZQUBDatWuHV155BTNnztT6s64KlixZgn379pVY/ueffyI+Ph45OTlmaJXhDh48aLHhw5LbZumszd0AKunAgQMYO3Ys5HI5unfvjpYtW0ImkyEjIwM///wzNm3ahP3798PDw0PyPvPz8/HOO+/g2LFj6Ny5M3r16gUrKyskJyfjs88+w969e5GQkABHR0eNcj4+PnjrrbcAAAqFAqdPn8batWtx/PhxbNmypUQ9giBg165d8PDwwIEDB5CbmwtnZ+fydcgTFi5ciMWLF6NJkybo168f3N3dcf/+fRw8eBAxMTGYN28eunfvbtQ61X788UeMHTsWPj4+GDx4MFxdXXHt2jUcP34cmzdvRvfu3dGvXz+EhoaKZa5du4aFCxeiX79+aN26tbi8UaNG4v/r16+PcePGlajPxcWlxLLRo0ejYcOGUCgUSE1NxbZt2/DHH39g586dsLOzk3QcmzdvxvDhw1GvXj2d2xrrfDxw4AAuXbqEfv366axzx44d+OijjxAWFoavv/5a8nHpolKpMHPmTDg6OiI/P7/Eel9fX/j5+WHlypX4/PPPjVKnvgoLCzFo0CBkZGTg1VdfxaBBg5Cfn4/z589j586deP7558Wf28iRIzF8+HCztNPYEhIS0LVrV0RFRWksP3nyJBYtWoSePXuiRo0aZmnbnj17YGVlpVeZgwcPYsOGDYiJiZFcxsPDA6dOnYK1tWn/NJfVtlOnTkEul5u0/sqMocnCXLlyBePGjYO7uztWr16Np556SmP9hx9+iI0bN0Im02+QcPbs2Th27Bg+/fRTDBo0SFw+YMAAbNiwAdOnT8ecOXMQGxurUa5evXp45ZVXxO/79OkDR0dHrFy5EpcvX0aTJk00tj969Chu3bqFNWvWYOjQodi7dy969uypV1vLsmfPHixevBhdu3bF/PnzYWNjI64bOnQokpOTUVRUZLT6nrRo0SJ4eXnh22+/ha2trca67OxsABBH8dTS0tKwcOFCBAUFafRlcS4uLqWue1KnTp3g7+8P4PHPo1atWli2bBn279+Pbt266SzfvHlzXLp0CcuWLcMnn3xS5rbGPB+3bt2KkJAQnUFt165dmDRpEtq3b2/UwAQA3377LW7evInXXnsNa9eu1brNiy++iPj4eOTl5WmMiFWUffv24e+//9Ya/h89eqQxKmxtbW3yP7CEEr/rxlZUVASVSgVbW1ujnu+GMHf9lo6X5yzM8uXLkZ+fj1mzZpX4AwU8fpEcPHgwGjRoIHmft27dwpYtW9C+fXuNwKQ2cOBAtGvXDlu2bMGtW7d07q9u3boAoPXdSFJSEry8vNC+fXuEhoaKl6yM5auvvkLNmjURFxenEZjUOnbsiM6dOxu1zuKuXLkCf39/rS+ibm5uJqu3LG3atAHw+BKaFB4eHnjllVewefNmnZd6jHU+Pnr0CMnJyQgLCytzu927d2P8+PFo27YtvvnmG6O+gN+/fx8LFizA6NGjyxyxCAsLQ35+Po4cOWK0uvWh/jmGhISUWGdnZ6cxcqtt/k1pFAoFZs2ahfbt2yMoKAjvvfee1kv6GzZswEsvvQQ/Pz+Eh4cjNja2xKWx0ub4REdHIzo6ukS9CxcuxPPPPw8/Pz9ERETg888/h0KhELfx9vZGfn4+tm3bpnH5Oj4+Xhzxe+6558R1165dE8v+8MMP6NWrFwICAtC2bVuMHTsWN2/elNQnJ06cQO/eveHv74+oqCgkJiZq3e7J4y0sLMSiRYvQpUsX+Pv7o127dnj99ddx+PBhAI8v0W/YsEE8NvUXoDltYPXq1YiKioK/vz8uXryodU6T2tWrVzFkyBAEBQUhPDwcixYtgiAI4nr15fWjR49qlHtyn2W1Tb3syUt3f//9N4YOHYqQkBAEBwfjjTfeQGpqqsY26ukDf/zxh6TzrLLiWxQLc+DAATRu3BiBgYFG2+dvv/0GpVKJV199tdRtXn31VRw9ehTJycno06ePuLyoqEg84RUKBf7++2+sWrUKzzzzDDw9PTX2oVAo8PPPP4uX81566SVMnjwZmZmZYtAqj8uXLyMjIwO9e/c2+iU/qdzd3ZGSkoJbt26hfv36RtuvUqnU+sJib29f4pLpk65fvw4Ael26GDlyJH744Qedo03GOh9Pnz6NwsJC+Pr6lrrNTz/9hPHjx6NNmzZYsmQJ7O3tS2zz33//QalU6qzPwcEBDg4OGsu++uor1K1bF/3798fXX39dalkvLy/Y29vjzz//xPPPP6+zLmNzd3cH8HhO17vvvqv3ZaHSzJw5EzVq1MCoUaNw/fp1rFmzBtOnT8eCBQvEbeLj47Fo0SKEhYXh9ddfx6VLl7Bp0yakpaVh06ZNWt+olEWlUmHkyJH4448/0LdvXzRr1gz//PMP1qxZg8uXL4s/h88//xyffPIJAgIC0LdvXwCPL187ODjg8uXL2LlzJz766CPUqlULAFC7dm0AwDfffIOvvvoKL774Il577TXcvXsX69evx8CBA7F9+/YyfyfS09MxZMgQ1K5dGzExMSgqKkJ8fLykNz+LFi1CQkIC+vTpg4CAAOTm5uL06dM4c+YMOnTogH79+uHOnTs4fPhwqZd5v//+ezx69Ah9+/aFra0tXF1doVKptG6rVCoxdOhQBAYGYvz48UhOTkZ8fDyUSqXeNy5IaVtx58+fx8CBA+Hk5IShQ4fC2toa3377LaKjo7F+/foSrw1SzrPKjKHJguTm5uLOnTslrukDQE5OjsZlJ0dHR61/VLS5cOECAKBly5albqNed/HiRY3lhw4d0pifAzx+B6xtEuGBAweQk5ODl156CQAQFRWFKVOmYNeuXXjzzTcltbUs6ra1aNGi3Psy1LBhw/Dxxx8jKioKISEhaN26NTp06ICQkBC9L5kWl5GRUaKfgccvcNOnT9dYlpubi7t370KhUOCvv/7CokWLYGtrq9cIm6enJ3r06CHObdI2imTM8zEjIwMA0LBhQ63r//77bxw6dAghISFISEgodV89e/YUQ2JZRo0apTFf49y5c/j222+xdOlSnfM1rK2tUb9+ffH3pqJFRUWhadOmWLhwIbZ
"text/plain": [
"<Figure size 600x400 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 700x500 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"============================================================\n",
"TOP 400 CLUSTERING (K=5)\n",
"============================================================\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAArIAAAHqCAYAAAD4TK2HAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd4FNXXwPHv9pLeGyEJJaH3bgFRQREbNiyAiiAqlp+iYlcsoKIoithAEEFEBRRUxAa+Kl1poUoNJb1ns33eP2IWlk0gIQlJ4HyeJ4/m7uzM2Zllc/bOveeqFEVREEIIIYQQopFR13cAQgghhBBCnA5JZIUQQgghRKMkiawQQgghhGiUJJEVQgghhBCNkiSyQgghhBCiUZJEVgghhBBCNEqSyAohhBBCiEZJElkhhBBCCNEoSSIrhBBCCCEaJUlkhRCikRo1ahRPP/10fYdRoby8PDp16sTKlSvrOxQBpKSk8M4775zx477zzjukpKSc8eOKc4cksuKskZKSUqWfNWvWeJ6Tl5fHq6++ysCBA2nfvj09evRg5MiR/Pbbbz77P3TokNd+WrduTb9+/bjvvvvYvn17tWJ1OBwMGjSIlJQUZsyY4fO42+3mo48+on///rRv354rr7ySpUuXVrivPXv2MHLkSDp37kyPHj149NFHyc3NrXIsNpuNWbNmccMNN9C1a1fat2/PwIEDmTBhAvv27fNsV/4HqbJ9r1mzhpSUFJYtW1bh4xMmTPD5g2a325k9ezbXXHMNXbp0oVu3blxxxRU888wz7NmzB6jedT3xGp348+GHH3qOPWzYMK/HOnTowJVXXsmsWbNwu91ecZbv9/hrVf56U1JS2Lp1q8/rHT9+PJ07d67wXPz666+MGTOGPn360K5dO3r06MGtt97KzJkzKS4urvA5J9qwYQN//vkno0aN8onpxGtgt9u5++67adWqFV999VWV9l+ZhQsXVnp+s7KyPNuFhIRw/fXX8/bbb9foeDV1YrzHv7+zs7N9ts/OzubVV1/lsssuo2PHjnTq1IkhQ4bw3nvvUVhYWOExrr/+elJSUpg3b15dv5wGqbS0lHfeecfrs1WIM0Vb3wEIUVtee+01r9+/+eYb/vzzT5/25s2bA7B3715uv/12cnNzGTJkCO3bt6ewsJAlS5YwZswY7rzzTh5//HGf4wwePJgLL7wQt9vNnj17+Pzzz/n9999ZsGABrVu3rlKsn332GUePHq308SlTpvDhhx9y44030r59e3755RceeeQRVCoVV1xxhWe79PR0br31VgICAvjf//6HxWJh5syZ7Nq1iy+//BK9Xn/SOHJzc7nrrrtITU3loosuYvDgwZjNZvbt28f333/PggULKkzSassDDzzA77//zhVXXMENN9yA0+lk7969rFixgs6dO9O8efNqXVer1Qocu0YnatOmjdfv0dHRPPzww0DZl5qlS5cyceJE8vLy+N///lfl1/Huu+/y/vvvn3I7t9vNU089xcKFC0lOTuaWW24hOjqakpISNm7cyFtvvcXKlSuZPXv2Kfc1Y8YMevfuTUJCwkm3czgcPPDAA6xcuZIXX3yR66+/vsqv62QeeOABmjRp4tUWGBjo9fvNN9/MnDlzWLVqFb17966V456u8njtdjsbNmzg888/Z+XKlSxduhSTyQTA5s2bGT16NBaLhauuuoq2bdsCsHXrVj766CPWr1/PzJkzvfa7f/9+tmzZQlxcHEuWLOGWW24546+tKjZv3oxGo6mTfZeWlvLuu+8yduxYevbs6fXYPffcw+jRo+vkuEIAoAhxlnrhhReU5OTkCh+z2+3K4MGDlY4dOyobN270eszpdCoPPfSQkpycrHz33Xee9rS0NCU5OVn5+OOPvbb/5ZdflOTkZOWZZ56pUlzZ2dlK165dlXfffbfC/aWnpytt27ZVXnjhBU+b2+1WbrnlFuXCCy9UnE6np/25555TOnTooBw+fNjT9ueffyrJycnK/PnzTxnL6NGjlVatWinLli3zecxmsymTJk3y/D516lQlOTlZycnJqXBfq1evVpKTk5UffvihwsdPvB6bNm1SkpOTlenTp/ts63Q6ldzc3Crt53iVXaOK3HbbbcoVV1zh1Wa1WpWLLrpI6dy5s9d5rmi/5a/36quvVpKTk5WtW7d67evxxx9XOnXq5NX2wQcfKMnJycorr7yiuN1un5gyMjKUDz744JSxZ2dnK23atFEWLFjg1X7iNbDb7cq9996rpKSkKF988cUp91sVX3/9tZKcnKxs3ry5StsPHjxYefTRR2vl2KejsngnTpyoJCcnK0uWLFEURVEKCgqUCy64QOnTp4/y77//+uwnKytLmTZtmk/722+/rfTu3Vv58ccflZSUFCUtLa1uXsgJLBbLGTlOVeTk5CjJycnK1KlT6zsUcQ6SoQXinLR8+XJ27drFqFGj6Nixo9djGo2GCRMmEBgYWKUxZb169QLKbj9XxeTJk0lKSuKqq66q8PGff/4Zh8Ph1bOjUqm4+eabSU9P559//vF6Hf369SM2NtbT1qdPHxITE/nhhx9OGsemTZtYsWIF119/PQMHDvR5XK/XV9gjXVvS0tIA6NKli89jGo2GkJCQOjt2ZQwGA+3ataOkpIScnJwqPee2224jKCjolO+V0tJSPvroI1q2bMljjz2GSqXy2SYyMrJKvVcrVqzA6XTSp0+fSrdxOp08/PDD/PLLLzz//PPceOONp34x1VRcXIzL5TrpNn369OG3335DUZRaP35NnPjvdv78+WRkZDB+/HjPXZvjhYeHc++99/q0L126lIEDB9KvXz8CAgIqHQJ0ovJhIN9//z1vvvkm5513Hp06dWLMmDE+d2uGDRvG4MGD2bp1K7feeisdO3bkzTffBCAnJ4cnn3ySPn360L59e6666ioWLVrkc7yKxshmZGTwxBNPeIa4XHHFFRUOPbHZbLzzzjueIVjnn38+Y8eO5eDBgxw6dMjT2/7uu+96hnCUH6uiMbJOp5Np06ZxySWX0K5dO/r378+bb76J3W732q5///7cfffdrF+/nuuvv5727dtz8cUXs3jx4iqdY3FukERWnJN+/fVXAK655poKHw8ICODiiy9m7969HDhw4KT7OnjwIADBwcGnPO7mzZtZvHgxTz75ZIWJDMD27dsxm80+f0w7dOjgeRzK/gjl5OTQrl07n3106NDhlON2y8/B1Vdffcq460J58r1kyRKcTmet7ru0tJTc3Fyfn6oc5/Dhw6hUKp/b5JXx9/dnxIgR/Pbbb6Smpla63YYNGygsLOSKK66o8S3ef/75h+DgYOLi4ip83OVy8fDDD/PTTz/x7LPPMnToUJ9tHA5Hheeoop8TxwwDDB8+nK5du9KxY0fGjBnD/v37K4ylbdu2FBYWsnv37hq95tp24r/bX3/9FaPRWOGXusps2rSJAwcOcMUVV6DX67n00ktZsmRJteKYPn06K1asYNSoUQwbNoy//vqL22+/3TNMplx+fj6jRo2idevWPPnkk/Ts2ROr1cqwYcP49ttvufLKK3nssccICAhg/Pjxpxyekp2dzY033siqVau49dZbeeqpp2jatClPPfUUs2bN8mzncrm4++67effdd2nbti3jx49n+PDhFBUVsWvXLkJDQ3n++ecBuPTSS3nttdd47bXXuPTSSys99tNPP83UqVNp06YNTzzxBN27d+eDDz6ocDjPgQMHePDBBznvvPMYP348QUFBjB8/vsG9n0T9kTGy4py0Z88eAgICKk0EAFq1auXZ9vhxiOVJktvtZu/evUycOBGAyy677KTHVBSFF198kUGDBtG5c+dKe3CzsrIICwvzSXQjIiIAyMzM9PpvefuJ2+bn52O32ysdJ1s+mSo5OfmkcdeVTp060aNHDxYsWMCvv/5Kr1696NKlCxdddJFXD/PpeOeddyrsIf3iiy/o1KmT53eXy+WZvJafn89XX33F1q1b6devH0ajscrHGz58OLNnz+bdd99l+vTpFW6zd+9eAFq2bOnV7nK5KCgo8GoLCQmp9ItO+b5O9t594403OHz4MM8++2ylYzb
"text/plain": [
"<Figure size 700x500 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABKUAAAHpCAYAAABTH4/7AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3XmYXHWZ9//3ObVvvXdnY08gZAhLEMVgFI06jIg+EoZFCaACgrK4K/L4G5ZBQJGRgXEEJQJCAFEIKoLzKAIiIDoOyLDIkrCEdJLuru6uveqsvz9OV5HO0unudNJZPq/ryhW76tQ53zqVNPYn931/Dd/3fURERERERERERLYhc7IXICIiIiIiIiIiux6FUiIiIiIiIiIiss0plBIRERERERERkW1OoZSIiIiIiIiIiGxzCqVERERERERERGSbUyglIiIiIiIiIiLbnEIpERERERERERHZ5hRKiYiIiIiIiIjINqdQSkREREREREREtjmFUiIiIrLTO/PMM/nmN7852cvYqIGBAQ455BAeeeSRyV7KdmfhwoVccMEFja+ffPJJZs+ezZNPPjnmc91zzz3Mnj2bN998cyKXOGkuuOACFi5cOOyx2bNnc911103SikRERMZOoZSIyC5o9uzZo/q17g9+AwMDfPvb3+aoo47iwAMP5B3veAenn346Dz300Abnf/PNN4edZ86cObz3ve/lnHPO4YUXXhjTWm3b5uijj2b27NksWbJkg+c9z+NHP/oRCxcu5MADD+QjH/kI991330bPtXz5ck4//XTmzZvHO97xDr761a/S398/6rXUajVuvvlmjj/+eN72trdx4IEHctRRR3HppZfy6quvNo677rrrmD179ibPXf/B+je/+c1Gn7/00kuZPXv2sMcsy+KWW27hYx/7GIceeiiHHXYYH/7wh/n//r//j+XLlwNj+1zX/4zW//XDH/6wce1TTjll2HMHHXQQH/nIR7j55pvxPG/YOuvnXfezqr/f2bNn8+yzz27wfi+44ALmzZu30Xvx+9//nrPPPpsjjjiCuXPn8o53vIOTTz6ZH//4xxSLxY2+Zn1//etfeeyxxzjzzDM3WNP6n4FlWZx11lnsv//+/PznPx/V+TelHoJs7Fdvb2/juNbWVv75n/+Zf//3f9+i622p9de77p/vvr6+SV2bbD+WLl3KPffcM9nLEBGRnUR4shcgIiLb3ne+851hX//iF7/gscce2+DxmTNnArBixQo++clP0t/fz6JFizjwwAPJ5/P86le/4uyzz+bTn/40X//61ze4zjHHHMN73vMePM9j+fLl3HHHHfzhD3/grrvuYs6cOaNa62233cbq1as3+fz3vvc9fvjDH3LCCSdw4IEH8uCDD/LlL38ZwzD48Ic/3DhuzZo1nHzyyWQyGb74xS9SLpf58Y9/zEsvvcTPfvYzotHoiOvo7+/njDPO4LnnnuN973sfxxxzDMlkkldffZX777+fu+66a6OBy0Q5//zz+cMf/sCHP/xhjj/+eBzHYcWKFTz88MPMmzePmTNnjulzrVarwFuf0fr+4R/+YdjXU6dO5Utf+hIQBJT33XcfV1xxBQMDA3zxi18c9fv4j//4D66//vrNHud5Hv/3//5f7rnnHvbbbz8+8YlPMHXqVEqlEk8//TTXXHMNjzzyCLfccstmz7VkyRLmz5/PnnvuOeJxtm1z/vnn88gjj/Cv//qv/PM///Oo39dIzj//fHbbbbdhjzU1NQ37+uMf/zi33norTzzxBPPnz5+Q645Xfb2WZfHXv/6VO+64g0ceeYT77ruPRCIxqWuTkT3zzDOEQqGteo077riD1tZWFi1atFWvIyIiuwaFUiIiu6D/83/+z7Cv//a3v/HYY49t8DgEP6h//vOfJ5/Ps3TpUg4++ODGc5/85Cf5yle+wo9//GMOPPBAjj766GGv/Yd/+Idh5zz00EP57Gc/yx133MGll1662XVms1m+//3vc8YZZ3Dttddu8PzatWu56aabOPnkk/mXf/kXAI4//ngWL17Md77zHf7pn/6p8QPa9ddfT6VS4Z577mH69OkAHHTQQXzqU59i2bJlnHjiiSOu5Rvf+AYvvPAC1157LUcdddSw577whS/wve99b7PvZ7yeeeYZHnroIb74xS9y9tlnD3vOdV3y+Twwts+13sK0/me0KZlMZthxH//4x/nQhz7Erbfeyvnnnz+qH4TnzJnDQw89xHPPPccBBxww4rE33ngj99xzD5/85Ce54IILMAyj8dxpp51GT08P995772avmc1meeSRR7j44otHPM62bb7whS/w8MMPc+mll3L88cdv9tyj9Z73vIcDDzxwxGNmzpzJfvvtx7JlyyY9lFp3vccffzwtLS3cdNNNPPjggxxzzDHjPq/nedi2TSwWm6il7tRqtRqRSATTHH1jg+6tiIjsaNS+JyIiI/p//+//8dJLL3HmmWcOC6QAQqEQl156KU1NTaOaY/LOd74TYNQzXb773e+y995789GPfnSjz//ud7/Dtm0+8YlPNB4zDIOPf/zjrFmzhqeeemrY+3jve9/bCKQAjjjiCPbaay8eeOCBEdfxt7/9jYcffph//ud/3iCQAohGoxutFJsoK1euBIJQb32hUIjW1tatdu1NicVizJ07l1KpRDabHdVrFi9eTHNz82b/rFQqFX70ox+x77778rWvfW1YIFXX1dXFZz7zmc1e8+GHH8ZxHI444ohNHuM4Dl/60pd48MEHufjiiznhhBM2/2bGqFgs4rruiMccccQRPPTQQ/i+P+HX3xLr/71dsmQJJ510EocffjgHHXQQixYt2mgr6uzZs7n00kv55S9/yYc//GEOPPBAHn300TGdY7T+9re/cfrpp/O2t72Ngw8+mMWLF/PXv/513Odbvnw5n//853nnO9/JQQcdxFFHHbVB8Pz8889zxhlncOihhzJv3jxOO+00nn766Q3OtXLlSs4//3ze8Y53cPDBB3PCCSfw8MMPDzum3k7661//mu9973u8+93v5uCDD260qP7ud7/jmGOO4cADD+SYY47ht7/97UbXvf5MqXor8euvv84FF1zAYYcdxtve9ja+8Y1vUKlUhr327rvv5tRTT2X+/PnMnTuXo48+mttvv33YMQsXLuTll1/mz3/+c6PN85RTTmk8n8/n+da3vsWRRx7J3Llz+eAHP8gPf/jDDdp8f/3rX7No0SLmzZvHoYceykc+8pFRVT2KiMjOR5VSIiIyot///vcAfOxjH9vo85lMhve///0sW7aM119/fcQWqTfeeAOAlpaWzV73mWee4d577+X222/faCgB8MILL5BMJhtthnUHHXRQ4/nDDjuMtWvXks1mmTt37gbnOOigg/jDH/4w4lrq92A0FUVbQz1I+9WvfsWhhx5KODxx//muVCobnX3V1NS02eusWrUKwzA2aEXblHQ6zWmnnca11147YrXUX//6V/L5PJ/+9Ke3uBXpqaeeoqWlhRkzZmz0edd1+dKXvsRvf/tb/uVf/oWTTjppg2Ns26ZQKIzqei0tLRtUtpx66qmUy2UikQgLFizgggsuYK+99trgtQcccAA333wzL7/8Mvvtt9+orrctrP/39ic/+QkLFy7kIx/5CLZt8+tf/5rPf/7z3HDDDbz3ve8d9to//elPPPDAA5x88sm0trY2PoexnGNznnjiCc4880zmzp3Lueeei2EY3HPPPZx22mncfvvtje8Ho/X3v/+dk08+mXA4zIknnsiMGTN44403+P3vf99oVX355Zc5+eSTSaVSnHHGGYTDYX76059yyimncNtttzUC/L6+Pk466SQqlQqnnHIKra2tLFu2jM9+9rNce+21fPCDHxx27f/8z/8kEolw+umnY1kWkUiEP/7xj5x33nnMmjWLL3/5ywwMDPCNb3yDqVOnjvo9feELX2C33XbjS1/6Es8//zw/+9nPaGtr46tf/WrjmDvuuIN9992XhQsXEg6Heeihh7jkkkvwfZ+TTz4ZgAsvvJB//dd/JZlMNqo2Ozo6gOB7yeLFi1m7di0nnXQS06ZN46mnnuLf/u3f6O3
"text/plain": [
"<Figure size 1200x500 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
2026-04-07 20:26:19 +02:00
},
{
"data": {
2026-04-10 11:05:13 +02:00
"image/png": "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
2026-04-07 20:26:19 +02:00
"text/plain": [
2026-04-10 11:05:13 +02:00
"<Figure size 500x300 with 1 Axes>"
2026-04-07 20:26:19 +02:00
]
},
"metadata": {},
"output_type": "display_data"
},
{
2026-04-10 11:05:13 +02:00
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAeoAAAEiCAYAAAA21pHjAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAS89JREFUeJzt3Xtczvf/+PFHXVJSOhDFHMoh1oFymELMmtPsgDkmZxs5bDb7zGlScpjDZshh5FgOOWRDtmGjsZxZhDFyGCGFHKJcV78//K7r61JRVxfXVZ73281t6/1+Xe/383p3dT3fr+PbJDs7OxshhBBCGCVTQwcghBBCiLxJohZCCCGMmCRqIYQQwohJohZCCCGMmCRqIYQQwohJohZCCCGMmCRqIYQQwohJohZCCCGMmCRqIYQQwohJohaiGBs4cCDjxo0zdBi5unXrFvXq1WP37t2GDqVYSkhIoFu3btSrVw9XV1dOnTrFnDlzcHV1NXRoooBKGDqAVym/H9AVK1bw1ltvAU++TH788Ud+//13rl69SqlSpfDw8KBnz568/fbbWq/777//eOeddzQ/m5qaUqFCBdzc3Bg6dCh16tTJd6xZWVl8+OGHnDt3jv/973/0799fa79KpSIiIoLVq1eTkpJCtWrV+PTTT2nfvn2OY507d47Jkydz5MgRzMzMaN68OaNHj8be3j5fsTx69IjVq1ezdetWzp8/T2ZmJhUrVqRJkyYEBgbi7OwMwJw5c5g7dy7x8fG5Hnv//v306tWLH374gTZt2uTYHxoaSlRUFP/8849mW2ZmJqtXryYmJoZLly5prqm3tzd9+vShevXqBfq9VqpUSet39Kwvv/ySTz75BIDAwEAOHDig2Wdubk7VqlXp1KkTvXr1wtT0/+5z1b/7p39X6vcLsGHDBtzd3bXONWrUKH799VeOHj2aI47ff/+d6OhoEhISSE9Px9LSkpo1a/LOO+/QpUsXrKysXvh+Dx8+zN69e9m2bZtmW16/g8zMTIYNG8bu3bsJCwvj448/fuHx87Jx40ZGjx6d6749e/bg4OAAgJ2dHR9//DE//PADzZs31/l8IqesrCw+//xzSpYsyejRo7GwsKBixYqGDitXUVFRlCpVio4dOxo6FKP1WiXqadOmaf38008/sXfv3hzbq1evDsD58+fp06cPaWlpdOzYEQ8PD9LT09m8eTODBg2iX79+fP311znO0759e/z8/FCpVJw7d47Vq1cTFxdHdHR0vpN1ZGQkycnJee7//vvv+fHHH+nSpQseHh7s3LmTL7/8EhMTE9577z1NuWvXrhEQEIC1tTUjRozgwYMHLFmyhDNnzrBu3TpKliz53DjS0tIYMGAAiYmJvP3227Rv3x5LS0uSkpKIjY0lOjqaEydO5Os96WL48OHExcXx3nvv0blzZx4/fsz58+fZtWsXXl5eVK9evUC/14cPHwL/9zt61ptvvqn1s6OjI1988QXw5KZty5YtTJkyhVu3bjFixIh8v4+5c+eyYMGCF5ZTqVSMHTuWjRs3UqtWLXr06IGjoyP379/n2LFjzJo1i927d7N8+fIXHisiIgIfHx+qVq363HJZWVkMHz6c3bt3M3HixEIl6acNHz6cN954Q2tbmTJltH7u3r07K1euJD4+Hh8fH72cV8ClS5e4cuUKYWFhdO7c2dDhPNfq1auxs7OTRP0cr1Wi/vDDD7V+/vvvv9m7d2+O7fDky+uzzz4jPT2dqKgo6tatq9nXp08fRo4cyZIlS/Dw8KBdu3Zar33zzTe1junt7c3gwYNZvXo1oaGhL4wzNTWV8PBwBgwYwOzZs3Psv379OkuXLiUgIIDx48cD0LlzZ3r27Mm0adNo06YNCoUCgAULFpCRkcHGjRs1d9Senp707duXmJgYunbt+txYRo8ezalTp5g9ezatW7fW2vf555/z/fffv/D96CohIYE//viDESNGMGjQIK19SqWS9PR0oGC/1//++w/I+TvKi7W1tVa57t2707ZtW1auXMnw4cM11/l56tSpwx9//EFiYiJubm7PLbt48WI2btxInz59GDVqFCYmJpp9vXv35saNG2zatOmF50xNTWX37t1MmDDhueXUNa9du3YRGhqq1y91Pz8/PDw8nlumevXq1KpVi5iYmCKZqFUqFVlZWZibmxs6FC1paWnAk8+vKPqkjzoPv/32G2fOnGHgwIFaSRpAoVAQGhpKmTJlmDNnzguP1bhxY+D/ksSLzJgxA2dnZz744INc9+/YsYOsrCx69Oih2WZiYkL37t25du2aVlPqb7/9RosWLbSavXx9falWrZpWk2hu/v77b3bt2sXHH3+cI0kDlCxZMtcWBX25fPky8ORG51kKhQI7O7uXdu68mJub4+7uzv3790lNTc3Xa3r27ImNjc0LPysZGRksWrSImjVr8r///U8rSauVL19e0zT/PLt27eLx48f4+vrmWebx48d88cUX7Ny5kwkTJtClS5cXv5kCunfvHkql8rllfH19+eOPPzDkg/z279+vaTXz9/dnzZo1ufbnurq6Ehoays8//8x7772Hh4cHf/75JwAnT55kwIABeHt74+XlRe/evTl27JjW67Oyspg7dy6tWrXCw8ODt956i+7du7N3715NmZSUFEaPHo2fnx/u7u40bdqUwYMH5/v7Y9SoUfTs2ROAzz77DFdXVwIDA/Ms//jxY8LDw/H398fd3Z2WLVvy3XffkZmZqSkzZcoU3nrrLa3f0cSJE3F1dWXFihWabTdv3sTV1ZVVq1blK9aWLVty9uxZDhw4gKura45YL1++zPDhw2nUqBF169alS5cu7Nq1K1/HftqOHTv45JNPaNq0Ke7u7vj7+xMeHp7js9myZUtGjRqV4/WBgYFace3fvx9XV1diY2OZO3cuzZo1w8vLi+HDh3P37l0yMzOZNGkSPj4+eHl5MXr0aK3rWVCvVY26IH7//XcAPvroo1z3W1tb88477xATE8PFixef27x46dIlAGxtbV943oSEBDZt2sSqVaty/aIGOHXqFJaWlpomejVPT0/N/gYNGnD9+nVSU1Nz9I2qy8bFxT03FvU1yE/N82VQ31xs3rwZb29vSpTQ38c1IyNDU+t4WpkyZV54nitXrmBiYpKjGTcvVlZW9O7dm9mzZz+3Vn348GHS09Pp169fvmrqz3P06FFsbW2pVKlSrvuVSiVffPEF27dvZ/z48XTr1i1HmaysLO7evZuv89na2mr12QP06tWLBw8eYGZmRtOmTRk1ahTVqlXL8Vo3NzeWLVvG2bNnqVWrVr7Op0/qBOvg4MCwYcNQqVSEh4fnOYZj3759bNu2jYCAAOzs7KhUqRJnz54lICCA0qVLM2DAAEqUKMHatWsJDAwkMjJSc7M/d+5cFi5cSOfOnfH09OTevXucOHGCxMREmjRpAsCwYcP4999/6dmzJ5UqVSItLY29e/eSnJycoyshN127dqVChQosWLCAwMBAPDw8KFeuXJ7lx40bR0xMDK1bt6Zv374kJCSwcOFCzp07R3h4OAANGjTI8Ts6dOgQpqamHDp0SDMW49ChQwA0bNgwX9d+zJgxTJw4EUtLS02rmTrWmzdv0q1bNzIyMggMDMTOzo6YmBgGDx7M7Nmzeffdd/N1DoCYmBgsLS3p27cvlpaW7Nu3j9mzZ3Pv3r1CVTZ+/PFHLCws+OSTT7h48SKRkZGUKFECExMT0tPTGTp0KH///TcbN26kUqVKDB06VKfzSKLOw7lz57C2ts7ziw6gdu3amrJPJ2p1ElCpVJw/f54pU6YA5DqA6mnZ2dlMnDiRdu3a4eXllecddEpKCmXLls2RyNWDdG7cuKH1X/X2Z8vevn2bzMzMPPupz507B2CQL0+AevXq0ahRI6Kjo/n9999p3Lgx3t7evP3224UeGDNnzpxca7hr166lXr16mp+VSqUmod++fZv169dz4sQJWrRogYWFRb7P16tXL5YvX87cuXOZP39+rmXOnz8PQM2aNbW2K5VK7ty5o7XNzs4uzxs59bGe99mdOXMmV65cYfz48VotM087cuSI5gv4RXbu3KlJIhY
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
2026-04-08 17:41:37 +02:00
{
"data": {
2026-04-10 11:05:13 +02:00
"image/png": "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
2026-04-08 17:41:37 +02:00
"text/plain": [
2026-04-10 11:05:13 +02:00
"<Figure size 500x300 with 1 Axes>"
2026-04-08 17:41:37 +02:00
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
2026-04-10 11:05:13 +02:00
"image/png": "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
2026-04-08 17:41:37 +02:00
"text/plain": [
2026-04-10 11:05:13 +02:00
"<Figure size 500x300 with 1 Axes>"
2026-04-08 17:41:37 +02:00
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
2026-04-10 11:05:13 +02:00
"image/png": "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
2026-04-08 17:41:37 +02:00
"text/plain": [
2026-04-10 11:05:13 +02:00
"<Figure size 500x300 with 1 Axes>"
2026-04-08 17:41:37 +02:00
]
},
"metadata": {},
"output_type": "display_data"
2026-04-10 11:05:13 +02:00
},
2026-04-08 17:41:37 +02:00
{
2026-04-10 11:05:13 +02:00
"data": {
"image/png": "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
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAfMAAAEiCAYAAADtbTvBAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAATSdJREFUeJzt3XtcTPn/B/BXMypSSknFulR02S5bxCqXsFisveDrtl3c78Ji3Wkj2+5ifUVyC1HsusRutHaXXbIr2ciWXLJp3SUlpKhm+v3Rb+ZrTJdpmpqmXs/Hw2N3zjlzznummXmf8/m8P5+jVVxcXAwiIiLSWAJ1B0BERERVw2RORESk4ZjMiYiINByTORERkYZjMiciItJwTOZEREQajsmciIhIwzGZExERaTgmcyIiIg3HZE5Uj0ycOBFLly5VdxilevLkCVxcXHD69Gl1h6IR/v33X4wbNw4dO3aEra0tTpw4gaioKNja2uLu3bvqDk/GkSNH0L9/fzg4OMDNza3Gj9+7d28sXLiw2p+jTg3UHYA62draKrTd7t278e677wIo+cHZunUrfvvtN9y/fx+NGjWCk5MTvL290atXL5nn3b17F++99570sUAggJmZGRwcHDBjxgzY29srHGthYSE+/vhjpKWlYf78+Rg/frzMerFYjLCwMOzbtw+ZmZlo27YtJk+ejEGDBsntKy0tDV9++SUuXrwIbW1teHp6YtGiRTA2NlYollevXmHfvn04duwYbt68iYKCArRo0QJdu3aFj48PLC0tAQAbNmzAxo0bERcXV+q+4+Pj4evri/Xr16N///5y61esWIHIyEhcv35duqygoAD79u3D4cOHcfv2bel72qFDB4wZMwbW1taV+ru2bNlS5m/0prlz52LSpEkAAB8fH5w/f166TldXF23atMHQoUPh6+sLgeB/58aSv/3rfyvJ6wWAQ4cOwdHRUeZYCxcuxM8//4zExES5OH777Tfs378fSUlJePbsGfT09NC+fXu89957GD58OPT19St8vRcuXMCff/6Jn376SbqsrL9BQUEB/Pz8cPr0aQQGBuI///lPhfsvS1RUFBYtWlTquj/++AOmpqYAgKZNm+I///kP1q9fD09PT6WPV18sXLgQd+/exWeffQYDAwM4Ojri7Nmz6g5LTlpaGhYtWoTu3btj0qRJaNiwobpDqnViYmIQ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
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 500x300 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 600x400 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAArIAAAHqCAYAAAD4TK2HAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsnXd4XNWZ/z/Tu6RR790qtuTeCy4YjE2vCYQWSIAEwqawG5LdbDbJ7pKw7Oa3QDZsEtomQOhgiG3ABhtww73IRbJ679P7zP39MdLYY0m2JBdJcD7Po0fPnNvO3Hvn3u95z1tkkiRJCAQCgUAgEAgEEwz5WHdAIBAIBAKBQCAYDULICgQCgUAgEAgmJELICgQCgUAgEAgmJELICgQCgUAgEAgmJELICgQCgUAgEAgmJELICgQCgUAgEAgmJELICgQCgUAgEAgmJELICgQCgUAgEAgmJELICgQCgUAgEAgmJELICgQCwXnk29/+Nv/0T/801t0YlN7eXqZPn86WLVvGuitfKoqLi3nqqafGuhsCwVcSIWQFF5Ti4uJh/e3cuTOyTW9vL7/5zW9YtWoV5eXlzJ07l3vvvZdPPvlkwP6bmpqi9lNaWsqyZct48MEHOXr06Ij66vf7WbNmDcXFxTz77LMDlodCIf74xz+yYsUKysvLufrqq3n//fcH3Vd1dTX33nsvM2bMYO7cufz93/89PT09w+6L1+vlhRde4Oabb2bWrFmUl5ezatUqfvnLX1JbWxtZ76mnnqK4uHjIfe/cuZPi4mI2bNgw6PJf/vKXFBcXR7X5fD5efPFFrrvuOmbOnMns2bO58sor+dnPfkZ1dTUwsut6+jU6/e8Pf/hD5Nh33HFH1LKpU6dy9dVX88ILLxAKhaL62b/fU69V//ctLi7m8OHDA77vo48+yowZMwY9Fx9//DEPPPAACxcupKysjLlz5/KNb3yD5557DofDMeg2p7Nnzx62bt3Kt7/97QF9Ov0a+Hw+7r//fkpKSnjjjTeGtf+heOutt4Y8v52dnZH1zGYzN910E//93/99Tsc7V/r7e+jQoRFv63a7eeqpp6KeGeONvXv38tRTT2Gz2ca6KyNixYoVQ95HXq93rLsnEAyKcqw7IPhy8/jjj0d9fvfdd9m6deuA9oKCAgBqamq4++676enp4YYbbqC8vBybzcZ7773HAw88wD333MOPf/zjAce56qqruOSSSwiFQlRXV/PKK6/w6aef8tprr1FaWjqsvv7lL3+htbV1yOW//e1v+cMf/sAtt9xCeXk5mzZt4kc/+hEymYwrr7wysl5bWxvf+MY3MJlM/OAHP8DlcvHcc89RWVnJ66+/jlqtPmM/enp6+Na3vkVFRQXLly/nqquuQq/XU1tby7p163jttdcGFWnni4cffphPP/2UK6+8kptvvplAIEBNTQ2bN29mxowZFBQUjOi6ejwe4OQ1Op3JkydHfU5NTeWHP/whEB7UvP/++zz22GP09vbygx/8YNjf4+mnn+aZZ54563qhUIh//Md/5K233qKoqIjbbruN1NRUnE4n+/fv5//9v//Hli1bePHFF8+6r2effZYFCxaQk5NzxvX8fj8PP/wwW7Zs4Ve/+hU33XTTsL/XmXj44YfJzMyMaouJiYn6fOutt/LnP/+Z7du3s2DBgvNy3IuJ2+3m6aef5qGHHmLevHlj3R0ADh48iEKhiHzet28fTz/9NNdff/2A8z/eKS0t5Zvf/OaAdpVKNQa9EQjOjhCyggvKtddeG/X5wIEDbN26dUA7hF/uf/d3f4fNZuOll15i2rRpkWV33303jzzyCM899xzl5eWsWbMmatvJkydH7XPmzJl85zvf4ZVXXuGXv/zlWfvZ3d3N7373O771rW/x5JNPDlje3t7O888/zze+8Q3++Z//GYCbb76Z22+/nccff5wrrrgi8iJ75plncLvdvPXWW6SnpwMwdepUvvnNb/L222/zta997Yx9+clPfsLRo0d58sknWbVqVdSy73//+/z2t7896/cZLQcPHuSTTz7hBz/4AQ888EDUsmAwGLEwjeS6NjU1AQOv0VCYTKao9W699VZWr17Nn//8Zx5++OEowTAUpaWlfPLJJ1RUVDBlypQzrvunP/2Jt956i7vvvptHH30UmUwWWXbXXXfR0dHBO++8c9Zjdnd3s2XLFv7lX/7ljOv5/X6+//3vs3nzZn75y19y8803n3Xfw+WSSy6hvLz8jOsUFBRQVFTE22+/PSGF7HhEo9GMdRfOGykpKcP6nfbjdrvR6XQXsEcCwZkRrgWCccOHH35IZWUl3/72t6NELIBCoeCXv/wlMTExw/JFmz9/PnBSRJ2NJ554gry8PK655ppBl2/cuBG/389tt90WaZPJZNx66620tbWxb9++qO+xbNmyiIgFWLhwIbm5uaxfv/6M/Thw4ACbN2/mpptuGiBiAdRq9aAW6fNFY2MjEB4InI5CocBsNl+wYw+FRqOhrKwMp9NJd3f3sLa5/fbbiY2NPeu94na7+eMf/8ikSZP4h3/4hygR209ycjL33XffWY+5efNmAoEACxcuHHKdQCDAD3/4QzZt2sS//Mu/cMstt5z9y4wQh8NBMBg84zoLFy7kk08+QZKk83780dLv9tHe3s53v/tdZsyYwfz58/nNb34T+T5NTU0R8f30009Hpr1Pvc7V1dU8/PDDzJ07l/Lycm644QY2bdoUdax+14Y9e/bw2GOPMX/+fKZPn86DDz44wE3n0KFD3HvvvcybN4+pU6eyYsUKfvKTn0Stc2ofnnrqqcjMxKWXXhrpY1NTE7fffvuQz5hVq1Zx7733Dnl+7r//fi699NJBl33ta1/jhhtuiHzeunUrt956K7Nnz2bGjBmsWrWK//qv/xpy38Pljjvu4KqrruLw4cN84xvfYNq0aZH9+nw+nnzySS677DLKyspYunQpjz/+OD6fL2ofPp+Pf//3f2f+/PnMmDGDBx54gLa2tgHX8dFHH2XFihUD+tDvTnU67777LjfccANTp05l7ty5/OAHPxgww9bf/xMnTnDHHXcwbdo0lixZwh//+McB+/N6vTz11FMRF7fFixfz0EMP0dDQgCRJrFixgu985zuDbjdr1qyIwUNw4RFCVjBu+PjjjwG47rrrBl1uMpm49NJLqampob6+/oz7amhoACAuLu6sxz148CDvvPMOP/3pTwcVMgBHjx5Fr9dHXCD6mTp1amQ5hC233d3dlJWVDdjH1KlTz+q3238ORmIROZ/0i+/33nuPQCBwXvftdrvp6ekZ8Dec4zQ3NyOTyYY9TWs0GrnrrrsiVtmh2LNnDzabjSuvvHJYlt4zsW/fPuLi4sjIyBh0eTAY5Ic//CEfffQR//zP/8zXv/71Aev4/f5Bz9Fgf6f7DAPceeedzJo1i2nTpvHAAw9QV1c3aF+mTJmCzWajqqrqnL7z+SYYDHLvvfcSFxfHP/zDPzB37lyee+45Xn31VQDi4+MjFu/LLruMxx9/nMcff5zLLrsMgKqqKr72ta9RXV3Nt7/9bR599FH0ej0PPvggH3300YDj/eu//ivHjh3joYce4tZbb+WTTz6JmsHp7u7m3nvvpampifvuu4+f/exnXH311Rw4cGDI73DZZZdx1VVXAeHZlf4+xsfHc+2113L8+HEqKyujtjl48CB1dXVcffXVQ+539erVNDU1cfDgwaj25uZm9u/fH3Fvqqqq4v7778fn8/Hwww/z4x//mBUrVrB3794h930qgUBgwL3mdrsjyy0WC9/+9rcpLS3lpz/9KfPmzSMUCvGd73yH5557juXLl/Ozn/2MlStX8uKLL/L9738/av//+I//yIsvvsiiRYt45JFHUKlUwxoononf//73/PjHPyYnJ4dHH32UO++8k+3bt/ONb3xjgJ+y1WrlW9/6FiUlJfz4xz8mPz+fJ554IioAMhgMcv/99/P0008zZcqUyD7tdjuVlZXIZDKuvvpqPvvsMywWS9T+P/74YxwOx5ADFsH5R7gWCMYN1dXVmEymIYUAQElJSWTdU/0Q+0VSKBSipqaGxx57DIArrrjijMe
"text/plain": [
"<Figure size 700x500 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
2026-04-08 17:41:37 +02:00
}
],
"source": [
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"import numpy as np\n",
"from sklearn.decomposition import PCA\n",
"from sklearn.metrics import silhouette_samples\n",
"from pandas.plotting import parallel_coordinates\n",
"\n",
"# ------------------------------------------------------------\n",
"# GENERIC FUNCTION\n",
"# ------------------------------------------------------------\n",
"def visualize_clustering(df, X_scaled, feature_names, cluster_col, title_prefix):\n",
"\n",
" print(f\"\\n{'='*60}\")\n",
" print(f\"{title_prefix}\")\n",
" print(f\"{'='*60}\")\n",
"\n",
" # ========================================================\n",
" # 1. PCA projection (2D)\n",
" # ========================================================\n",
" pca = PCA(n_components=2)\n",
" X_pca = pca.fit_transform(X_scaled)\n",
"\n",
" plt.figure(figsize=(7,5))\n",
" sns.scatterplot(\n",
" x=X_pca[:,0], y=X_pca[:,1],\n",
" hue=df[cluster_col],\n",
" palette=\"tab10\",\n",
" s=20\n",
" )\n",
" plt.title(f\"{title_prefix} — PCA projection\")\n",
" plt.xlabel(\"PC1\")\n",
" plt.ylabel(\"PC2\")\n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
" # ========================================================\n",
" # 3. Parallel coordinates\n",
" # ========================================================\n",
" df_plot = df[feature_names + [cluster_col]].copy()\n",
" df_plot[cluster_col] = df_plot[cluster_col].astype(str)\n",
"\n",
" plt.figure(figsize=(12,5))\n",
" parallel_coordinates(df_plot, cluster_col, colormap=\"tab10\", alpha=0.2)\n",
" plt.xticks(rotation=45)\n",
" plt.title(f\"{title_prefix} — Parallel coordinates\")\n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
" # ========================================================\n",
" # 4. Boxplots (distribution per cluster)\n",
" # ========================================================\n",
" for col in feature_names:\n",
" plt.figure(figsize=(5,3))\n",
" sns.boxplot(x=cluster_col, y=col, data=df)\n",
" plt.title(f\"{title_prefix} — {col}\")\n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
" # ========================================================\n",
" # 5. Silhouette distribution\n",
" # ========================================================\n",
" sil_vals = silhouette_samples(X_scaled, df[cluster_col])\n",
"\n",
" plt.figure(figsize=(6,4))\n",
" sns.histplot(sil_vals, bins=30)\n",
" plt.title(f\"{title_prefix} — Silhouette distribution\")\n",
" plt.xlabel(\"Silhouette value\")\n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
" # ========================================================\n",
" # 6. Business 2D plot (intensity vs frequency)\n",
" # ========================================================\n",
" if \"flow_freq\" in df.columns and \"gross_flow_to_aum\" in df.columns:\n",
"\n",
" plt.figure(figsize=(7,5))\n",
" sns.scatterplot(\n",
" data=df,\n",
" x=\"flow_freq\",\n",
" y=\"gross_flow_to_aum\",\n",
" hue=cluster_col,\n",
" size=\"aum_qty_mean\" if \"aum_qty_mean\" in df.columns else None,\n",
" sizes=(20,200),\n",
" alpha=0.7\n",
" )\n",
" plt.yscale(\"log\")\n",
" plt.title(f\"{title_prefix} — Intensity vs Frequency\")\n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
"\n",
"# ============================================================\n",
"# RUN FOR GLOBAL\n",
"# ============================================================\n",
"visualize_clustering(\n",
" df = dfc,\n",
" X_scaled = X_global_scaled,\n",
" feature_names= all_features_global,\n",
" cluster_col = \"cluster_k4\",\n",
" title_prefix = \"GLOBAL CLUSTERING (K=4)\"\n",
")\n",
"\n",
"# ============================================================\n",
"# RUN FOR TOP 400\n",
"# ============================================================\n",
"visualize_clustering(\n",
" df = dfc_top400,\n",
" X_scaled = X_top400_scaled,\n",
" feature_names= all_features_top400,\n",
" cluster_col = \"cluster_k5\",\n",
" title_prefix = \"TOP 400 CLUSTERING (K=5)\"\n",
")"
]
2026-04-07 20:26:19 +02:00
},
{
"cell_type": "code",
2026-04-10 11:05:13 +02:00
"execution_count": 70,
2026-04-07 20:26:19 +02:00
"id": "fc913550-7c1d-44ee-aa36-e7b027b98e2e",
"metadata": {},
2026-04-08 17:41:37 +02:00
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Top discriminant features:\n",
"months_since_last_tx 2953.000000\n",
"avg_holding_months_per_isin 519.522790\n",
"n_isin_total 24.250000\n",
"gross_flow_to_aum 10.461616\n",
"log_aum_qty_mean 5.029849\n",
"dtype: float64\n"
]
},
{
"data": {
2026-04-10 11:05:13 +02:00
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAkUAAAHdCAYAAAATow1yAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsnXd8FHX+/5+72U3ZbDa997ZJKFIUFEFBwd49G6dwp3iKeiCenqinX8Fy2PjZT2PDUyxYQMUCigoSRERUahLSe7LZJFtSt83vj9yOWdI2vTDPx2MfkNnPzHxmd3bmNe8qEwRBQEJCQkJCQkLiOEc+0hOQkJCQkJCQkBgNSKJIQkJCQkJCQgJJFElISEhISEhIAJIokpCQkJCQkJAAJFEkISEhISEhIQFIokhCQkJCQkJCApBEkYSEhISEhIQEIIkiCQkJCQkJCQlAEkUSEhISEhISEoAkiiSOc55//nnS0tKGdZ9/+9vfuP/++4d1n/2lvLyctLQ0Nm7cKC4bic+sq3kMlD179pCWlsaePXsGbZvDzWuvvcb8+fPJyMjgkksuGenpjCgNDQ1MnTqVHTt2jPRUJMYwipGewFigrKyMdevWsWvXLqqrqwGIjo7m5JNP5uqrryY9PV0c+/zzz/PCCy+we/dugoKCut2m1Wrlvffe49NPP6WwsBCApKQkLrnkEhYuXIhSqXQZf+aZZ1JRUSH+7enpSWRkJPPnz+fmm28mICCgy/088cQTvP7665x33nk888wznd4vLy9n/vz53H333SxZssTdj2RIWbRoET///LP4t5eXF/Hx8fzpT39i8eLFyOVjV8vv27ePXbt28dVXXwGdv9fuWLNmDZdffvlQT09iBNmxYwcHDhxg2bJlbo3PysriySef5OKLL2bZsmUEBgaOinmNFIGBgVxxxRU8++yzzJ07d6SnIzFGkURRL3z//ffccccdeHh4cNFFF5Geno5cLqewsJCvv/6a9957j2+//Zbo6Gi3t9nc3MzNN9/Mzz//zBlnnMHll1+OTCZj586dPProo3zzzTdkZmaiUqlc1svIyOD6668HwGKxcOjQId566y327t3LRx991Gk/giDwxRdfEB0dzffff09jYyNqtXpgH8gwERERwT/+8Q+g/Qnw888/Z82aNTQ0NHDHHXeM8Oz6z+uvv86sWbOIj48H4L777qOpqUl8/4cffuDzzz/n3nvvdbnJTZ8+fdjn2h233HILN91007DuMzo6mgMHDqBQDN4la8aMGRw4cKDTA8hIsWPHDt555x23xcdPP/2EXC7n0UcfxdPTc9TMayRZuHAhb7/9Nrt372bWrFkjPR2JMYgkinqgtLSUf/zjH0RFRfHmm28SFhbm8v5dd93Fu+++22fLxWOPPcbPP//MAw88wHXXXScu//Of/8w777zDQw89xOOPP87q1atd1gsPD3cxkV955ZWoVCreeOMNiouLSUhIcBm/Z88eqqur+e9//8uNN97IN998w2WXXdanuY4Ufn5+Lse6cOFCzjvvPN5++22WL1+Oh4fHCM6uawRBoK2tDW9v7y7fr6urY8eOHaxatUpctmDBApcxer2ezz//nAULFhATEzOU0+03CoViUMVJT9hsNhwOB56ennh5eQ3qtuVy+aBvczipq6vD29t7SAXRUNLc3NzpwW+gJCcno9Vq2bRp05CIIofDgcViGfTtSgwdSqWyT/cLSRT1wGuvvUZzczNr1qzpJIig/eawePHiPm2zurqajz76iFNOOcVFEDm59tpr2bp1Kx999BG33HILERERPW4vNDQUoMsvffPmzaSkpHDKKacwa9YsNm/ePGZE0bF4eXkxadIktm7dSl1dnfh95OTk8Oabb7J37150Oh0ajYbTTz+du+++u5M74ZdffmHNmjUcPXqU8PBwbrzxxi739fHHH/Ppp5+Sl5eH2WwmLi6O6667jj//+c8u484880xSU1O57rrrePrpp8nLy+POO+/kr3/9a5fb3b59OzabjVNPPbVPx26z2cjMzGTTpk1UV1cTFhbGhRdeyN///neXG6JzPosWLeLJJ5+ksLCQ2NhYVqxYwdlnn93rfkwmE//+97/55ptvkMlkzJ8/v8tjcbqIc3NzxWW7du3ihRdeIC8vD7vdTlhYGOecc45o7QNoa2vjlVde4fPPP6eyshJ/f3+mTp3K3XffTVxcnIsr18PDg/Xr11NRUcHGjRvx8/Nj/vz5Lm7Ee+65h61bt/LFF1+wevVqfv75Z9RqNUuXLuXaa68lNzeXRx99lAMHDhAYGMg//vEPLrroInE+e/bsYfHixbz11lucfPLJQLvrtqGhgWeeeYbVq1dz4MABNBoNixcv5m9/+5u4rsVi4aWXXmLHjh2UlJRgt9uZMGECy5cv55RTThHHdTwmtVrNq6++SnV1NWlpaTz44IOccMIJ4rFs2rQJwCVeq+Nn3JGOY5z/7/jZfPrpp/z3v/8lPz8fb29vZs+ezd13301kZKS43i+//MJbb73FgQMH0Ov1BAcHi9+ZU9j3NK+uPr+Ox9zVd/Xpp5/y8MMP88svvzBr1iz+85//4HA4eOutt/jwww8pLS3Fz8+PBQsWcOedd+Lv7y9u9+DBgzzzzDMcOnSIlpYWQkJCOPnkk1mzZo3LZ3PqqaeyceNGBEFAJpN1+fn1B4vFQlFREQ6HY9C2KTE8BAQEEBER4db5IImiHvj++++Jj49nypQpg7bNH374AbvdzqWXXtrtmEsvvZQ9e/awc+dOrrzySnG5zWajvr4eaP+BHjlyhHXr1jFjxgxiY2NdtmGxWPj6669Fd9sFF1zAfffdR21trSikxhoVFRXIZDI0Go247Mcff6SsrIzLL7+c0NBQ8vLy+OCDD8jPz+eDDz4QfwS5ubksWbKEoKAgli1bhs1m4/nnnyc4OLjTft577z1SU1M588wzUSgUfP/996xevRpBELj22mtdxhYVFXHnnXdy9dVXc9VVV5GYmNjt/H/77TcCAgL65GoFuP/++9m0aRPnnHMO119/PQcOHCAzM5OCggJefPFFl7HFxcXccccdXHPNNVx22WV8/PHH3H777bz22mvMnj27230IgsCtt97Kvn37uOaaa0hOTuabb75h5cqVvc4vLy+Pm2++mbS0NJYvX46npyclJSX8+uuv4hi73c7NN9/M7t27ueCCC1i8eDFNTU3s2rWLo0ePEhcXJ47duHEjbW1tXHXVVXh6euLv79/tjchut/O3v/2Nk046ibvuuovNmzfz0EMP4ePjw9NPP81FF13E2Wefzfvvv8/KlSuZOnVqp9/KsRiNRm688UbOOusszjvvPLZu3cpTTz2FVqsVY1UaGxv58MMPufDCC7nyyitpamrio48+4sYbb+TDDz8kIyPDZZuff/45TU1NXH311chkMl577TWWLVvGtm3bUCqVXH311eh0Onbt2sUTTzzR62f+xBNP8MEHH3DgwAEeeeQR4A8X60svvcSzzz7LeeedxxVXXEF9fT3r16/n2muv5ZNPPhF/P1u2bKG1tZWFCxcSEBDAgQMHWL9+PdXV1Tz33HMAfZ5XT9hsNpYsWcKJJ57IypUrReH1f//3f2zatInLL7+cRYsWUV5ezjvvvMORI0d47733UCqV1NXVsWTJEgIDA7npppvQaDSUl5fzzTffdNrPxIkTefPNN8nLy0Or1Q5ozk4EQaCqqgoPDw9iY2PHdFzj8YQgCDQ3N6PT6QBcHgp6WkmiC8xms6DVaoVbb72103tGo1Goq6sTXy0tLeJ7zz33nKDVaoW6urout/voo48KWq1WOHLkSLf7Pnz4sKDVaoU1a9aIy8444wxBq9V2el1zzTVCfX19p21s2bJF0Gq1QnFxsXg8kydPFtatW+cyrqysTNBqtcJrr73W4+cxnFx33XXCueeeK36+BQUFwuOPPy5otVrhpptuchnb8bN38vnnnwtarVbYu3evuOzWW28VJk+eLFRUVIjL8vPzhYyMDEGr1fa6zRtuuEGYP3++yzLnd/LDDz+4dVwLFy4ULrvssh7HvPbaa4JWqxXKysoEQRCE7OxsQavVCv/6179
2026-04-08 17:41:37 +02:00
"text/plain": [
"<Figure size 600x600 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>months_since_last_tx</th>\n",
" <th>avg_holding_months_per_isin</th>\n",
" <th>n_isin_total</th>\n",
" <th>gross_flow_to_aum</th>\n",
" <th>log_aum_qty_mean</th>\n",
" </tr>\n",
" <tr>\n",
" <th>cluster_k4</th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
2026-04-10 11:05:13 +02:00
" <th>1</th>\n",
2026-04-08 17:41:37 +02:00
" <td>27.0</td>\n",
" <td>60.000000</td>\n",
" <td>3.0</td>\n",
" <td>1.159420</td>\n",
" <td>5.166510</td>\n",
" </tr>\n",
" <tr>\n",
2026-04-10 11:05:13 +02:00
" <th>2</th>\n",
2026-04-08 17:41:37 +02:00
" <td>127.0</td>\n",
" <td>12.000000</td>\n",
" <td>3.0</td>\n",
" <td>1.476151</td>\n",
" <td>3.407548</td>\n",
" </tr>\n",
" <tr>\n",
2026-04-10 11:05:13 +02:00
" <th>3</th>\n",
2026-04-08 17:41:37 +02:00
" <td>3.0</td>\n",
" <td>28.896552</td>\n",
" <td>12.0</td>\n",
" <td>5.351092</td>\n",
" <td>8.762920</td>\n",
" </tr>\n",
" <tr>\n",
2026-04-10 11:05:13 +02:00
" <th>4</th>\n",
2026-04-08 17:41:37 +02:00
" <td>69.0</td>\n",
" <td>11.333333</td>\n",
" <td>1.0</td>\n",
" <td>7.889030</td>\n",
" <td>5.279875</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" months_since_last_tx avg_holding_months_per_isin n_isin_total \\\n",
"cluster_k4 \n",
2026-04-10 11:05:13 +02:00
"1 27.0 60.000000 3.0 \n",
"2 127.0 12.000000 3.0 \n",
"3 3.0 28.896552 12.0 \n",
"4 69.0 11.333333 1.0 \n",
2026-04-08 17:41:37 +02:00
"\n",
" gross_flow_to_aum log_aum_qty_mean \n",
"cluster_k4 \n",
2026-04-10 11:05:13 +02:00
"1 1.159420 5.166510 \n",
"2 1.476151 3.407548 \n",
"3 5.351092 8.762920 \n",
"4 7.889030 5.279875 "
2026-04-08 17:41:37 +02:00
]
},
2026-04-10 11:05:13 +02:00
"execution_count": 70,
2026-04-08 17:41:37 +02:00
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def select_top_features(df, feature_names, cluster_col, top_n=5):\n",
" # Select most discriminant features based on variance of cluster medians\n",
" \n",
" prof = df.groupby(cluster_col)[feature_names].median()\n",
" \n",
" # variance entre clusters\n",
" var_between = prof.var(axis=0).sort_values(ascending=False)\n",
" \n",
" top_features = var_between.head(top_n).index.tolist()\n",
" \n",
" print(\"\\nTop discriminant features:\")\n",
" print(var_between.head(top_n))\n",
" \n",
" return top_features\n",
" \n",
" prof = df.groupby(cluster_col)[feature_names].median()\n",
"\n",
"def plot_radar(df, feature_names, cluster_col, title):\n",
" \n",
" prof = df.groupby(cluster_col)[feature_names].median()\n",
" \n",
" # NORMALISATION (clé !!!)\n",
" prof_norm = (prof - prof.min()) / (prof.max() - prof.min() + 1e-9)\n",
" \n",
" labels = prof_norm.columns\n",
" num_vars = len(labels)\n",
" \n",
" angles = np.linspace(0, 2*np.pi, num_vars, endpoint=False).tolist()\n",
" angles += angles[:1]\n",
" \n",
" fig, ax = plt.subplots(figsize=(6,6), subplot_kw=dict(polar=True))\n",
" \n",
2026-04-10 11:05:13 +02:00
" for cluster_label, row in prof_norm.iterrows():\n",
" values = row.values.tolist()\n",
2026-04-08 17:41:37 +02:00
" values += values[:1]\n",
2026-04-10 11:05:13 +02:00
" ax.plot(angles, values, label=f\"Cluster {cluster_label}\")\n",
2026-04-08 17:41:37 +02:00
" ax.fill(angles, values, alpha=0.1)\n",
" \n",
" ax.set_xticks(angles[:-1])\n",
" ax.set_xticklabels(labels)\n",
" \n",
" plt.title(title)\n",
" plt.legend(loc=\"upper right\", bbox_to_anchor=(1.3,1.1))\n",
" plt.tight_layout()\n",
" plt.show()\n",
" \n",
" return prof\n",
"\n",
"top_features_global = select_top_features(\n",
" dfc,\n",
" all_features_global,\n",
" \"cluster_k4\",\n",
" top_n=5\n",
")\n",
"\n",
"plot_radar(\n",
" dfc,\n",
" top_features_global,\n",
" \"cluster_k4\",\n",
" title=\"GLOBAL — Radar (Top discriminant features)\"\n",
")"
]
},
{
"cell_type": "code",
2026-04-10 11:05:13 +02:00
"execution_count": 67,
2026-04-08 17:41:37 +02:00
"id": "0b87d9ff-cca1-4c0b-a12f-64a9c09c5c8b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Top discriminant features:\n",
"n_tx_total 4.121397e+06\n",
"n_isin_total 2.883250e+02\n",
"avg_holding_months_per_isin 9.944313e+01\n",
"months_since_last_tx 7.050000e+01\n",
"gross_flow_to_aum 3.969037e+00\n",
"log_aum_qty_mean 3.438283e-01\n",
"dtype: float64\n"
]
},
{
"data": {
"image/png": "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jBw5stlnTk5OrFmzhoMHD7Jx40auuuqqdtf1pz/9CWhS9I7w/PPPExMTw7XXXstLL73U4vO9e/diMpm45ZZb7O8JgsCcOXN46KGH+PXXX7ngggvs+3HxxRcTGhpqHztx4kSio6P56quvuPnmmx2aU19gm3NhYWGz9z/++GM++eQTMjMzqa2tJTIykrlz5zbbf2h66nj11Vd5//33qa6uZsSIETzxxBMttqPT6di8eTMHDx6kqKgIQRAYM2YMS5cuJTk52T7OZsb9xz/+QUZGBjt37kStVvPTTz+1adbdu3cvI0aM6LQZtrCwkOeee44jR47Q2NhIUlIS9913HxdffHGL+bzwwgukpaXx8ccfU19fz5/+9CdWrlxJSEhIh9v5+eefWbduHRkZGQQFBXHXXXe1Ou7SSy/lwgsvtFuOTCYTmzdv5tNPP6W0tBR3d3diY2P561//yqRJk+zLZWdn89JLL3H06FEaGhoICQnhiiuuYMmSJUBTHMvLL7/MF198wauvvsoPP/xAWFgYu3fvtn92tsssKSmJW2+9lXHjxrFx40aKiopISUlhzZo1JCUl8f777/PGG29QVlbGqFGjWLduHeHh4fblV6xYwU8//cS3334L/M9NuWzZMpRKJa+99hplZWUkJSWxcuVKRowYYV82LS2Nf//73xw7doyKigq8vLy46KKLWLZsGb6+vvZxtnl/8803vPrqq+zduxdRFLn88st54okncHNzs+8LwK5du9i1axcAM2fOtH/HZ7Nz504eeeQRAN555x3eeecd4H/uxJqaGjZu3Mg333xDZWUlISEhzJo1i7vuuqtZOusbb7zBf/7zH3Jzc9Hr9cTHx3PPPfdwxRVXNPuO25rXud/fuft87rG65557uPjii8nLy8NsNhMZGYmnpyc3vvojvxToWuxn9yhs99MLonz5cOEEh6wyOp2OAwcOsGTJklbrz3THjbNmzRpMJhPbt2/H3d2drKwse3XajRs3smjRIvbs2YNSqbTXTbGda6tXryY6Oppjx47x8MMPo1KpuPDCC+3r3rBhA8uXLyciIqLVOf766694eXkxfPhw+3sTJ05EJpNx8uRJ/u///q/L+yUJlwGI7US9/vrrW/3c09OTadOmsWvXLvLz84mKimpzXQUFBQD4+Ph0uN2TJ0+ye/du3n333TZPuNTUVNzd3e0uLRu2i25qaioXXHAB5eXlVFZWtuoXHzFiBD/88EOH8+lLiouLgZYXiffee4+EhAQuvfRS5HI53333HatXr0YURW699Vb7uBdffJFXX32VqVOnMnXqVE6fPs2CBQswmUzN1ldYWMjevXu54oorCA8PR6PR8MEHHzB37ly++OILgoKCmo3/17/+hUKh4M4778RoNKJQKFqdv8lk4vfff2fOnDmd2m+NRsPs2bPR6/XMmzcPX19fdu3axV/+8hdeeumlFheXV199FUEQuPvuu6msrOStt97i9ttv55NPPmm3YFR6ejp33nknKpWKRYsWYTab2bhxI35+fh3O8eWXX2bz5s3MmjWLESNGUFdXx6lTpzh9+rRduKSlpXHrrbcil8u5+eabCQsLo6CggG+//dYuXGw8+OCDREVFsWTJkg5dOT///DPffvutXahu2bKFhQsXctddd/Huu+9yyy23UF1dzeuvv86jjz7K22+
"text/plain": [
"<Figure size 600x600 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>n_tx_total</th>\n",
" <th>n_isin_total</th>\n",
" <th>avg_holding_months_per_isin</th>\n",
" <th>months_since_last_tx</th>\n",
" <th>gross_flow_to_aum</th>\n",
" <th>log_aum_qty_mean</th>\n",
" </tr>\n",
" <tr>\n",
" <th>cluster_k5</th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>819.0</td>\n",
" <td>25.0</td>\n",
" <td>52.904762</td>\n",
" <td>0.0</td>\n",
" <td>1.488451</td>\n",
" <td>10.974937</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>4.0</td>\n",
" <td>2.0</td>\n",
" <td>42.428571</td>\n",
" <td>19.0</td>\n",
" <td>1.388519</td>\n",
" <td>11.173746</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>90.5</td>\n",
" <td>12.5</td>\n",
" <td>32.149303</td>\n",
" <td>1.0</td>\n",
" <td>4.382506</td>\n",
" <td>10.356551</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>1448.0</td>\n",
" <td>24.0</td>\n",
" <td>40.857143</td>\n",
" <td>0.0</td>\n",
" <td>5.470824</td>\n",
" <td>11.044803</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>4935.5</td>\n",
" <td>47.5</td>\n",
" <td>57.100000</td>\n",
" <td>0.0</td>\n",
" <td>5.154737</td>\n",
" <td>11.993787</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" n_tx_total n_isin_total avg_holding_months_per_isin \\\n",
"cluster_k5 \n",
"0 819.0 25.0 52.904762 \n",
"1 4.0 2.0 42.428571 \n",
"2 90.5 12.5 32.149303 \n",
"3 1448.0 24.0 40.857143 \n",
"4 4935.5 47.5 57.100000 \n",
"\n",
" months_since_last_tx gross_flow_to_aum log_aum_qty_mean \n",
"cluster_k5 \n",
"0 0.0 1.488451 10.974937 \n",
"1 19.0 1.388519 11.173746 \n",
"2 1.0 4.382506 10.356551 \n",
"3 0.0 5.470824 11.044803 \n",
"4 0.0 5.154737 11.993787 "
]
},
2026-04-10 11:05:13 +02:00
"execution_count": 67,
2026-04-08 17:41:37 +02:00
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"top_features_top400 = select_top_features(\n",
" dfc_top400,\n",
" all_features_top400,\n",
" \"cluster_k5\",\n",
" top_n=6\n",
")\n",
"\n",
"plot_radar(\n",
" dfc_top400,\n",
" top_features_top400,\n",
" \"cluster_k5\",\n",
" title=\"TOP 400 — Radar (Top discriminant features)\"\n",
")"
]
2026-04-07 20:26:19 +02:00
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}