{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " annual_income_k spending_score\n", "count 600.000000 600.00000\n", "mean 67.911667 50.23500\n", "std 40.793145 32.06634\n", "min 15.000000 1.00000\n", "25% 30.000000 22.00000\n", "50% 59.500000 49.50000\n", "75% 107.000000 81.25000\n", "max 139.000000 99.00000\n", "Cluster name map: {0: 'Budget', 1: 'Affluent', 2: 'Moderate', 3: 'Luxury'}\n", " annual_income_k spending_score\n", "label \n", "Affluent 105.0 79.6\n", "Budget 29.6 80.7\n", "Luxury 106.7 20.1\n", "Moderate 30.3 20.5\n", "\n", "Sample (income=$110k, spending=85): cluster 1 → Affluent\n" ] } ], "source": [ "import numpy as np\n", "import pandas as pd\n", "from sklearn.cluster import KMeans\n", "from sklearn.preprocessing import StandardScaler\n", "from sklearn.pipeline import Pipeline\n", "\n", "np.random.seed(42)\n", "\n", "# Four natural customer clusters\n", "segments = [\n", " dict(n=150, income=(15, 45), spending=(1, 40)), # Budget\n", " dict(n=150, income=(15, 45), spending=(60, 100)), # Reckless\n", " dict(n=150, income=(75, 140), spending=(1, 40)), # Conservative\n", " dict(n=150, income=(75, 140), spending=(60, 100)), # Luxury\n", "]\n", "\n", "rows = []\n", "for s in segments:\n", " rows.append(pd.DataFrame({\n", " 'annual_income_k': np.random.randint(*s['income'], s['n']).astype(float),\n", " 'spending_score': np.random.randint(*s['spending'], s['n']).astype(float),\n", " }))\n", "df = pd.concat(rows, ignore_index=True)\n", "\n", "print(df.describe())\n", "\n", "X = df[['annual_income_k', 'spending_score']]\n", "\n", "scaler = StandardScaler()\n", "X_scaled = scaler.fit_transform(X)\n", "\n", "kmeans = KMeans(n_clusters=4, random_state=42, n_init=10)\n", "kmeans.fit(X_scaled)\n", "\n", "# Build deterministic name map: sort clusters by income centroid ascending\n", "centers_original = scaler.inverse_transform(kmeans.cluster_centers_)\n", "income_order = np.argsort(centers_original[:, 0]) # cluster ids sorted by income\n", "income_rank = np.empty_like(income_order)\n", "income_rank[income_order] = np.arange(4) # rank of each cluster id\n", "rank_names = {0: 'Budget', 1: 'Moderate', 2: 'Affluent', 3: 'Luxury'}\n", "cluster_name_map = {int(cid): rank_names[int(rank)] for cid, rank in enumerate(income_rank)}\n", "print('Cluster name map:', cluster_name_map)\n", "\n", "df['cluster'] = kmeans.labels_\n", "df['label'] = df['cluster'].map(cluster_name_map)\n", "print(df.groupby('label')[['annual_income_k', 'spending_score']].mean().round(1))\n", "\n", "# Sample inference\n", "sample = pd.DataFrame([[110, 85]], columns=['annual_income_k', 'spending_score'])\n", "sample_scaled = scaler.transform(sample)\n", "pred_cluster = int(kmeans.predict(sample_scaled)[0])\n", "print(f'\\nSample (income=$110k, spending=85): cluster {pred_cluster} → {cluster_name_map[pred_cluster]}')" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/usr/local/lib/python3.12/dist-packages/huggingface_hub/utils/_auth.py:122: UserWarning: \n", "Error while fetching `HF_TOKEN` secret value from your vault: 'Requesting secret HF_TOKEN timed out. Secrets can only be fetched when running from the Colab UI.'.\n", "You are not authenticated with the Hugging Face Hub in this notebook.\n", "If the error persists, please let us know by opening an issue on GitHub (https://github.com/huggingface/huggingface_hub/issues/new).\n", " warnings.warn(\n" ] } ], "source": [ "from huggingface_hub import notebook_login\n", "notebook_login()" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Model saved locally: /content/ML_KMeans_CustomerSegmentation.joblib\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "493ae0155ee74bc3a50bcbdc9eca841b", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Processing Files (0 / 0) : | | 0.00B / 0.00B " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "cb65ed2e5fab412389e054a3c24ea299", "version_major": 2, "version_minor": 0 }, "text/plain": [ "New Data Upload : | | 0.00B / 0.00B " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "e76993f92ed34aea89e25a7f2bfaf198", "version_major": 2, "version_minor": 0 }, "text/plain": [ " ...stomerSegmentation.joblib: 100%|##########| 3.99kB / 3.99kB " ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Uploaded to https://huggingface.co/looh2/model\n" ] } ], "source": [ "import joblib\n", "from pathlib import Path\n", "from huggingface_hub import HfApi\n", "\n", "repo_id = \"looh2/model\"\n", "model_path = Path(\"ML_KMeans_CustomerSegmentation.joblib\")\n", "\n", "bundle = {'scaler': scaler, 'kmeans': kmeans, 'cluster_name_map': cluster_name_map}\n", "joblib.dump(bundle, model_path)\n", "print(f\"Model saved locally: {model_path.resolve()}\")\n", "\n", "api = HfApi()\n", "api.create_repo(repo_id=repo_id, repo_type=\"model\", exist_ok=True)\n", "api.upload_file(\n", " path_or_fileobj=str(model_path),\n", " path_in_repo=model_path.name,\n", " repo_id=repo_id,\n", " repo_type=\"model\",\n", ")\n", "print(f\"Uploaded to https://huggingface.co/{repo_id}\")" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "10c911f0fdaa498e8421397a9c8a2a89", "version_major": 2, "version_minor": 0 }, "text/plain": [ "ML_KMeans_CustomerSegmentation.joblib: 0%| | 0.00/3.99k [00:00