Project_Carmignac/clustering.ipynb

3722 lines
4.5 MiB
Plaintext
Raw Normal View History

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",
"| **Part 2** | Top 400 accounts (AUM > €5M) | High-conviction clustering with performance reactivity features |\n",
"\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",
"execution_count": 1,
"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",
"\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": [
"# SHARED UTILITIES\n",
"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",
"execution_count": 6,
"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",
"Segment the full client base into behavioral profiles using 8 carefully selected features. The analysis covers ~7,000 accounts with at least 6 months of history.\n",
"\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",
"| `log_aum_qty_mean` | Log mean AUM — only size variable retained |\n",
"| `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",
"execution_count": 7,
"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": [
"K=4 | sil=0.2310 | db=1.5112\n",
" n_comptes pct\n",
"cluster_k4 \n",
"0 1478 20.6\n",
"1 1820 25.4\n",
"2 1171 16.3\n",
"3 2708 37.7\n"
]
}
],
"source": [
"# 2f. Engineered ratios\n",
"dfc = df_client_base.copy()\n",
"dfc[\"log_aum_qty_mean\"] = np.log1p(dfc[\"aum_qty_mean\"].clip(lower=0))\n",
"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",
" \"log_aum_qty_mean\",\n",
" \"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",
"col = \"log_aum_qty_mean\"\n",
"if col in dfc_clean.columns:\n",
" dfc_clean[col] = winsorize_mad(dfc_clean[col], n_sigma=3)\n",
"\n",
"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",
"plt.tight_layout(); plt.show()\n",
"\n",
"# 5c. Final clustering K=4\n",
"RESULTS_GLOBAL = {}\n",
"for k in [4]:\n",
" km = KMeans(n_clusters=k, n_init=50, random_state=RANDOM_STATE)\n",
" dfc[f\"cluster_k{k}\"] = km.fit_predict(X_global_scaled)\n",
" 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",
"execution_count": 8,
"id": "1c0ea35a",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== Median behavioral features — K=4 ===\n",
" gross_flow_to_aum flow_freq flow_direction_balance n_isin_total avg_holding_months_per_isin exit_rate_per_isin log_aum_qty_mean months_since_last_tx\n",
"cluster_k4 \n",
"0 1.159 0.043 -1.000 3.0 60.000 0.400 5.167 27.0\n",
"1 1.476 0.012 -1.000 3.0 12.000 0.714 3.408 127.0\n",
"2 5.351 0.617 -0.006 12.0 28.897 0.667 8.763 3.0\n",
"3 7.889 0.071 0.000 1.0 11.333 1.000 5.280 69.0\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1200x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== Median allocation — K=4 ===\n",
" share_asset_fixed_income share_asset_diversified share_asset_equity share_fund_carmignac_patrimoine share_fund_carmignac_investissement share_fund_carmignac_sécurité share_fund_carmignac_emergents\n",
"cluster_k4 \n",
"0 0.000 0.373 0.227 0.260 0.000 0.000 0.000\n",
"1 0.000 0.326 0.099 0.156 0.000 0.000 0.000\n",
"2 0.284 0.207 0.154 0.149 0.011 0.017 0.002\n",
"3 0.768 0.000 0.000 0.000 0.000 0.000 0.000\n",
"\n",
"=== Distribution géographique per cluster ===\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABrcAAAGGCAYAAADRitpgAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3XVcFOkfB/APICBISAsIiAUqILaYZyt2d3fn2YeF2I2JqIjtIZ6tZ3d3/mwMpJVmgd3fH5yLK+GCLLvLft738vW6nZ0ZvsMOM/PZ55ln1EQikQhERERERERERERERERESkBd3gUQERERERERERERERERSYuNW0RERERERERERERERKQ02LhFRERERERERERERERESoONW0RERERERERERERERKQ02LhFRERERERERERERERESoONW0RERERERERERERERKQ02LhFRERERERERERERERESoONW0RERERERERERERERKQ02LhFRERERERERERERERESoONW0RE/zlw4AAcHBzw8eNH8bTevXujd+/e+fLzHRwcsGbNGvHrNWvWwMHBAZGRkfny8xs2bIipU6fmy8/KjI+PD5o3bw6hUCi3GhTFx48f4eDggAMHDsi7lFwZP348xo4dK+8yiIiIiCgH8jP7SIP5jPlMUTCfEREpJjZuERVwQUFB8PDwQKNGjeDs7IzKlSujW7du8PPzQ2JiorzLAwDs3LlTaS8SM3P37l2sWbMG0dHR8i4lA0WtLTY2Fps3b8bgwYOhrp52ahKJRPD29kbdunXh5uaG+fPnQyAQSCwXFxeHunXr4vDhw/IoW2m9evUKa9askfiiIC8NHjwYp06dwvPnz2WyfiIiIqKC6Htjzvd/5cuXR926dTF16lSEhITIuzylpagZCFDc2pjP8hfzGRFR7hSSdwFEJDvnz5/H2LFjoaWlhbZt26Js2bJITk7GnTt3sGTJErx69Qrz5s2Td5nYvXs3jIyM0KFDB3mXkoGvr2+Ol7l37x68vb3Rvn17GBgYSL3cw4cPoaGhkeOflxPZ1XbixAmoqanJ9Odn5e+//0ZKSgpatWolnnbo0CFs2LABgwcPho6ODjZs2ABTU1MMHTpUPM+GDRtgbW2N1q1by6NspfXq1St4e3ujevXqKF68eJ6vv3z58nBycsKWLVuwePHiPF8/ERERUUE2ZswYFC9e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
"text/plain": [
"<Figure size 1800x400 with 4 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"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",
" \"log_aum_qty_mean\", \"months_since_last_tx\",\n",
"]\n",
"profile_vars_behavior = [c for c in profile_vars_behavior if c in dfc.columns]\n",
"\n",
"prof_behavior = plot_heatmap(\n",
" dfc, profile_vars_behavior, \"cluster_k4\",\n",
" title=\"Cluster Signatures — Behavioral Features (K=4, robust z-score)\",\n",
" figsize=(14, 4)\n",
")\n",
"print(\"\\n=== Median behavioral features — K=4 ===\")\n",
"print(prof_behavior.round(3).to_string())\n",
"\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",
" \"share_fund_carmignac_investissement\", \"share_fund_carmignac_sécurité\",\n",
" \"share_fund_carmignac_emergents\",\n",
" ] if c in dfc.columns\n",
"]\n",
"\n",
"prof_allocation = plot_heatmap(\n",
" dfc, profile_vars_allocation, \"cluster_k4\",\n",
" title=\"Cluster signatures — Allocation produits (K=4, descriptif)\",\n",
" figsize=(12, 4)\n",
")\n",
"print(\"\\n=== Median allocation — K=4 ===\")\n",
"print(prof_allocation.round(3).to_string())\n",
"\n",
"# 5e. Geographic description (post-clustering)\n",
"print(\"\\n=== Distribution géographique per cluster ===\")\n",
"geo_country = pd.crosstab(\n",
" dfc[\"cluster_k4\"], dfc[\"country_grp\"].fillna(\"Unknown\"),\n",
" normalize=\"index\"\n",
").round(3) * 100\n",
"geo_region = pd.crosstab(\n",
" dfc[\"cluster_k4\"], dfc[\"region_grp\"].fillna(\"Unknown\"),\n",
" normalize=\"index\"\n",
").round(3) * 100\n",
"\n",
"fig, axes = plt.subplots(1, 2, figsize=(18, 4))\n",
"sns.heatmap(geo_country, cmap=\"Blues\", annot=True, fmt=\".1f\",\n",
" ax=axes[0], cbar_kws={\"label\": \"%\"})\n",
"axes[0].set_title(\"Country distribution (% per cluster)\")\n",
"sns.heatmap(geo_region, cmap=\"Blues\", annot=True, fmt=\".1f\",\n",
" ax=axes[1], cbar_kws={\"label\": \"%\"})\n",
"axes[1].set_title(\"Region distribution (% per cluster)\")\n",
"plt.tight_layout(); plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 9,
"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": {
"image/png": "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
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Alternative:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_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",
"\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": {
"image/png": "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
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Diversified:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_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",
"\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": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABM4AAAGGCAYAAACDus3zAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAA9ORJREFUeJzs3XlcTPv/B/DXTItEpU0UQlFdirrJvsW92WWXXbbs++5G2felRHyzZN+zhchO9i1J106WtNCelpnfH37NbVSMaZnk9Xw8zoM553M+8z7T6dPMez6LQCwWi0FERERERERERERShIoOgIiIiIiIiIiIqChi4oyIiIiIiIiIiCgHTJwRERERERERERHlgIkzIiIiIiIiIiKiHDBxRkRERERERERElAMmzoiIiIiIiIiIiHLAxBkREREREREREVEOmDgjIiIiIiIiIiLKARNnREREREREREREOWDijIiIpNjb22PatGmKDkMhpk2bBnt7e0WHQUVYYf9+DBkyBLNmzSq05yvK+vbti3bt2ik6DMqie/fuWLJkiaLDICIiKlBMnBER/SZev34NV1dXtGjRApaWlrCxsUHPnj2xdetWpKSkFEoMycnJ8PDwwPXr1wvl+TLFxMRg3rx5aNWqFaysrFC/fn107doVS5cuRWJiYqHG8jPWr1+PM2fOKDqMIuX69eswMzPLdTt+/HihxvP06VN4eHggPDw83+u+ffs2rly5giFDhkj2ZV7/yZMnpcqmpqZi2LBhMDc3x/79+/P0vDdv3oSLiwuaNm0KS0tLNGzYEIMGDcLt27fzVO+vir+HuRsyZAh27tyJyMhIRYdCRERUYJQVHQARERW88+fPY+zYsVBVVUXHjh1RvXp1pKWl4fbt21i6dCmePn2KuXPnFngcycnJ8PT0xKhRo1C3bt0Cfz4A+Pz5M7p06YKEhAR06dIFVatWxefPnxEWFoZdu3bByckJpUqVAgDMnTsXYrG4UOKShbe3NxwcHNCyZUtFh1Lk9O3bF5aWltn2165du0Cf9+TJkxAIBJLHT58+haenJ+zs7FChQoV8fS4fHx/Ur18fxsbG3y2XlpaGMWPG4MKFC5g7dy66du2ap+d9+fIlhEIhevbsCT09PcTFxeHIkSPo06cPvL290aRJkzzV/6vh72HuWrRogdKlS2Pnzp0YO3asosMhIiIqEEycEREVc2/evMH48eNhaGiIrVu3omzZspJjvXv3xqtXr3D+/HnFBZgPkpKSoK6unuOx/fv34927d9i1axdsbGykjiUkJEBFRUXyOOv/iyuRSIS0tDSUKFFC0aHkia2tLVq1alXoz6uqqloozxMdHY0LFy5gzpw53y2XlpaGcePG4fz583B3d0e3bt3y/NzdunXLVk+vXr3QsmVLbN26Nd8SZ9/7vSXF+NmfiVAohIODAw4fPowxY8ZIJZWJiIiKCw7VJCIq5v73v/8hKSkJ8+fPl0qaZTI2Nkb//v1zPd/DwwNmZmbZ9h88eBBmZmZSQ9SCg4MxaNAg1K1bF1ZWVrC3t8f06dMBAOHh4ahfvz4AwNPTUzK0zsPDQ3L+s2fPMGbMGNjZ2cHS0hKdO3dGYGBgjs9748YNzJkzB/Xr10fTpk1zjf/169dQUlLKsSdS6dKlpRJIOc1x9unTJ0yePBk2NjawtbXF1KlT8fjxY5iZmeHgwYNS51pbWyMiIgIjRoyAtbU16tWrh8WLFyMjI0OqTh8fH/Ts2VPyOnXu3Dnb0DszMzMkJSXh0KFDktcqc26t3OZiy+lnZWZmBnd3dxw5cgRt27aFpaUlLl26BACIiIjA9OnT0aBBA9SsWRNt27bNcZjftm3b0LZtW9SqVQt16tRB586dcfTo0Zxe7iIlNTUVCxYsQL169WBtbQ0XFxd8+PAh2333M69n1jnODh48KOll069fP8nP6fr165g6dSrq1q2LtLS0bPU6OzvDwcHhu7GfP38e6enpaNCgQa5l0tPTMWHCBAQGBmLOnDno3r37d+vMi5IlS0JHRwfx8fFynf+j39sdO3agbdu2qFmzJho1agQ3NzfExcXlWNfDhw/Rs2dPSRuza9euHJ/r2+GzmcNcsw4Vf/nyJUaPHo2GDRvC0tISTZo0wfjx4yXX+b3fw5zY29vnOoz4R0PUIyMjMX36dDRp0kTyOgwfPjzbdVy4cAF9+vSBtbU1bGxs0KVLl2y/jydOnEDnzp1hZWWFunXrYtKkSYiIiJAqk9lmvX79GkOGDIG1tTUmTZoE4GuCfcuWLZI2o0GDBnB1dUVsbGy2uBs0aIC3b98iNDT0u9dHRET0q2KPMyKiYu7cuXOoWLFitt5W+S06OhqDBg2CtrY2hg4dCk1NTYSHh+P06dMAAB0dHcyZMwdz5szBX3/9hb/++gsAJImJJ0+ewMnJCQYGBhgyZAjU1dVx4sQJjBw5Eh4eHpLymdzc3KCjo4ORI0ciKSkp17iMjIyQkZGBw4cPo1OnTj91TSKRCMOHD8eDBw/g5OSEqlWrIjAwEFOnTs2xfEZGBgYNGgQrKytMmTIFQUFB2LRpEypWrIhevXpJyvn6+sLe3h7t27dHWloajh8/jrFjx8Lb2xvNmjUDACxZsgSzZs2ClZWVJCFSqVKln4o/07Vr13DixAn07t0b2traMDIyQlRUFLp37w6BQIDevXtDR0cHFy9exMyZM5GQkIABAwYAAPbu3Yt58+bBwcEB/fr1w5cvXxAWFob79++jffv2csWTHxITExETE5Ntv7a2tqTXy8yZM3HkyBG0a9cONjY2uHbtGoYOHZpvMdSpUwd9+/bFtm3b4OLigqpVqwIATExM0LFjR/j5+eHy5cto3ry55JzIyEhcu3YNI0eO/G7dd+/eRZkyZWBkZJTj8YyMDEyYMAGnT5+Gq6srevbsma1MWlqazImuMmXKQCiU/j41ISEBqamp+PTpEw4fPox///0XLi4uMtWXm5x+bz08PODp6YkGDRrAyckJL168wK5duxAcHIxdu3ZJ9QSNjY3F0KFD0bp1a7Rt2xYnTpzAnDlzoKKi8tNDVFNTUzFo0CCkpqaiT58+0NPTQ0REBM6fP4+4uDhoaGj89O/hjBkzss2buHXrVoSGhqJMmTLfjWf06NF4+vQp+vTpAyMjI8TExODKlSt4//69ZBjwwYMHMWPGDFSrVg3Dhg2DhoYGQkNDcenSJcnv48GDBzF9+nRYWlpiwoQJiI6Ohq+vL+7cuQM/Pz9oampKnjM9PR2DBg3Cn3/+ialTp0JNTQ0A4OrqikOHDqFz587o27cvwsPDsWPHDjx69Cjbz6RmzZoAgDt37uCPP/6Q8dUnIiL6dTBxRkRUjCUkJCAiIgItWrQo8Oe6e/cuYmNj4ePjIzX31Pjx4wEA6urqcHBwwJw5c2BmZoaOHTtKnT9//nyUL18eBw4ckAyH69WrF5ycnLBs2bJsiTMtLS1s2bIFSkpK342rS5cu2LJlC6ZNm4YNGzbAzs4OderUQdOmTaGhofHdc8+cOYO7d+9ixowZkl55Tk5OGDhwYI7lv3z5gtatW0uSIk5OTujUqRP2798vlTg7deqU5AMq8HXIbOfOnbF582ZJ4qxjx46YM2cOKlasmO21+lkvXrzA0aNHYWpqKtk3c+ZMZGRk4OjRo9DW1pbEO2HCBHh6eqJnz55QU1PD+fPnUa1aNaxZsyZPMeS3GTNm5Lj/8uXL0NfXx+PHj3HkyBH06tULs2fPBvD1dZ44cSLCwsLyJYaKFSvC1tYW27ZtQ4MGDaTm7dPR0UG5cuVw5MgRqcTZ8ePHIRKJ0KFDh+/W/fz581yTZgCwfPlyvH37Fq6urlL3VlZ37txBv379ZLqWwMDAbHO0jR07FpcvXwbwdRhzjx49MGLECJnqy823v7cxMTHw9vZGo0aNsHHjRknyrmrVqpKekl26dJGc//HjR0ybNk3yO9ijRw90794dK1asQMeOHX9quPWzZ88QHh6O1atXSw37HTVqlOT/P/t7+O08aCdOnEBISAjGjBmTY8/dTHFxcbh79y6mTJmCQYMGSfYPGzZM8v/4+HjMmzcPVlZW2LZtm1Rv2cy
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Equity:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_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",
"\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": {
"image/png": "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
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Fixed Income:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_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",
"\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",
"df_client_asset[\"log_aum_qty_mean\"] = np.log1p(\n",
" 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",
" \"flow_direction_balance\", \"log_aum_qty_mean\",\n",
" \"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": "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"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "05d06b16",
"metadata": {},
"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 C0 86.5 6.2 7.3\n",
"Global C1 93.2 0.9 5.9\n",
"Global C2 48.8 10.8 40.4\n",
"Global C3 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 C0 21.3 34.6 12.0\n",
"Global C1 28.2 6.5 11.9\n",
"Global C2 9.5 47.9 52.5\n",
"Global C3 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 C0 31.6 40.5 27.9\n",
"Global C1 40.9 54.0 5.1\n",
"Global C2 21.2 61.7 17.1\n",
"Global C3 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 C0 14.6 18.6 54.6\n",
"Global C1 23.3 30.5 12.3\n",
"Global C2 7.8 22.4 26.5\n",
"Global C3 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 C0 37.3 29.4 0.3 32.9\n",
"Global C1 44.2 6.8 0.0 49.1\n",
"Global C2 18.6 9.7 48.5 23.1\n",
"Global C3 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 C0 15.8 57.2 0.9 20.7\n",
"Global C1 23.1 16.2 0.0 38.0\n",
"Global C2 6.2 15.0 98.3 11.5\n",
"Global C3 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 C0 65.4 21.2 13.5\n",
"Global C1 72.0 24.1 3.9\n",
"Global C2 19.2 52.9 27.8\n",
"Global C3 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 C0 28.1 10.3 28.2\n",
"Global C1 38.2 14.4 10.1\n",
"Global C2 6.6 20.4 46.2\n",
"Global C3 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",
"0 1.79\n",
"1 1.50\n",
"2 2.92\n",
"3 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 C0 0.0 49.1 29.3 14.9 6.7\n",
"Global C1 0.0 64.7 23.3 9.6 2.4\n",
"Global C2 0.8 17.5 13.7 24.9 43.1\n",
"Global C3 0.4 73.9 14.8 7.3 3.5\n"
]
}
],
"source": [
"# ============================================================\n",
"# CROSS-ANALYSIS — Global clustering × Asset-type clustering\n",
"# ============================================================\n",
"\n",
"# Step 1. Merge asset cluster labels into global dataframe\n",
"dfc_cross = dfc[[ID_COL, \"cluster_k4\"]].copy()\n",
"\n",
"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",
"\n",
"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())"
]
},
{
"cell_type": "markdown",
"id": "7228921e",
"metadata": {},
"source": [
"---\n",
"### 5f. Fund-Level Sub-Clustering\n",
"\n",
"Same logic applied within each of the top 15 funds by AUM, with a minimum of 20 accounts per fund.\n"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "c28bd684",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"=== Available funds (top 20 by AUM) ===\n",
"Product - Fund\n",
"Carmignac Patrimoine 1.779824e+12\n",
"Carmignac Sécurité 1.039756e+12\n",
"Carmignac Investissement 4.742077e+11\n",
"Carmignac Portfolio Sécurité 2.334162e+11\n",
"Carmignac Portfolio Flexible Bond 1.935158e+11\n",
"Carmignac Emergents 1.165950e+11\n",
"Carmignac Portfolio Patrimoine 8.958118e+10\n",
"Carmignac Portfolio Global Bond 8.130476e+10\n",
"Carmignac Portfolio Credit 7.134488e+10\n",
"Carmignac Portfolio Emerging Patrimoine 6.955348e+10\n",
"Carmignac Portfolio Grande Europe 5.661533e+10\n",
"Carmignac Court Terme 4.825378e+10\n",
"Carmignac Portfolio Long-Short European Equities 4.666827e+10\n",
"Carmignac Portfolio Climate Transition 4.595703e+10\n",
"Carmignac Credit 2027 3.838470e+10\n",
"Carmignac Absolute Return Europe 3.649769e+10\n",
"Carmignac Investissement Latitude 3.288443e+10\n",
"Carmignac Multi Expertise 2.605206e+10\n",
"Carmignac Portfolio Emergents 2.596680e+10\n",
"Carmignac Portfolio Asia Discovery 2.234114e+10\n",
"\n",
"Selected funds (15) :\n",
" Carmignac Patrimoine : 6183 clients, AUM=1779824207927\n",
" Carmignac Sécurité : 2825 clients, AUM=1039756177353\n",
" Carmignac Investissement : 4982 clients, AUM=474207712295\n",
" Carmignac Portfolio Sécurité : 1264 clients, AUM=233416206914\n",
" Carmignac Portfolio Flexible Bond : 1431 clients, AUM=193515764737\n",
" Carmignac Emergents : 4231 clients, AUM=116594983722\n",
" Carmignac Portfolio Patrimoine : 1234 clients, AUM=89581181925\n",
" Carmignac Portfolio Global Bond : 2114 clients, AUM=81304760901\n",
" Carmignac Portfolio Credit : 1135 clients, AUM=71344877483\n",
" Carmignac Portfolio Emerging Patrimoine : 1673 clients, AUM=69553477364\n",
" Carmignac Portfolio Grande Europe : 2950 clients, AUM=56615328729\n",
" Carmignac Court Terme : 1331 clients, AUM=48253783176\n",
" Carmignac Portfolio Long-Short European Equities : 689 clients, AUM=46668268707\n",
" Carmignac Portfolio Climate Transition : 3097 clients, AUM=45957034098\n",
" Carmignac Credit 2027 : 327 clients, AUM=38384700194\n",
"\n",
"df_month_fund shape: (3865290, 19)\n",
"df_client_fund shape: (20093, 23)\n",
"\n",
"Comptes par fund :\n",
"Product - Fund\n",
"Carmignac Patrimoine 3153\n",
"Carmignac Investissement 2192\n",
"Carmignac Emergents 1779\n",
"Carmignac Portfolio Global Bond 1716\n",
"Carmignac Sécurité 1622\n",
"Carmignac Portfolio Grande Europe 1386\n",
"Carmignac Portfolio Climate Transition 1278\n",
"Carmignac Portfolio Sécurité 1161\n",
"Carmignac Portfolio Patrimoine 1143\n",
"Carmignac Portfolio Emerging Patrimoine 1135\n",
"Carmignac Portfolio Flexible Bond 1087\n",
"Carmignac Portfolio Credit 1016\n",
"Carmignac Portfolio Long-Short European Equities 605\n",
"Carmignac Court Terme 525\n",
"Carmignac Credit 2027 295\n",
"Name: Registrar Account - ID, dtype: int64\n",
"\n",
"============================================================\n",
"FUND : Carmignac Patrimoine\n",
"============================================================\n",
" k silhouette davies_bouldin min_cluster_size\n",
" 2 0.5054 0.8315 656\n",
" 3 0.4949 0.8428 569\n",
" 4 0.3566 1.2291 338\n",
" 5 0.3844 1.1120 276\n",
" 6 0.3207 1.1835 271\n",
"→ K retenu : 2 (silhouette=0.5054)\n",
" n_comptes pct\n",
"cluster_carmignac_patrimoine \n",
"0 656 20.8\n",
"1 2497 79.2\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Carmignac Patrimoine:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_aum_qty_mean months_since_last_tx_fund aum_final_to_peak aum_drawdown_last buy_on_perf_rate\n",
"cluster_carmignac_patrimoine \n",
"0 0.085 0.127 1.00 -1.000 4.749 12.0 0.907 0.093 0.0\n",
"1 0.038 2.334 0.65 -0.879 4.740 83.0 0.000 1.000 0.0\n",
"\n",
"============================================================\n",
"FUND : Carmignac Sécurité\n",
"============================================================\n",
" k silhouette davies_bouldin min_cluster_size\n",
" 2 0.5729 0.8464 309\n",
" 3 0.5553 0.8254 192\n",
" 4 0.4175 0.9791 96\n",
" 5 0.4124 0.9532 92\n",
" 6 0.2702 1.1743 83\n",
"→ K retenu : 2 (silhouette=0.5729)\n",
" n_comptes pct\n",
"cluster_carmignac_sécurité \n",
"0 309 19.1\n",
"1 1313 80.9\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Carmignac Sécurité:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_aum_qty_mean months_since_last_tx_fund aum_final_to_peak aum_drawdown_last buy_on_perf_rate\n",
"cluster_carmignac_sécurité \n",
"0 0.111 1.312 1.000 0.067 5.178 12.0 0.916 0.084 0.0\n",
"1 0.071 4.471 0.525 -0.128 4.721 81.0 0.000 1.000 0.0\n",
"\n",
"============================================================\n",
"FUND : Carmignac Investissement\n",
"============================================================\n",
" k silhouette davies_bouldin min_cluster_size\n",
" 2 0.4306 1.0230 381\n",
" 3 0.4242 0.9113 363\n",
" 4 0.2926 1.3034 312\n",
" 5 0.3051 1.2219 216\n",
" 6 0.2917 1.2180 209\n",
"→ K retenu : 2 (silhouette=0.4306)\n",
" n_comptes pct\n",
"cluster_carmignac_investissement \n",
"0 1811 82.6\n",
"1 381 17.4\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABNwAAAGGCAYAAACg4ZwmAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3XdUE9nbB/BvgoCFooCigqCCYAEE7IgdK6tiXxUbIvbu2sWCirp2QcWGvbvY29ob6qrYwY59kaIUQSmZ9w9f8iMGNATYQPx+zsk55M7MnSe5mZSHW0SCIAggIiIiIiIiIiKiXCFWdQBERERERERERETqhAk3IiIiIiIiIiKiXMSEGxERERERERERUS5iwo2IiIiIiIiIiCgXMeFGRERERERERESUi5hwIyIiIiIiIiIiykVMuBEREREREREREeUiJtyIiIiIiIiIiIhyERNuREREREREREREuYgJNyIq8Jo2bYqJEyeqOgyVmDhxIpo2barqMEjN9OrVC7169VJ1GJTBr/w+l5skEgl+++03rFq1StWh5AtNmzbFwIEDVR0G/b+UlBQ0atQI27ZtU3UoRESUC5hwI6J869WrV/D29kazZs1ga2sLR0dH/P7779i0aRO+fPnyn8SQlJSEFStW4Nq1a//J+dLFxMRg9uzZaNWqFezs7FCvXj107twZf/75Jz5//vyfxpIdq1evxqlTp1QdRr4UFRWF+fPno1WrVqhevTrs7e3RsWNHrFy5EnFxcaoOTy1NnDgRDg4Oqg5DYbdu3cKKFSv4elDQ+fPnsWLFimwdc/jwYbx//x7u7u7Ssr/++gvW1ta4d++ezL7x8fHo3LkzbG1tceHChRzFevLkSYwaNQrNmjVD9erV0bJlS8ybN++XbGtVfa4WBJqamujXrx9Wr16Nr1+/qjocIiLKoUKqDoCIKDPnzp3DyJEjoaWlhfbt28PKygopKSm4efMm/vzzTzx9+hQ+Pj55HkdSUhL8/PwwbNgw1KlTJ8/PBwCfPn1Cp06dkJCQgE6dOqFixYr49OkTHj16hB07dqB79+4oVqwYAMDHxweCIPwncSkiICAALVu2hIuLi6pDyVfu3r0LLy8vJCYmol27dqhWrRoA4P79+1i7di1u3LiBDRs2qDjK/1m/fr2qQ/glhYSEwM/PDx06dICenp7MtuPHj0MkEqkosvzp/Pnz2LZtG4YPH67wMevXr4erqyt0dXV/uF9CQgI8PDzw6NEj+Pn5oWHDhjmKddq0aShVqhTatWuHsmXL4tGjR9i6dSvOnz+PoKAgFC5cOEf1FySq+FwtSDp27IiFCxfi0KFD6Ny5s6rDISKiHFAq4TZp0iRMmTIFOjo6MuWJiYnw8fGBr69vrgRHRL+m169fY/To0Shbtiw2bdqEUqVKSbf17NkTL1++xLlz51QXYC5ITExE0aJFM922d+9evHv3Djt27ICjo6PMtoSEBGhqakrvZ/xbXUkkEqSkpEBbW1vVoSglLi4Ow4YNg4aGBoKCgmBhYSGzffTo0di9e3eunCspKQlFihTJcT1aWlq5EA3lJrZJzj18+BBhYWE/HZqbkJCA/v37IzQ0FH5+fmjUqFGOz718+XK55JKNjQ0mTJiAQ4cOoUuXLjk+B/DjzxZSjey+L+vp6cHZ2RlBQUFMuBERFXBKDSndv39/pt2cv3z5ggMHDuQ4KCL6ta1btw6JiYmYM2eOTLItnbm5Ofr06ZPl8StWrIC1tbVcefqwoTdv3kjL7t27h/79+6NOnTqws7ND06ZNMWnSJADAmzdvUK9ePQCAn58frK2tYW1tLTOE6dmzZxgxYgRq164NW1tbdOzYEadPn870vNevX8eMGTNQr169H/6Ae/XqFTQ0NGBvby+3TUdHRybxlNkcbh8/fsQff/wBR0dH1KxZExMmTEBYWBisra3x119/yRzr4OCAiIgIDBkyBA4ODqhbty7mz5+PtLQ0mTrXr1+P33//Xfo8dezYEcePH5fZx9raGomJiQgKCpI+V+k/bLOaay6ztrK2tsasWbNw8OBBuLq6wtbWFhcvXgQAREREYNKkSXBycoKNjQ1cXV2xd+9euXq3bNkCV1dXVK9eHbVq1ULHjh1x6NChzJ7uPLdz505ERERg4sSJcsk2ADAyMsKQIUOk90+dOgUvLy84OzvDxsYGLi4u8Pf3l2uTXr164bfffsP9+/fRs2dPVK9eHYsXL8abN29gbW2N9evXY9u2bdIhbB4eHnj//j0EQYC/vz8aNmwIOzs7DB48GJ8+fZKr+/s53N6+fYtBgwbB3t4e9erVw9y5c3Hx4kVYW1vLDA1Lj+vp06fo1asXqlevjgYNGmDt2rUy9SUnJ2PZsmXo2LEjatSoAXt7e/To0QNXr16Ve44kEgk2bdqEtm3bwtbWFnXr1kX//v3lhgAqIn3Oqhs3bkiHCzZr1gz79++X7nPv3j1YW1sjKChI7vj0x3z27FlpWW68LlesWIEFCxYAAJo1aya9htLfr76fwy0lJQV+fn5o0aIFbG1tUadOHXTv3h2XL1+W7hMZGYlJkyahYcOGsLGxgbOzMwYPHizzHgh86ynWo0cP2Nvbw8HBAV5eXnjy5InMPunvF+/evcPAgQPh4OCABg0aSOeaevToEXr37g17e3s0adIk0+stLi4Oc+bMQaNGjWBjY4PmzZtjzZo1kEgk0n0yvn537doFFxcX2NjYoFOnTrh7965MPOnnTn+uMnvfz+jUqVPQ1NREzZo1s9zn8+fP8PT0xIMHD7BixQo0btz4h3UqKrOeXOk9gZ89e6ZUnenvn0+fPsXYsWNRq1Yt9OjRAwCQmpoKf39/6fPXtGlTLF68GMnJyZnWdenSJbRv3x62trZo06YNTp48mem5vpcXn6vfy9i+39++fy1/Lzw8HMOHD0f9+vVha2uLhg0bYvTo0YiPj5fZ78CBA+jcubP02uzZsycuXboks8+2bdvg6uoqvZZmzpwpNyQ4q/dl4Nt73vLly9G8eXPY2NigUaNGWLBgQaZt4uTkhJs3b8q9NxMRUcGSrR5uCQkJEAQBgiDg8+fPMj/60tLScOHCBRgYGOR6kET0azl79izKlSsn17srt0VHR6N///4oUaIEvLy8oKenhzdv3uDvv/8GABgYGGDGjBmYMWMGmjdvjubNmwOA9EfHkydP0L17dxgbG2PAgAEoWrQojh07hqFDh2LFihXS/dPNnDkTBgYGGDp0KBITE7OMy8TEBGlpaThw4AA6dOiQrcckkUgwePBg3L17F927d0fFihVx+vRpTJgwIdP909LS0L9/f9jZ2WH8+PEIDg7Ghg0bUK5cOekPNwDYvHkzmjZtirZt2yIlJQVHjhzByJEjERAQIP1BumDBAkydOhV2dnbo2rUrAMDMzCxb8ae7evUqjh07hp49e6JEiRIwMTFBVFQUunbtCpFIhJ49e8LAwAAXLlzAlClTkJCQgL59+wIAdu/ejdmzZ6Nly5bo3bs3vn79ikePHuHOnTto27atUvHkxJkzZ1C4cGG0bNlSof2DgoJQtGhR9OvXD0WLFsXVq1exfPlyJCQkyLXjp0+fMGDAALi6uqJdu3YwNDSUbjt06BBSUlLQq1cvfPr0CevWrcOoUaNQt25dXLt2DQMGDMDLly+xdetWzJ8//4e90xMTE9GnTx9ERkaid+/eMDIywuHDh7Ocgyk2Nhaenp5o3rw5WrdujRMnTmDhwoWwsrKSJpsTEhKwZ88e/Pbbb+jSpQs+f/6MvXv3wtPTE3v27EGVKlWk9U2ZMgV//fUXGjZsiM6dOyMtLQ03btzAnTt3YGtrq9DzmtHLly8xcuRIdO7cGR06dMC+ffswceJEVKtWDZUqVYKtrS3KlSuHY8eOyV2DR48ehb6+PpydnQEg116XzZs3R3h4OA4fPoxJkyahRIkSAJDl9yo/Pz8EBASgS5cusLOzQ0JCAu7fv48HDx6gfv36AIDhw4fj6dOncHd3h4mJCWJiYnD58mW8f/8epqamAL79E3XixIlwdnbGuHHjkJSUhB07dqBHjx4ICgqS7gd8e78YMGAAatasiXHjxuHQoUO
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Carmignac Investissement:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_aum_qty_mean months_since_last_tx_fund aum_final_to_peak aum_drawdown_last buy_on_perf_rate\n",
"cluster_carmignac_investissement \n",
"0 0.025 1.069 0.738 -1.000 3.241 92.0 0.000 1.000 0.000\n",
"1 0.531 1.860 1.352 -0.468 7.592 0.0 0.123 0.877 0.029\n",
"\n",
"============================================================\n",
"FUND : Carmignac Portfolio Sécurité\n",
"============================================================\n",
" k silhouette davies_bouldin min_cluster_size\n",
" 2 0.8029 0.3052 180\n",
" 3 0.6465 0.4761 180\n",
" 4 0.6612 0.5242 61\n",
" 5 0.7213 0.4794 61\n",
" 6 0.7007 0.6254 44\n",
"→ K retenu : 2 (silhouette=0.8029)\n",
" n_comptes pct\n",
"cluster_carmignac_portfolio_sécurité \n",
"0 981 84.5\n",
"1 180 15.5\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Carmignac Portfolio Sécurité:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_aum_qty_mean months_since_last_tx_fund aum_final_to_peak aum_drawdown_last buy_on_perf_rate\n",
"cluster_carmignac_portfolio_sécurité \n",
"0 0.061 7.231 0.419 0.000 6.043 72.0 0.0 1.0 0.0\n",
"1 0.221 1.468 1.000 0.333 8.488 0.0 1.0 0.0 0.0\n",
"\n",
"============================================================\n",
"FUND : Carmignac Portfolio Flexible Bond\n",
"============================================================\n",
" k silhouette davies_bouldin min_cluster_size\n",
" 2 0.6085 0.7567 122\n",
" 3 0.3985 1.1704 75\n",
" 4 0.4165 0.9067 34\n",
" 5 0.3106 1.1499 32\n",
" 6 0.2713 1.1373 32\n",
"→ K retenu : 2 (silhouette=0.6085)\n",
" n_comptes pct\n",
"cluster_carmignac_portfolio_flexible_b \n",
"0 965 88.8\n",
"1 122 11.2\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Carmignac Portfolio Flexible Bond:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_aum_qty_mean months_since_last_tx_fund aum_final_to_peak aum_drawdown_last buy_on_perf_rate\n",
"cluster_carmignac_portfolio_flexible_b \n",
"0 0.060 3.925 0.562 -0.156 3.982 81.0 0.000 1.000 0.000\n",
"1 0.742 6.079 1.650 0.119 7.743 0.0 0.677 0.323 0.085\n",
"\n",
"============================================================\n",
"FUND : Carmignac Emergents\n",
"============================================================\n",
" k silhouette davies_bouldin min_cluster_size\n",
" 2 0.4352 1.1772 508\n",
" 3 0.4735 0.8703 290\n",
" 4 0.4071 1.0186 140\n",
" 5 0.2776 1.2986 137\n",
" 6 0.2956 1.2394 125\n",
"→ K retenu : 3 (silhouette=0.4735)\n",
" n_comptes pct\n",
"cluster_carmignac_emergents \n",
"0 345 19.4\n",
"1 1144 64.3\n",
"2 290 16.3\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Carmignac Emergents:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_aum_qty_mean months_since_last_tx_fund aum_final_to_peak aum_drawdown_last buy_on_perf_rate\n",
"cluster_carmignac_emergents \n",
"0 0.070 0.031 1.000 -0.926 3.362 12.0 0.907 0.093 0.000\n",
"1 0.018 2.101 0.425 -1.000 2.718 95.0 0.000 1.000 0.000\n",
"2 0.546 2.578 1.000 -0.168 7.015 0.0 0.181 0.819 0.028\n",
"\n",
"============================================================\n",
"FUND : Carmignac Portfolio Patrimoine\n",
"============================================================\n",
" k silhouette davies_bouldin min_cluster_size\n",
" 2 0.5909 0.5603 251\n",
" 3 0.6446 0.5163 170\n",
" 4 0.6254 0.5994 100\n",
" 5 0.6019 0.7216 74\n",
" 6 0.5870 0.7977 72\n",
"→ K retenu : 3 (silhouette=0.6446)\n",
" n_comptes pct\n",
"cluster_carmignac_portfolio_patrimoine \n",
"0 238 20.8\n",
"1 735 64.3\n",
"2 170 14.9\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Carmignac Portfolio Patrimoine:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_aum_qty_mean months_since_last_tx_fund aum_final_to_peak aum_drawdown_last buy_on_perf_rate\n",
"cluster_carmignac_portfolio_patrimoine \n",
"0 0.040 3.795 0.377 -1.0 5.872 48.0 0.0 1.0 0.0\n",
"1 0.057 5.620 0.458 0.0 5.613 80.0 0.0 1.0 0.0\n",
"2 0.041 4.016 0.463 1.0 5.188 90.0 0.0 1.0 0.0\n",
"\n",
"============================================================\n",
"FUND : Carmignac Portfolio Global Bond\n",
"============================================================\n",
" k silhouette davies_bouldin min_cluster_size\n",
" 2 0.7570 0.3758 294\n",
" 3 0.8452 0.2188 202\n",
" 4 0.8204 0.3165 97\n",
" 5 0.8294 0.3355 94\n",
" 6 0.8277 0.3595 60\n",
"→ K retenu : 3 (silhouette=0.8452)\n",
" n_comptes pct\n",
"cluster_carmignac_portfolio_global_bon \n",
"0 1244 72.5\n",
"1 270 15.7\n",
"2 202 11.8\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Carmignac Portfolio Global Bond:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_aum_qty_mean months_since_last_tx_fund aum_final_to_peak aum_drawdown_last buy_on_perf_rate\n",
"cluster_carmignac_portfolio_global_bon \n",
"0 0.073 7.429 0.373 0.000 4.423 69.0 0.0 1.0 0.0\n",
"1 0.042 3.957 0.432 -1.000 4.338 60.0 0.0 1.0 0.0\n",
"2 0.066 3.125 0.570 0.955 4.353 71.0 0.0 1.0 0.0\n",
"\n",
"============================================================\n",
"FUND : Carmignac Portfolio Credit\n",
"============================================================\n",
" k silhouette davies_bouldin min_cluster_size\n",
" 2 0.7000 0.5933 107\n",
" 3 0.5964 0.6651 36\n",
" 4 0.4836 0.7926 17\n",
" 5 0.2673 1.1077 17\n",
" 6 0.2966 1.0748 17\n",
"→ K retenu : 2 (silhouette=0.7000)\n",
" n_comptes pct\n",
"cluster_carmignac_portfolio_credit \n",
"0 107 10.5\n",
"1 909 89.5\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Carmignac Portfolio Credit:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_aum_qty_mean months_since_last_tx_fund aum_final_to_peak aum_drawdown_last buy_on_perf_rate\n",
"cluster_carmignac_portfolio_credit \n",
"0 0.821 4.720 1.113 0.586 9.596 0.0 1.0 0.0 0.123\n",
"1 0.069 2.494 0.684 0.098 5.962 25.0 0.0 1.0 0.000\n",
"\n",
"============================================================\n",
"FUND : Carmignac Portfolio Emerging Patrimoine\n",
"============================================================\n",
" k silhouette davies_bouldin min_cluster_size\n",
" 2 0.5535 1.0735 255\n",
" 3 0.5964 0.7555 133\n",
" 4 0.4753 0.9800 69\n",
" 5 0.4225 1.1074 30\n",
" 6 0.4453 1.0458 35\n",
"→ K retenu : 3 (silhouette=0.5964)\n",
" n_comptes pct\n",
"cluster_carmignac_portfolio_emerging_p \n",
"0 850 74.9\n",
"1 152 13.4\n",
"2 133 11.7\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Carmignac Portfolio Emerging Patrimoine:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_aum_qty_mean months_since_last_tx_fund aum_final_to_peak aum_drawdown_last buy_on_perf_rate\n",
"cluster_carmignac_portfolio_emerging_p \n",
"0 0.038 3.181 0.500 -0.613 5.206 83.0 0.000 1.000 0.000\n",
"1 0.037 0.283 1.000 -0.142 5.668 29.0 0.926 0.074 0.000\n",
"2 0.714 4.279 2.212 -0.115 9.002 0.0 0.218 0.782 0.063\n",
"\n",
"============================================================\n",
"FUND : Carmignac Portfolio Grande Europe\n",
"============================================================\n",
" k silhouette davies_bouldin min_cluster_size\n",
" 2 0.3729 1.1301 232\n",
" 3 0.3476 1.1497 217\n",
" 4 0.2731 1.3479 165\n",
" 5 0.2535 1.3399 147\n",
" 6 0.2606 1.3154 118\n",
"→ K retenu : 2 (silhouette=0.3729)\n",
" n_comptes pct\n",
"cluster_carmignac_portfolio_grande_eur \n",
"0 232 16.7\n",
"1 1154 83.3\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Carmignac Portfolio Grande Europe:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_aum_qty_mean months_since_last_tx_fund aum_final_to_peak aum_drawdown_last buy_on_perf_rate\n",
"cluster_carmignac_portfolio_grande_eur \n",
"0 0.519 4.254 1.00 -0.034 7.587 0.0 0.398 0.602 0.034\n",
"1 0.029 1.864 0.59 -0.524 4.093 81.0 0.000 1.000 0.000\n",
"\n",
"============================================================\n",
"FUND : Carmignac Court Terme\n",
"============================================================\n",
" k silhouette davies_bouldin min_cluster_size\n",
" 2 0.4725 0.9119 113\n",
" 3 0.4110 0.8591 109\n",
" 4 0.3617 1.1573 108\n",
" 5 0.3467 1.2485 57\n",
" 6 0.3690 1.1097 34\n",
"→ K retenu : 2 (silhouette=0.4725)\n",
" n_comptes pct\n",
"cluster_carmignac_court_terme \n",
"0 412 78.5\n",
"1 113 21.5\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Carmignac Court Terme:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_aum_qty_mean months_since_last_tx_fund aum_final_to_peak aum_drawdown_last buy_on_perf_rate\n",
"cluster_carmignac_court_terme \n",
"0 0.053 7.561 0.333 -0.023 1.924 86.5 0.00 1.00 0.000\n",
"1 0.254 5.379 1.000 0.286 4.609 0.0 0.89 0.11 0.008\n",
"\n",
"============================================================\n",
"FUND : Carmignac Portfolio Long-Short European Equities\n",
"============================================================\n",
" k silhouette davies_bouldin min_cluster_size\n",
" 2 0.3490 1.3504 188\n",
" 3 0.3816 1.0159 119\n",
" 4 0.3969 0.9229 54\n",
" 5 0.3535 1.0391 54\n",
" 6 0.3742 1.0386 46\n",
"→ K retenu : 4 (silhouette=0.3969)\n",
" n_comptes pct\n",
"cluster_carmignac_portfolio_long-short \n",
"0 107 17.7\n",
"1 54 8.9\n",
"2 333 55.0\n",
"3 111 18.3\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABM8AAAGGCAYAAABseKbNAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3XdYU9cbwPFv2AKKTJWpgKAiuHFrxdXWUUdr3bta92yddW+ttYqr7j3qwGqdtfqzrYp1b+u2LpQle0ju7w9KNAIaIojj/TxPHs255568l5vcJG/OUCmKoqCjYsWKoVKpMt2uUqno06cPPXr00LVJIYQQQgghhBBCCCHeWqqsJM+OHTuGoih06NCBOXPmYGVlpdlmbGyMo6MjBQoUyJFAhRBCCCGEEEIIIYR404yyUtnf35+nT5/StGlTSpYsSaFChXIqLiGEEEIIIYQQQgghcp1BVncwMjJi9+7dpKSk5EQ8QgghhBBCCCGEEEK8NbKcPAOoVKkSf//9d3bHIoQQQgghhBBCCCHEWyVLwzbT1KhRg++//55//vkHHx8f8uTJo7W9du3a2RKcEEIIIYQQQgghhBC5KUsLBqQpVqxY5g2qVFy6dOm1ghJCCCGEEEIIIYQQ4m2gV/JMCCGEEEIIIYQQQogPgV5zngkhhBBCCCGEEEII8SHQa84zgGPHjrF06VKuX78OgIeHB127dqV8+fLZFpwQQgghhBBCCCGEELlJr55n27Zto1OnTpiZmdGuXTvatWuHmZkZHTt2ZPv27dkdoxDiFQICAhg6dGhuh5Erhg4dSkBAQG6HId4zae9tInsEBQXx8ccf4+Pjk+Uf2TJ6jXt7ezNnzpzsDPG9NnToUMqUKZPbYYhcFhsbS+XKlfnll19yO5Rcd/fuXby9vVmyZEluhyL+c+3aNUqUKME///yT26EIIUSG9Op5tmDBAr755hs6duyoKWvfvj3Lli1j3rx5NGrUKLviE+KDdufOHRYvXsxff/3Fo0ePMDY2xsvLi08++YQvv/wSMzOzHI8hPj6exYsX4+/vT8WKFXP88dKEh4czb948/vzzT+7fv4+FhQVOTk5UrFiRnj17YmFh8cZiyYoFCxbg6elJnTp1cjuUt05oaChLlizhwIEDPHjwAJVKhbu7O3Xq1KFt27bky5cvt0N87wwdOpStW7dq7ltYWODs7EyTJk1o27YtJiYm2fI4ISEhbNy4kTp16lC8eHGtbdevX2fYsGFUr16dbt26vZHrVlZ5e3vTpk0bRo0alduhZElsbCxLlixh79693L17F1NTUwoWLEiFChX46quvKFCgwBuPKavvGcHBwbRv3z7T7TNnzqRBgwbZGeIHa+XKlVhYWGj9PefMmUNgYCBHjhzBxsZGU/7gwQPatWtHVFQUy5Ytw8fHJ1tiSE5O5rPPPuP69et8++23dOnSJVvafVdcu3aNXbt20bRpU5ydnXM7nLeKp6cnNWvWZPbs2QQGBuZ2OEIIkY5eybN///2XWrVqpSsPCAhg5syZrx2UEAIOHjxIv379MDEx4bPPPsPLy4vk5GROnDjB9OnTuXbtGuPHj8/xOOLj4wkMDKR3795vLHkWGRlJ8+bNiYmJoXnz5ri7uxMZGcmVK1dYt24drVq10iTPxo8fz9u07snChQupX7++JM9ecPbsWbp160ZcXByNGzfWfBE7f/48ixYt4vjx4yxdujSXo3zmfeqNYGJiwoQJEwCIjo5mz549TJ06lXPnzvHDDz9ky2M8evSIwMBAnJyc0iXPjh07hlqtZsSIEbi5uWXL4509exZDQ8NsaetdlZycTNu2bblx44YmGRoXF8fVq1fZsWMHdevWzbXkmT7vGe3atcPX1zddeenSpbMxug9XcnIyK1eupGPHjq987YSEhNC+fXuePHmSrYkzgNWrV/PgwYNsa+9dc+3aNQIDA/H395fkWQZatmxJt27duHPnDq6urrkdjhBCaNEreVaoUCGOHDmS7kPw4cOHKVSoULYEJsSH7N9//2XAgAE4OjqyYsUKHBwcNNvatGnD7du3OXjwYO4FmA3i4uIwNzfPcNumTZu4f/8+69ato2zZslrbYmJiMDY21tx//v/vK7VaTXJyMqamprkdil6ioqLo3bs3hoaGbN26FQ8PD63tAwYMYOPGjdnyWPHx8eTJk+e128muHllvAyMjIz777DPN/datW/PFF1+wc+dOhg4d+loJlqdPn6JWq19aJywsDIC8efPq/TgveldfC9npt99+4+LFi8yYMSNdj//ExESSk5PfaDxp1yl9lS9fno8//jgbI9L2svecD8HBgwcJDw/nk08+eWm9tMRZZGQkS5cupWTJktkWQ1hYGHPnzqVr167Mnj0729pNk13Xf5E99PnsUqVKFaysrNi6dSv9+vXLweiEECLr9JrzrFOnTkyYMIHRo0cTFBREUFAQo0aNYtKkSXTu3Dm7YxTig7N48WLi4uKYOHGiVuIsjZubGx06dMh0/zlz5uDt7Z2ufMuWLXh7e3P37l1N2blz5+jSpQsVK1bEz8+PgIAAhg0bBqTOCVK5cmUAAgMD8fb2TjfX0PXr1+nbty/+/v74+vrSrFkz9u/fn+HjHjt2jDFjxlC5cmVq1qyZafx37tzB0NAwwx4HlpaWWh/EMpoPKSIigm+++YayZctSvnx5hgwZwuXLl/H29mbLli1a+5YpU4aQkBB69uxJmTJlqFSpElOnTiUlJUWrzSVLltCyZUvN36lZs2bs3r1bq463tzdxcXFs3bpV87dKm4sus7nZMjpX3t7ejBs3jl9++YUGDRrg6+vLH3/8AaR+sRk2bBhVqlShZMmSNGjQgE2bNqVrd9WqVTRo0IBSpUpRoUIFmjVrlmtzUq5fv56QkBCGDh2aLnEGYGdnR8+ePTX3f/vtN7p160a1atUoWbIkderUYe7cuenOSbt27WjYsCHnz5+nTZs2lCpVipkzZ2rNZbNmzRpq165NqVKl6Ny5Mw8ePEBRFObOnUuNGjXw8/OjR48eREZGpmv7xTnP7t27x9dff03p0qWpXLkykyZN4o8//sDb25vg4OB0cV27do127dpRqlQpqlevzqJFi7TaS0pK4scff6RZs2aUK1eO0qVL07p1a44ePZrub6RWq1mxYgWNGjXC19eXSpUq0aVLF86dO6fzeUhjYGCAv7+/5pgg9Uvt8OHDqVKlCr6+vjRu3FhruCdozxG0fPly6tSpg6+vL2vXruXzzz8HYNiwYZrn/pYtWwgICNBcLypXrpzu+rFmzRoaNGhAyZIlqVatGmPHjiUqKuqVx5DRnGcXL16ka9eulC1bljJlytChQwdOnz6d5b9PZuLi4pgyZQo1a9akZMmS1K9fnyVLlqTr+Zr2+v3tt99o2LCh5nV66NChdG0GBwfTrFkzfH19qVOnDuvXr8/0+v2if//9FyDdDwyQmly0tLRMV67LtS6rx/n8dWrdunWvfM/QV9rz7/lr+POxPP8YaX/Da9euMWjQICpUqEDr1q2B1ITv3LlzqVOnDiVLltSMmkhKStJqMyAggO7du/Pnn3/y2Wef4evry6effsrevXvTPX5UVBQTJ07U/M3q1q3LTz/9lC6xrMv7SNrx6Poc0tVvv/2Gk5PTS3vzPHr0iPbt2xMWFsaSJUsy7An4OmbMmEGRIkVo3Ljxa7eV2fUfdLuePW/58uXUqlULPz8/2rZtm27OrczmwMzoff3XX3+lWbNmlClThrJly9KoUSNWrFgBpH4WSksItW/fXvP6eP7943nBwcGaOi/edJnr9a+//qJVq1aUL1+eMmXKUL9+/XQjhBITE5kzZw7169fH19eXatWq0bt3b+7cuaOp8zrXhKx+djE2Nsbf3z/d50ghhHgb6NXzrHXr1tjb27N06VLNm767uzs//PCDDFUSIhscOHAAFxeXDL8UZaewsDC6dOmCtbU13bp1I1++fNy9e5d9+/YBYGNjw5gxYxgzZgx169albt26AJovdlevXqVVq1YUKFCAr776CnNzc3bt2kWvXr2YM2eOpn6asWPHYmNjQ69evYiLi8s0LicnJ1JSUti2bRt
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Carmignac Portfolio Long-Short European Equities:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_aum_qty_mean months_since_last_tx_fund aum_final_to_peak aum_drawdown_last buy_on_perf_rate\n",
"cluster_carmignac_portfolio_long-short \n",
"0 0.125 1.688 1.000 0.537 7.833 3.0 1.0 0.0 0.0\n",
"1 0.041 4.727 0.348 1.000 5.110 56.0 0.0 1.0 0.0\n",
"2 0.119 6.673 0.635 0.000 6.731 32.0 0.0 1.0 0.0\n",
"3 0.056 3.823 0.412 -1.000 4.991 29.0 0.0 1.0 0.0\n",
"\n",
"============================================================\n",
"FUND : Carmignac Portfolio Climate Transition\n",
"============================================================\n",
" k silhouette davies_bouldin min_cluster_size\n",
" 2 0.6663 0.6410 156\n",
" 3 0.5630 0.7450 57\n",
" 4 0.4067 0.9958 37\n",
" 5 0.2259 1.2463 37\n",
" 6 0.2268 1.2256 18\n",
"→ K retenu : 2 (silhouette=0.6663)\n",
" n_comptes pct\n",
"cluster_carmignac_portfolio_climate_tr \n",
"0 156 12.2\n",
"1 1122 87.8\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Carmignac Portfolio Climate Transition:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_aum_qty_mean months_since_last_tx_fund aum_final_to_peak aum_drawdown_last buy_on_perf_rate\n",
"cluster_carmignac_portfolio_climate_tr \n",
"0 0.679 5.287 0.962 -0.049 7.379 7.0 0.0 1.0 0.044\n",
"1 0.025 1.503 0.583 -0.792 4.215 92.0 0.0 1.0 0.000\n",
"\n",
"============================================================\n",
"FUND : Carmignac Credit 2027\n",
"============================================================\n",
" k silhouette davies_bouldin min_cluster_size\n",
" 2 0.2840 1.4117 134\n",
" 3 0.3331 1.1580 49\n",
" 4 0.3456 1.0712 39\n",
" 5 0.3182 1.2795 40\n",
" 6 0.3321 1.1970 26\n",
"→ K retenu : 4 (silhouette=0.3456)\n",
" n_comptes pct\n",
"cluster_carmignac_credit_2027 \n",
"0 47 15.9\n",
"1 118 40.0\n",
"2 91 30.8\n",
"3 39 13.2\n"
]
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1400x400 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Medians — Carmignac Credit 2027:\n",
" flow_freq gross_flow_to_aum avg_n_isin_held flow_direction_balance log_aum_qty_mean months_since_last_tx_fund aum_final_to_peak aum_drawdown_last buy_on_perf_rate\n",
"cluster_carmignac_credit_2027 \n",
"0 0.130 5.183 0.634 0.041 6.130 7.0 0.000 1.000 0.0\n",
"1 0.851 2.353 1.577 0.574 9.763 0.0 0.842 0.158 0.0\n",
"2 0.083 1.024 1.000 1.000 6.967 16.0 1.000 0.000 0.0\n",
"3 0.000 0.000 0.912 -0.869 6.620 130.0 0.405 0.595 0.0\n",
"\n",
"============================================================\n",
"SUMMARY — Fund-level clustering\n",
"============================================================\n",
" Carmignac Patrimoine : K=2, sil=0.5054, n=3153\n",
" Carmignac Sécurité : K=2, sil=0.5729, n=1622\n",
" Carmignac Investissement : K=2, sil=0.4306, n=2192\n",
" Carmignac Portfolio Sécurité : K=2, sil=0.8029, n=1161\n",
" Carmignac Portfolio Flexible Bond : K=2, sil=0.6085, n=1087\n",
" Carmignac Emergents : K=3, sil=0.4735, n=1779\n",
" Carmignac Portfolio Patrimoine : K=3, sil=0.6446, n=1143\n",
" Carmignac Portfolio Global Bond : K=3, sil=0.8452, n=1716\n",
" Carmignac Portfolio Credit : K=2, sil=0.7000, n=1016\n",
" Carmignac Portfolio Emerging Patrimoine : K=3, sil=0.5964, n=1135\n",
" Carmignac Portfolio Grande Europe : K=2, sil=0.3729, n=1386\n",
" Carmignac Court Terme : K=2, sil=0.4725, n=525\n",
" Carmignac Portfolio Long-Short European : K=4, sil=0.3969, n=605\n",
" Carmignac Portfolio Climate Transition : K=2, sil=0.6663, n=1278\n",
" Carmignac Credit 2027 : K=4, sil=0.3456, n=295\n",
"\n",
"============================================================\n",
"ARI — coherence: global clustering × fund clustering\n",
"============================================================\n",
" Carmignac Patrimoine : ARI=0.0238 (n=3000)\n",
" Carmignac Sécurité : ARI=0.0119 (n=1477)\n",
" Carmignac Investissement : ARI=0.0426 (n=2053)\n",
" Carmignac Portfolio Sécurité : ARI=0.0820 (n=1047)\n",
" Carmignac Portfolio Flexible Bond : ARI=-0.0448 (n=944)\n",
" Carmignac Emergents : ARI=0.0153 (n=1640)\n",
" Carmignac Portfolio Patrimoine : ARI=0.0118 (n=1029)\n",
" Carmignac Portfolio Global Bond : ARI=0.0799 (n=1584)\n",
" Carmignac Portfolio Credit : ARI=0.0090 (n=901)\n",
" Carmignac Portfolio Emerging Patrimoine : ARI=-0.0332 (n=996)\n",
" Carmignac Portfolio Grande Europe : ARI=-0.0264 (n=1247)\n",
" Carmignac Court Terme : ARI=-0.0347 (n=423)\n",
" Carmignac Portfolio Long-Short European : ARI=0.0516 (n=495)\n",
" Carmignac Portfolio Climate Transition : ARI=-0.0456 (n=1141)\n",
" Carmignac Credit 2027 : ARI=0.0470 (n=238)\n"
]
}
],
"source": [
"print(\"=== Available funds (top 20 by AUM) ===\")\n",
"top_funds_aum = (\n",
" df_aum.groupby(FUND_COL)[AUM_VAL_COL].sum()\n",
" .sort_values(ascending=False)\n",
" .head(20)\n",
")\n",
"print(top_funds_aum.to_string())\n",
"\n",
"# Step 1. Select top funds by AUM with minimum account count\n",
"# Critères : top 15 par AUM total + au moins 20 clients\n",
"min_clients_per_fund = 20\n",
"n_top_funds = 15\n",
"\n",
"aum_by_fund_total = df_aum.groupby(FUND_COL)[AUM_VAL_COL].sum()\n",
"clients_by_fund = df_aum.groupby(FUND_COL)[ID_COL].nunique()\n",
"\n",
"valid_funds = (\n",
" aum_by_fund_total[\n",
" clients_by_fund >= min_clients_per_fund\n",
" ]\n",
" .sort_values(ascending=False)\n",
" .head(n_top_funds)\n",
" .index.tolist()\n",
")\n",
"print(f\"\\nSelected funds ({len(valid_funds)}) :\")\n",
"for f in valid_funds:\n",
" print(f\" {f} : {clients_by_fund[f]} clients, AUM={aum_by_fund_total[f]:.0f}\")\n",
"\n",
"# Step 2. Build account × fund monthly panel\n",
"df_rel_m_fund = df_rel_m.copy()\n",
"df_rel_m_fund = df_rel_m_fund.merge(\n",
" df_aum[[ID_COL, ISIN_COL, \"month\", FUND_COL]].drop_duplicates(),\n",
" on=[ID_COL, ISIN_COL, \"month\"],\n",
" how=\"left\"\n",
")\n",
"\n",
"# Monthly panel par compte x fund\n",
"tmp_fund = df_rel_m_fund.copy()\n",
"tmp_fund[\"isin_held_flag\"] = (tmp_fund[\"aum_qty\"] > 0).astype(int)\n",
"tmp_fund[\"isin_active_flag\"] = (tmp_fund[\"gross_flow_qty\"] > 0).astype(int)\n",
"\n",
"df_month_fund = (\n",
" tmp_fund.dropna(subset=[FUND_COL])\n",
" .groupby([ID_COL, FUND_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",
" ret_fund_m = (\"ret_fund_m\", \"mean\"),\n",
" )\n",
" .sort_values([ID_COL, FUND_COL, \"month\"])\n",
" .reset_index(drop=True)\n",
")\n",
"\n",
"df_month_fund[\"active_month\"] = (df_month_fund[\"gross_flow_qty\"] > 0).astype(int)\n",
"df_month_fund[\"flow_direction\"] = np.where(\n",
" df_month_fund[\"gross_flow_qty\"] > 0,\n",
" df_month_fund[\"net_flow_qty\"] / df_month_fund[\"gross_flow_qty\"],\n",
" np.nan\n",
")\n",
"df_month_fund[\"sub_share\"] = np.where(\n",
" df_month_fund[\"gross_flow_qty\"] > 0,\n",
" df_month_fund[\"sub_qty\"] / df_month_fund[\"gross_flow_qty\"],\n",
" np.nan\n",
")\n",
"df_month_fund[\"aum_peak\"] = df_month_fund.groupby(\n",
" [ID_COL, FUND_COL]\n",
")[\"aum_qty\"].cummax()\n",
"df_month_fund[\"aum_drawdown\"] = np.where(\n",
" df_month_fund[\"aum_peak\"] > 0,\n",
" 1 - df_month_fund[\"aum_qty\"] / df_month_fund[\"aum_peak\"],\n",
" np.nan\n",
")\n",
"\n",
"# Lag performance pour réactivité\n",
"df_month_fund[\"ret_fund_lag1\"] = df_month_fund.groupby(\n",
" [ID_COL, FUND_COL]\n",
")[\"ret_fund_m\"].shift(1)\n",
"df_month_fund[\"buy_on_perf\"] = (\n",
" (df_month_fund[\"net_flow_qty\"] > 0) &\n",
" (df_month_fund[\"ret_fund_lag1\"] > 0)\n",
").astype(int)\n",
"df_month_fund[\"has_perf\"] = df_month_fund[\"ret_fund_lag1\"].notna().astype(int)\n",
"\n",
"print(\"\\ndf_month_fund shape:\", df_month_fund.shape)\n",
"\n",
"# Step 3. Feature engineering per account × fund\n",
"reference_date = df_month_fund[\"month\"].max()\n",
"\n",
"# months_since_last_tx par fund\n",
"last_active_fund = (\n",
" df_month_fund[df_month_fund[\"active_month\"] == 1]\n",
" .groupby([ID_COL, FUND_COL])[\"month\"]\n",
" .max()\n",
" .reset_index(name=\"last_active_month\")\n",
")\n",
"last_active_fund[\"months_since_last_tx_fund\"] = (\n",
" (reference_date.to_period(\"M\") -\n",
" last_active_fund[\"last_active_month\"].dt.to_period(\"M\"))\n",
" .apply(lambda x: x.n)\n",
")\n",
"\n",
"df_client_fund = (\n",
" df_month_fund.groupby([ID_COL, FUND_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",
" flow_direction_mean = (\"flow_direction\", \"mean\"),\n",
" sub_share_mean = (\"sub_share\", \"mean\"),\n",
" aum_drawdown_last = (\"aum_drawdown\", \"last\"),\n",
" buy_on_perf_months = (\"buy_on_perf\", \"sum\"),\n",
" has_perf_months = (\"has_perf\", \"sum\"),\n",
" )\n",
")\n",
"\n",
"# Merge months_since_last_tx\n",
"df_client_fund = df_client_fund.merge(\n",
" last_active_fund[[ID_COL, FUND_COL, \"months_since_last_tx_fund\"]],\n",
" on=[ID_COL, FUND_COL], how=\"left\"\n",
")\n",
"max_months = df_client_fund[\"months_since_last_tx_fund\"].max()\n",
"df_client_fund[\"months_since_last_tx_fund\"] = (\n",
" df_client_fund[\"months_since_last_tx_fund\"].fillna(max_months + 1)\n",
")\n",
"\n",
"# Ratios protégés\n",
"df_client_fund[\"gross_flow_to_aum\"] = np.where(\n",
" df_client_fund[\"aum_qty_mean\"] > 1,\n",
" df_client_fund[\"gross_flow_qty_sum\"] / df_client_fund[\"aum_qty_mean\"],\n",
" np.nan\n",
")\n",
"df_client_fund[\"flow_direction_balance\"] = np.where(\n",
" df_client_fund[\"gross_flow_qty_sum\"] > 0,\n",
" df_client_fund[\"net_flow_qty_sum\"] / df_client_fund[\"gross_flow_qty_sum\"],\n",
" np.nan\n",
")\n",
"df_client_fund[\"aum_final_to_peak\"] = np.where(\n",
" df_client_fund[\"aum_qty_max\"] > 0,\n",
" np.clip(df_client_fund[\"aum_qty_last\"] / df_client_fund[\"aum_qty_max\"], 0, 1),\n",
" np.nan\n",
")\n",
"df_client_fund[\"log_aum_qty_mean\"] = np.log1p(\n",
" df_client_fund[\"aum_qty_mean\"].clip(lower=0)\n",
")\n",
"df_client_fund[\"buy_on_perf_rate\"] = np.where(\n",
" df_client_fund[\"has_perf_months\"] >= 6,\n",
" df_client_fund[\"buy_on_perf_months\"] / df_client_fund[\"has_perf_months\"],\n",
" np.nan\n",
")\n",
"\n",
"# Filtre qualité\n",
"df_client_fund = df_client_fund[\n",
" (df_client_fund[\"n_months\"] >= 6) &\n",
" (df_client_fund[\"aum_qty_mean\"] > 0) &\n",
" (df_client_fund[FUND_COL].isin(valid_funds))\n",
"].copy()\n",
"\n",
"print(\"df_client_fund shape:\", df_client_fund.shape)\n",
"print(\"\\nComptes par fund :\")\n",
"print(df_client_fund.groupby(FUND_COL)[ID_COL].nunique().sort_values(ascending=False))\n",
"\n",
"# 4. Features pour le clustering par fund ───────────────────────────────\n",
"fund_features = [\n",
" \"flow_freq\",\n",
" \"gross_flow_to_aum\",\n",
" \"avg_n_isin_held\",\n",
" \"flow_direction_balance\",\n",
" \"log_aum_qty_mean\",\n",
" \"months_since_last_tx_fund\",\n",
" \"aum_final_to_peak\",\n",
" \"aum_drawdown_last\",\n",
" \"buy_on_perf_rate\",\n",
"]\n",
"\n",
"# Step 5. Clustering loop across all selected funds\n",
"FUND_RESULTS = {}\n",
"\n",
"for fund in valid_funds:\n",
" print(f\"\\n{'='*60}\")\n",
" print(f\"FUND : {fund}\")\n",
" print(f\"{'='*60}\")\n",
"\n",
" df_f = df_client_fund[df_client_fund[FUND_COL] == fund].copy()\n",
" feats = [c for c in fund_features if c in df_f.columns]\n",
"\n",
" # Preprocessing\n",
" d = df_f.copy()\n",
" d[\"flow_direction_balance\"] = d[\"flow_direction_balance\"].fillna(0)\n",
" d[\"buy_on_perf_rate\"] = d[\"buy_on_perf_rate\"].fillna(0)\n",
"\n",
" for col in [\"avg_n_isin_held\", \"months_since_last_tx_fund\",\n",
" \"aum_drawdown_last\", \"aum_final_to_peak\"]:\n",
" if col not in d.columns:\n",
" continue\n",
" vals = d[col].to_numpy(dtype=float)\n",
" med = np.nanmedian(vals)\n",
" mad = np.nanmedian(np.abs(vals - med)) * 1.4826\n",
" if mad > 0:\n",
" d[col] = np.clip(vals, med - 3*mad, med + 3*mad)\n",
" else:\n",
" d[col] = np.clip(vals, 0, np.nanpercentile(vals, 95))\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_f = d[feats].fillna(d[feats].median()).to_numpy()\n",
" X_f_scaled = RobustScaler().fit_transform(X_f)\n",
"\n",
" # K-selection\n",
" best_k = 2\n",
" best_sil = -1\n",
" rows_k = []\n",
" max_k = min(6, len(df_f) // 20)\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_f_scaled)\n",
" # Vérifier que les clusters ne sont pas trop déséquilibrés\n",
" sizes = pd.Series(labels).value_counts()\n",
" if sizes.min() < 10:\n",
" continue\n",
" sil = silhouette_score(X_f_scaled, labels)\n",
" db = davies_bouldin_score(X_f_scaled, labels)\n",
" rows_k.append({\n",
" \"k\": k,\n",
" \"silhouette\": round(sil, 4),\n",
" \"davies_bouldin\": round(db, 4),\n",
" \"min_cluster_size\": sizes.min(),\n",
" })\n",
" if sil > best_sil:\n",
" best_sil = sil\n",
" best_k = k\n",
"\n",
" df_k = pd.DataFrame(rows_k)\n",
" print(df_k.to_string(index=False))\n",
" print(f\"→ K retenu : {best_k} (silhouette={best_sil:.4f})\")\n",
"\n",
" # Clustering final\n",
" km_final = KMeans(n_clusters=best_k, n_init=50, random_state=RANDOM_STATE)\n",
" cluster_col = f\"cluster_{fund.lower().replace(' ','_')[:30]}\"\n",
" df_f[cluster_col] = km_final.fit_predict(X_f_scaled)\n",
"\n",
" # Sizes\n",
" counts = df_f[cluster_col].value_counts().sort_index()\n",
" props = counts / counts.sum() * 100\n",
" print(pd.DataFrame({\"n_comptes\": counts, \"pct\": props.round(1)}))\n",
"\n",
" # Heatmap\n",
" profile_vars_fund = [c for c in fund_features if c in df_f.columns]\n",
" prof = plot_heatmap(\n",
" df_f, profile_vars_fund, cluster_col,\n",
" title=f\"Cluster Signatures — {fund[:40]} (K={best_k}, robust z-score)\",\n",
" figsize=(14, 4)\n",
" )\n",
" print(f\"\\nMedians — {fund}:\")\n",
" print(prof.round(3).to_string())\n",
"\n",
" FUND_RESULTS[fund] = {\n",
" \"df\": df_f,\n",
" \"cluster_col\": cluster_col,\n",
" \"k\": best_k,\n",
" \"silhouette\": best_sil,\n",
" \"profile\": prof,\n",
" \"n\": len(df_f),\n",
" }\n",
"\n",
"# Step 6. Summary\n",
"print(\"\\n\" + \"=\"*60)\n",
"print(\"SUMMARY — Fund-level clustering\")\n",
"print(\"=\"*60)\n",
"for fund, res in FUND_RESULTS.items():\n",
" print(f\" {fund[:40]:40s} : K={res['k']}, \"\n",
" f\"sil={res['silhouette']:.4f}, n={res['n']}\")\n",
"\n",
"# 7. Croisement avec le clustering global ───────────────────────────────\n",
"print(\"\\n\" + \"=\"*60)\n",
"print(\"ARI — coherence: global clustering × fund clustering\")\n",
"print(\"=\"*60)\n",
"\n",
"dfc_fund_cross = dfc[[ID_COL, \"cluster_k4\"]].copy()\n",
"\n",
"for fund, res in FUND_RESULTS.items():\n",
" cluster_col = res[\"cluster_col\"]\n",
" df_f = res[\"df\"][[ID_COL, cluster_col]].copy()\n",
" dfc_fund_cross = dfc_fund_cross.merge(df_f, on=ID_COL, how=\"left\")\n",
"\n",
" mask = dfc_fund_cross[cluster_col].notna()\n",
" if mask.sum() < 10:\n",
" continue\n",
" labels_global = dfc_fund_cross.loc[mask, \"cluster_k4\"].values\n",
" labels_fund = dfc_fund_cross.loc[mask, cluster_col].values\n",
" ari = adjusted_rand_score(labels_global, labels_fund)\n",
" print(f\" {fund[:40]:40s} : ARI={ari:.4f} (n={mask.sum()})\")"
]
},
{
"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": 21,
"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",
" 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": [
"# ============================================================\n",
"# PART 2 — TOP 400 ACCOUNTS CLUSTERING\n",
"# ============================================================\n",
"\n",
"# 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",
"dfc_top400[\"log_aum_qty_mean\"] = np.log1p(dfc_top400[\"aum_qty_mean\"].clip(lower=0))\n",
"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",
" \"log_aum_qty_mean\",\n",
" \"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",
"for col in [\"log_aum_qty_mean\"]:\n",
" 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",
" \"log_aum_qty_mean\",\n",
" \"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",
"execution_count": 22,
"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",
"execution_count": 23,
"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": "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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 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": "iVBORw0KGgoAAAANSUhEUgAAAxYAAAGGCAYAAADmRxfNAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAThFJREFUeJzt3XlYVHX///EXDCAirqhk7lkzLmDgrmmampamuWRqLuWWmUuZpXBbpqahprlQuYtrbrlkRWrlbZmit5kmLm3uWy6guQACw/z+8Md8HVkEDzCgz8d1ceV8zvY+M2em8zrnc85xsdlsNgEAAACAAa7OLgAAAABA3kewAAAAAGAYwQIAAACAYQQLAAAAAIYRLAAAAAAYRrAAAAAAYBjBAgAAAIBhBAsAAAAAhhEsAAAAABhGsAByGYvForFjxzq7jAdC06ZN1b9/f2eXIenW5x4aGursMoAH2ty5c/XMM88oKSnJ2aWkaujQoXrjjTecXQaQJoIFkENOnjypUaNGqVmzZvL391eNGjXUpUsXLVq0SHFxcc4uz2l+/PFHdqiz2fnz5xUaGqrDhw87uxRDLBZLhv527dqVo3X98ssv9mVHR0enGH7+/Hm98cYbqlWrlmrUqKEBAwbo1KlTqc5r9erVevbZZ+Xv768WLVpoyZIl2V1+njRr1ix9//33WTrP69eva968eerXr59cXf9v9yitgz2zZs2SxWJRcHCwoSBy5MgRTZo0Sc8//7wCAwPVsGFDvfrqq4qMjEwxbr9+/bR582b9/vvv97w8IDu5ObsA4EGwdetWvfHGG/Lw8NDzzz8vs9mshIQE7dmzRx999JH+/vtvffDBB84u0yl+/PFHLVu2TIMHD3Z2KfetCxcu6JNPPlHp0qVVpUoVZ5dzzyZNmuTw+ssvv9T27dtTtFeqVCnHakpKStK4cePk5eWlmJiYFMNv3Lihnj176tq1a+rfv7/c3d21cOFCde/eXevXr1fRokXt465YsULvv/++WrZsqV69eumXX37RuHHjFBsbq1dffTXH1ikvmD17tlq2bKnmzZtn2Ty/+OILJSYm6rnnnrvruHPmzNHUqVPVvn17jR8/3iGI3Mtyv/jiC7Vo0UIvvfSSrl27ppUrV6pz586aN2+eGjRoYB+3atWq8vPz04IFC1Js90BuQLAAstmpU6c0dOhQPfzww1q0aJFKlixpH9atWzedOHFCW7duzdGakpKSlJCQoHz58mX5vGNiYuTl5ZXl80Xuk9Of9fPPP+/w+rffftP27dtTtOeklStX6ty5c3rhhRe0ePHiFMM///xzHT9+XKtXr1b16tUlSY0aNVKbNm0UFhamt956S5IUFxenqVOnqkmTJpoxY4Yk6cUXX1RSUpJmzpypzp07q3Dhwjm3Yg+gtWvXqmnTpnf9XZw3b56mTJmidu3a6cMPPzQUKiSpdevWGjRokAoUKGBv69ixo1q1aqXQ0FCHYCFJzz77rEJDQ3Xjxg2HaYDcgK5QQDabN2+eYmJiNH78eIdQkax8+fJ6+eWXU7R///33eu655+Tn56fWrVvrp59+chgeFBSkpk2bppguNDRUFovFoS35VP6GDRvUunVr+fv7a9u2bVq7dq0sFov27NmjkJAQ1atXTwEBARo4cGCqXTruFBQUpMDAQJ08eVL9+vVTYGCg3n77bUm3uocMGTJETZo0kZ+fnxo3bqwPP/zQodtXUFCQli1bZq8x+S9ZUlKSFi5caK+5QYMGGjVqlP7991+HOiIjI9WnTx/VrVtX1atXV9OmTRUcHHzX+pP9/PPPev755+Xv769WrVpp8+bN9mGnTp2SxWLRwoULU0z366+/ymKx6Ouvv053/jdv3lRoaKhatmwpf39/NWzYUIMGDdLJkyfTnCYzn+/27dvVtWtX1apVS4GBgWrZsqU+/vhjSdKuXbv0wgsvSJKCg4Pt7/HatWvt0//222/q06ePatasqccff1zdu3fXnj17Ul3u33//rWHDhql27dp66aWX0l1vZ4iJidGECRPUuHFj+fn5qWXLlpo/f75sNpvDeLd/J5I/lw4dOmj37t0ZXtaVK1c0bdo0DRkyRIUKFUp1nE2bNsnf398eKqRbZ1Tq16+vb7/91t62a9cuXblyJcV72q1bN8XExNz14MOZM2c0evRotWzZUtWrV1fdunU1ZMgQnT59OsW4V69e1YcffqimTZvKz89PTz75pIYPH+7wnc/INpuR9/r06dMptrdkd15XlLyNnThxQkFBQapVq5Zq1qyp4OBgxcbGOkwXExOjdevW2bfnoKAgSbe6M40fP96+bvXr11evXr108ODBdN+/U6dO6Y8//kixE3+nsLAwffTRR2rbtq1CQkIMhwpJ8vPzSxEQihYtqlq1auno0aMpxm/QoIFiYmK0Y8cOw8sGshpnLIBs9t///ldly5ZVjRo1MjzNnj17tHnzZr300ksqUKCAlixZoiFDhui///2vQ9eJzNi5c6e+/fZbdevWTUWLFlXp0qV19epVSdK4ceNUqFAhDRo0SGfOnNGiRYs0duxYTZs27a7zTUxMtO+UjhgxQp6enpKkjRs3Ki4uTl27dlWRIkW0f/9+LV26VP/884/9iGznzp114cKFVLuzSNKoUaO0bt06dejQQT169NDp06e1bNkyHTp0SMuXL5e7u7uioqLUp08fFS1aVK+++qoKFSqk06dP67vvvsvQ+3L8+HENHTpUXbp0Ufv27bVmzRq98cYbmjdvnp544gn7Z7dhwwa98sorDtN+9dVXKlCggJo1a5bm/K1Wq/r376+IiAi1bt1aPXv21I0bN7R9+3b9+eefKleuXIbqTMtff/2l/v37y2KxaMiQIfLw8NCJEyf066+/Srq1EztkyBDNmDFDnTt3Vs2aNSXJvj1GRESoX79+8vPz06BBg+Ti4qK1a9fq5Zdf1ueff+6wQyxJb7zxhsqXL6+hQ4em2Fl3NpvNpgEDBtjDVJUqVbRt2zZNmjRJ58+f13/+8x+H8Xfv3q3w8HD16NFDHh4eWr58ufr27avVq1fLbDbfdXnTp09XiRIl1KVLF3322WcphiclJemPP/5Qx44dUwzz9/fXzz//rOvXr8vb21uHDh2SdGsn83bVqlWTq6urDh8+nO6ZmcjISO3du1etW7fWQw89pDNnzmj58uXq2bOnvvnmG+XPn1/Sra5Z3bp105EjR9SxY0dVrVpVly9f1pYtW3T+/HkVK1YsQ9tsZt/rzHjzzTdVpkwZvfXWWzp06JBWr16tYsWK6Z133pF0q0vcu+++q+rVq+vFF1+UJPv36P3339emTZvUvXt3VapUSVeuXNGePXt05MgRVatWLc1l7t27V9KtrkZpWbRokSZMmKDnnntOEyZMSDVUZOSAjCR5e3vLw8Mj3XEuXryoIkWKpGh/9NFH5enpqV9//VVPP/10hpYH5BgbgGxz7do1m9lstg0YMCDD05jNZlu1atVsJ06csLcdPnzYZjabbUuWLLG3jRgxwvbUU0+lmH7GjBk2s9mcYp6VK1e2/fXXXw7ta9assZnNZtsrr7xiS0pKsrd/+OGHtipVqtiuXr2abq0jRoywmc1m2+TJk1MMi42NTdE2e/Zsm8VisZ05c8beNmbMmBT12mw22+7du21ms9m2YcMGh/affvrJof27776zmc1m2/79+9OtNTVPPfWUzWw22zZt2mRvu3btmu2JJ56wtWvXzt62YsUKm9lstv3999/2tvj4eFvdunVtI0aMSHcZX3zxhc1sNtvCwsJSDLv9PTebzbYZM2bYX2f08w0LC7OZzWZbVFRUmjXs37/fZjabbWvWrEmx/BYtWth69+7tUEtsbKytadOmtl69eqVY7ltvvZXu+uakO7ed5G3hs88+cxhv8ODBNovF4vCdMpvNNrPZbIuMjLS3nTlzxubv728bOHDgXZd9+PBhW5UqVWzbtm2z2Wz/9/7c/jlERUXZzGaz7ZNPPkkx/dKlS21ms9l25MgR+7pUqVIl1WXVq1fPNnTo0HTrSe37tnfvXpvZbLatW7fO3jZ9+nSb2Wy2bd68OcX4ydtARrbZjL7Xp06
"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",
"execution_count": 24,
"id": "b2716808",
"metadata": {},
"outputs": [
{
"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 C0 20.0 39.0 30.0 9.0 2.0\n",
"Global C1 0.0 100.0 0.0 0.0 0.0\n",
"Global C2 20.0 0.0 15.0 41.0 23.0\n",
"Global C3 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": null,
"id": "5a3ec2e8-d19f-43d5-80c7-5deb19c33197",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "e42c50aa-0343-47b9-b562-78137d63d5e9",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "fc913550-7c1d-44ee-aa36-e7b027b98e2e",
"metadata": {},
"outputs": [],
"source": []
}
],
"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
}