K-Means Clustering - clusters

K-means Clustering: A Practical Guide to Understanding Customer Patterns

Clustering is usually the first unsupervised technique anyone reaches for on a new dataset. It groups observations that behave alike, with no labels required. Among the many methods available, K-means clustering has stayed the dependable default. It is simple, fast and effective when the groups are reasonably compact.

K-means partitions observations into K clusters, and you pick K before fitting. A centroid summarizes each one, and it is the arithmetic mean of the observations assigned to it. It is computed from the data and need not land on any single observation. Those centroids give each group a compact profile, which is what makes the output easy to turn into a business or research decision. Remember that the segments are analytical constructs. They depend on the data and on the choices you make, so a different setup can produce different segments. Their labels (A, B and C in the figures below, or 0, 1 and 2 in code) are arbitrary identifiers with no inherent order.

The algorithm: assignment and update

The algorithm works through a short, repeating cycle. Start with K initial centroids. Assign every observation to its nearest one. Then move each centroid to the mean of the observations it just collected. These two steps, assignment and update, repeat until the assignments stop changing. Figure 1 shows the loop in motion. Watch how observations near a boundary switch groups during the assignment step, and how each centroid drifts toward the middle of its cluster during the update step. The figures use two features so the geometry is visible on the page. In practice K-means often works in many dimensions at once.

Figure 1 ยท Interactive How K-means finds clusters

Animated scatter plot in two feature dimensions. K-means repeats two steps: an assignment step that colors each observation by its nearest centroid, and an update step that moves each centroid to the mean of the observations assigned to it. The side panel reports the current step, the iteration number, and the inertia, meaning the within-cluster sum of squared distances. Inertia never rises from one step to the next. It settles once the assignments stop changing.

Clusters K
Figure 1. K-means alternates two steps. An assignment step sends each observation to its nearest centroid. An update step moves each centroid to the mean of what it just collected. Inertia, the within-cluster sum of squared distances, never rises across either. Use New start to re-initialize. The algorithm converges to a local optimum, so different starting centroids can produce different final clusters, and comparing several starts is part of the method.

The objective K-means minimizes

One quantity drives every pass. Standard K-means minimizes the within-cluster sum of squared Euclidean distances between observations and their centroids, usually reported as inertia. Because the distances are squared, an observation far from its centroid contributes far more than a nearby one. Figure 2 breaks this objective down term by term. Select an observation to reveal its squared distance, or drag a centroid away from the mean to watch the objective climb. The objective never increases during the assignment and update steps, which is why the loop in Figure 1 steadily settles toward a stable solution.

Figure 2 ยท Interactive What K-means is optimizing

Three clusters, each with its centroid. A line joins every observation to its centroid, and squaring those lengths and adding them up gives the K-means objective. Drag a centroid off the mean of its assigned observations and the objective climbs. It bottoms out when every centroid sits exactly on its cluster mean.

ΣΣ add the squared distances over every observation in every cluster ‖xi−μk2 squared distance from an observation to its centroid μk the centroid of cluster k: the mean of its members
Figure 2. Standard K-means minimizes the within-cluster sum of squared Euclidean distances to the centroids, the quantity reported as inertia. Each squared distance is one term in the double sum. A centroid contributes least when it sits at the mean of its cluster, which is exactly what the update step in Figure 1 computes. Squaring the distances gives distant observations more weight, and measuring them in the features’ own units is why feature scale moves the result.

Assumptions and data preparation

Two properties of that objective shape when K-means works well. First, squared Euclidean distance pushes the method toward compact, roughly spherical clusters. Elongated groups give it trouble, as do groups of very different sizes or densities, and anything non-convex. Second, the mean and the squared distance both react to scale and to extreme values. One feature on a large numeric range can dominate the result, and so can a handful of outliers. Standardizing features before clustering keeps each one contributing on comparable terms. Initialization matters as well. Because the algorithm only reaches a locally optimal solution, different starting centroids can lead to different clusters, as the New start control in Figure 1 demonstrates. Running the algorithm from several initializations, or using the K-means++ scheme, makes a poor local solution less likely.

Choosing the number of clusters

K-means takes the number of clusters as an input; K is a modeling choice you make in advance. A common aid is the elbow method, which plots inertia against K. Inertia never rises as K grows, because more centroids can only shrink squared distances. The fitted curve behaves the same way in practice. So you cannot pick K by minimizing it. Instead, you look for the elbow, the point where additional clusters stop meaningfully reducing inertia. Figure 3 lets you vary K and watch both the partition and the elbow curve respond. Treat the elbow as a practical guide. The most useful K also depends on how stable the clusters are across initializations, on domain knowledge, and on how you plan to use the segments. Real datasets do not always contain clean, well-separated groups, so a mathematically tidy solution is not automatically the most meaningful one.

Figure 3 ยท Interactive Choosing K, and why the elbow is a guide

Two linked views of the same dataset. On the left, the observations partitioned for a chosen K. On the right, inertia plotted against K. Watch the right-hand curve drop steeply, then flatten out near K equals 4. That flattening point is the elbow.

Clusters K
Clusters K4
Inertia (WCSS)1.32
AssessmentAt the elbow

A fourth cluster still cuts inertia sharply. A fifth barely moves it. That is the balance point, compact enough to mean something, simple enough to act on.

Read it carefully. The best achievable inertia never increases as K grows, so the smallest inertia always belongs to the largest K. The elbow points to where extra clusters stop paying off, and it is one standard heuristic among several. The most useful K also depends on domain knowledge and how the segments will be used.

Left: the dataset split into four clusters. Right: an elbow plot of inertia against K. Inertia falls as K increases, and the gains shrink sharply after about K = 4. That bend suggests four clusters, and domain knowledge settles the choice.

Figure 3. K-means takes the number of clusters as an input, so K is chosen before fitting. The best achievable inertia decreases monotonically with K, which makes the elbow, where additional clusters stop meaningfully reducing inertia, a standard heuristic for the choice. That choice also weighs interpretability, stability across initializations, and how the segments will be used. The four groups here are compact and well separated, so the bend at K = 4 is unusually sharp. The credit-card data in Figure 4 bends more gradually, and the k used there rests on interpretability as much as on the curve.

The rest of the article puts K-means to work on a real dataset. Standardize the features, read the elbow to choose K, fit the model, then interpret the customer segments it produces.

Example: K-means clustering on customer data

K-Means Segmentation. How K-Means groups nearly 9,000 credit card customers into four behavioral segments, each summarized by a centroid. The data are imputed, standardized, clustered and profiled. Key topics covered: Credit card customer data, Impute missing values, Clusters in PCA space, Standardize features, Fit K-Means, Profile each segment, Cluster heatmap, Choose k with the elbow method.

Hover any card to explore

Import dependencies

Python
import numpy as np
import pandas as pd

import matplotlib.pyplot as plt
import seaborn as sns
import plotly.graph_objects as go

from sklearn.experimental import enable_iterative_imputer
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA

from tableone import TableOne

# Set a global random seed for reproducibility
RANDOM_STATE = 42

# Configure plotting aesthetics
sns.set(style="whitegrid", context="notebook")
plt.rcParams["figure.figsize"] = (10, 6)
plt.rcParams["axes.titlesize"] = 14
plt.rcParams["axes.labelsize"] = 12

Load and inspect the data

Python
# Load the dataset
data_path = "CC GENERAL.csv"
df = pd.read_csv(data_path)

# Show first few rows
print("First 5 rows of the dataset:")
display(df.head())

# Basic info about the dataset
print("\nDataset info:")
df.info()

# Descriptive statistics for numerical columns
print("\nDescriptive statistics for numerical variables:")
display(df.describe().T)
Descriptive statistics for numerical variables:
Table 1

Descriptive statistics of the raw variables

Table 1. One row per numerical variable, produced by df.describe().T on the 8,950 credit-card customers before any preprocessing. The count column exposes the gaps imputed later: CREDIT_LIMIT has 8,949 values and MINIMUM_PAYMENTS 8,637. Spread matters more here than any average. PURCHASES runs from 0.00 to 49039.57 with a median of 361.28, while the frequency columns top out at 1.00, except CASH_ADVANCE_FREQUENCY, which reaches 1.50. Because K-means measures Euclidean distance, the dollar columns would dominate the frequency columns, which is why every feature is standardized before fitting.
countmeanstdmin25%50%75%max
BALANCE8950.01564.472081.530.00128.28873.392054.1419043.14
BALANCE_FREQUENCY8950.00.880.240.000.891.001.001.00
PURCHASES8950.01003.202136.630.0039.64361.281110.1349039.57
ONEOFF_PURCHASES8950.0592.441659.890.000.0038.00577.4040761.25
INSTALLMENTS_PURCHASES8950.0411.07904.340.000.0089.00468.6422500.00
CASH_ADVANCE8950.0978.872097.160.000.000.001113.8247137.21
PURCHASES_FREQUENCY8950.00.490.400.000.080.500.921.00
ONEOFF_PURCHASES_FREQUENCY8950.00.200.300.000.000.080.301.00
PURCHASES_INSTALLMENTS_FREQUENCY8950.00.360.400.000.000.170.751.00
CASH_ADVANCE_FREQUENCY8950.00.140.200.000.000.000.221.50
CASH_ADVANCE_TRX8950.03.256.820.000.000.004.00123.00
PURCHASES_TRX8950.014.7124.860.001.007.0017.00358.00
CREDIT_LIMIT8949.04494.453638.8250.001600.003000.006500.0030000.00
PAYMENTS8950.01733.142895.060.00383.28856.901901.1350721.48
MINIMUM_PAYMENTS8637.0864.212372.450.02169.12312.34825.4976406.21
PRC_FULL_PAYMENT8950.00.150.290.000.000.000.141.00
TENURE8950.011.521.346.0012.0012.0012.0012.00

Data preprocessing

Before clustering, we need to:

  1. Remove the identifier column (CUST_ID) from the feature set
  2. Inspect and handle missing values
  3. Construct a clean numerical feature matrix X suitable for downstream scaling and clustering

We first examine the extent of missingness in each column to understand which variables require imputation. The identifier column (CUST_ID) is excluded from the feature matrix, as it does not carry meaningful information for clustering.

Handling Missing Values

We fill the missing values with a model-based multivariate imputation strategy using IterativeImputer with a BayesianRidge estimator, which is well suited to data where the features are correlated. This approach:

  • Models each feature with missing values as a function of the other features.
  • Iteratively refines imputations over multiple rounds until convergence.
  • Approximates multivariate relationships between variables, which is especially important for clustering.
Python
import pandas as pd
from sklearn.impute import IterativeImputer
from sklearn.linear_model import BayesianRidge

# Keep a copy of the original dataframe with CUST_ID for later interpretation
df_original = df.copy()

# Check missing values per column
print("Missing values per column:")
missing_counts = df.isna().sum().sort_values(ascending=False)
display(missing_counts)

# Drop CUST_ID from the features (identifier, not used for clustering)
if "CUST_ID" in df.columns:
    df_features = df.drop(columns=["CUST_ID"])
else:
    df_features = df.copy()

# Feature matrix (numerical variables)
X = df_features.copy()

# Model-based multivariate imputer
imputer = IterativeImputer(
    estimator=BayesianRidge(),
    max_iter=20,
    tol=1e-3,
    imputation_order="ascending",
    random_state=42
)

# Fit and transform
X_imputed = imputer.fit_transform(X)

# Convert back to DataFrame
X_imputed = pd.DataFrame(
    X_imputed,
    columns=X.columns,
    index=X.index
)

print("\nPreview of imputed feature matrix X:")
display(X_imputed.head())
MINIMUM_PAYMENTS                    313
CREDIT_LIMIT                          1
BALANCE                               0
CUST_ID                               0
BALANCE_FREQUENCY                     0
PURCHASES                             0
CASH_ADVANCE                          0
PURCHASES_FREQUENCY                   0
ONEOFF_PURCHASES                      0
INSTALLMENTS_PURCHASES                0
PURCHASES_INSTALLMENTS_FREQUENCY      0
ONEOFF_PURCHASES_FREQUENCY            0
CASH_ADVANCE_TRX                      0
CASH_ADVANCE_FREQUENCY                0
PURCHASES_TRX                         0
PAYMENTS                              0
PRC_FULL_PAYMENT                      0
TENURE                                0
dtype: int64

Feature scaling

K-means is a distance-based algorithm that uses Euclidean distances. Variables on larger scales (e.g., dollars) can dominate others (e.g., frequencies mostly in [0, 1], though CASH_ADVANCE_FREQUENCY reaches 1.50).

Therefore, we:

  • Use StandardScaler to standardize each feature:
    • Mean = 0
    • Standard deviation = 1

This lets all variables contribute more equally to the clustering.

Python
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_imputed)

print("Shape of scaled feature matrix:", X_scaled.shape)
Shape of scaled feature matrix: (8950, 17)

Selecting the Number of Clusters (Elbow Method)

The elbow method is a common heuristic for choosing the number of clusters k:

  1. Fit K-means for a range of k values (e.g., 2 to 8)
  2. Record the inertia (within-cluster sum of squares, WCSS) for each k
  3. Plot k vs. inertia
  4. Look for a point where the rate of decrease sharply changes, i.e., the โ€œelbowโ€

Weโ€™ll then choose a reasonable k based on this plot.

Python
# Range of k values to try
k_values = range(2, 9)

inertias = []

for k in k_values:
    kmeans = KMeans(
        n_clusters=k,
        init='k-means++',
        random_state=RANDOM_STATE,
        n_init=10
    )
    kmeans.fit(X_scaled)
    inertias.append(kmeans.inertia_)

# Plot the elbow curve
plt.figure()
plt.plot(k_values, inertias, marker="o")
plt.xticks(k_values)
plt.xlabel("Number of clusters (k)")
plt.ylabel("Inertia (Within-Cluster Sum of Squares)")
plt.title("Elbow Method for Optimal k")
plt.grid(True)
plt.savefig("elbow_method.png", dpi=300, bbox_inches="tight")
plt.show()
Figure 4

Inertia against the number of clusters

K-means Clustering Elbow Plot
Figure 4. Inertia, the within-cluster sum of squares, plotted against k for one K-means run per value of k from 2 to 8 on the standardized 8,950 by 17 matrix, each run started from ten k-means++ initializations. Adding a centroid can only lower the best achievable inertia, and the fitted curve here falls at every step as well, so the curve shows where the gains from another k begin to slow. The bend is broad, sitting around 3 to 6 clusters, and k = 4 is chosen within it for interpretability.

From the elbow plot in Figure 4, we inspect where the reduction in inertia starts to flatten out. The elbow for this dataset appears somewhere around 3 to 6 clusters.

For this blog post, we will assume that the elbow occurs at k = 4, which balances interpretability and granularity.

In practice, you should visually inspect the elbow plot and possibly compare several values of k based on business interpretability and cluster stability.

Python
# Choose the number of clusters based on visual inspection of the elbow plot
optimal_k = 4  # Chosen as a reasonable elbow point

print(f"Using optimal_k = {optimal_k} clusters for the final K-Means model.")
Using optimal_k = 4 clusters for the final K-Means model.

Fit Final K-means Model

With optimal_k chosen, we now:

  • Fit a final KMeans model on the scaled features
  • Obtain cluster labels for each customer
  • Attach the cluster labels back to the original dataframe (df_clustered)
  • Inspect how many customers fall into each cluster
Python
# Fit final KMeans model
kmeans_final = KMeans(
    n_clusters=optimal_k,
    init='k-means++',
    random_state=RANDOM_STATE,
    n_init=10
)
kmeans_final.fit(X_scaled)

# Get cluster labels
cluster_labels = kmeans_final.labels_

# Attach cluster labels to the original dataframe
df_clustered = df_original.copy()
df_clustered["cluster"] = cluster_labels

# Show cluster sizes
print("Cluster label counts:")
display(df_clustered["cluster"].value_counts().sort_index())

print("\nPreview of data with cluster labels:")
display(df_clustered.head())
Cluster label counts:
cluster
0    3981
1    3354
2    1219
3     396
Name: count, dtype: int64

Each customer is now assigned to one of the k clusters (0 to k-1). These clusters represent behavioral customer segments, which we will further explore using visualization and descriptive statistics.

Visualizing Clusters with PCA

The dataset has many dimensions (17 behavioral features), which makes it hard to visualize clusters directly. To address this, we use:

  • Principal Component Analysis (PCA) to reduce the standardized features to 3 principal components (PC1, PC2 and PC3)
  • Then plot customers in the PCA space, both as a 2D scatter and as an interactive 3D scene, colored by their cluster

PCA helps capture as much variance as possible in a low-dimensional projection, giving us an approximate but informative visual representation of the clusters.

Python
# Perform PCA to reduce to 3 principal components
pca = PCA(n_components=3, random_state=RANDOM_STATE)
X_pca = pca.fit_transform(X_scaled)

# Create a DataFrame for PCA results
df_pca = pd.DataFrame(
    X_pca,
    columns=["PC1", "PC2", "PC3"],
    index=df_clustered.index
)
df_pca["cluster"] = df_clustered["cluster"]

# 2D scatter plot of the first two principal components colored by cluster
plt.figure()
sns.scatterplot(
    data=df_pca,
    x="PC1",
    y="PC2",
    hue="cluster",
    palette="tab10",
    alpha=0.7
)
plt.title("Customer Segments Visualized in 2D PCA Space")
plt.xlabel("Principal Component 1")
plt.ylabel("Principal Component 2")
plt.legend(title="Cluster")
plt.grid(True)

plt.savefig("pca_2d_clusters.png", dpi=300, bbox_inches="tight")
plt.show()

# 3D interactive scatter plot using the first three principal components
fig = go.Figure(
    data=[
        go.Scatter3d(
            x=df_pca.loc[df_pca["cluster"] == c, "PC1"],
            y=df_pca.loc[df_pca["cluster"] == c, "PC2"],
            z=df_pca.loc[df_pca["cluster"] == c, "PC3"],
            mode="markers",
            marker=dict(size=4),
            name=f"Cluster {c}",
        )
        for c in sorted(df_pca["cluster"].unique())
    ]
)

fig.update_layout(
    title="Customer Segments Visualized in 3D PCA Space",
    scene=dict(
        xaxis_title="PC1",
        yaxis_title="PC2",
        zaxis_title="PC3",
    ),
    legend_title_text="Cluster",
)
fig.write_html("pca_3d_clusters_nt.html")
fig.show()

# Explained variance information
explained_var = pca.explained_variance_ratio_
print(
    f"Explained variance by PC1, PC2, PC3: {explained_var[0]:.2%}, {explained_var[1]:.2%}, {explained_var[2]:.2%}"
)
Figure 5

Segments in the first two principal components

Principal Component Analysis K-means Clustering
Figure 5. Each of the 8,950 customers drawn at its position on the first two principal components of the standardized features, colored by the cluster label from the k = 4 fit. PC1 holds 27.30 percent of the variance and PC2 20.32 percent, so 47.62 percent of the variation the clustering used is on the page. Colors that overlap in this projection can separate in the full 17-dimensional space, where the partition itself lives. The labels 0 to 3 are identifiers with no order.
Figure 6

Rotating view of the segments in three components

Open this figure at full size in a new tab
Figure 6. The same customers in an interactive Plotly scatter, one trace per segment, with PC3 added as a depth axis. Drag to rotate the cloud and use the legend to hide or isolate a segment. PC3 carries 8.83 percent of the variance on top of the 27.30 and 20.32 percent held by PC1 and PC2, so 56.45 percent of the variation the clustering used is in the view. Rotating shows that segments overlapping at one angle can pull apart at another.
Explained variance by PC1, PC2, PC3: 27.30%, 20.32%, 8.83%

Figure 5 shows how customers are distributed in the 2D PCA space. While some overlap between clusters is expected (since we compressed many dimensions into just two), we can often see areas where certain clusters are more concentrated.

This visualization is useful for:

  • Gaining intuition about cluster separation
  • Communicating the segmentation story visually to non-technical stakeholders

The explained variance ratios give us an idea of how much of the original variation in the data is captured by the first two principal components: 27.30 and 20.32 percent, so under half of the variation the clustering used is on the page and any separation is shown only in part.

Descriptive Statistics by Cluster (Table 1 using tableone)

To understand what makes each cluster unique, we will:

  • Use the tableone package to create a Table 1 of descriptive statistics
  • Stratify the table by cluster
  • Include the main behavioral numeric variables (e.g., balances, purchases, cash advances), summarized from the raw pre-imputation values rather than the imputed, standardized matrix the model was fit on

The table helps us attach business meaning to each segment, such as:

  • High spenders
  • Frequent revolvers (carry a balance)
  • Cash-advance heavy users
  • Low-activity or dormant customers

Note: If tableone is not installed in your environment, you can install it via:
pip install tableone

Python
# Prepare variables for Table 1
# We will include all numeric behavioral variables and stratify by 'cluster'.
# Exclude 'cluster' itself and the identifier 'CUST_ID' from the summarized columns.

# Identify numeric columns
numeric_columns = df_clustered.select_dtypes(include=[np.number]).columns.tolist()

# Remove 'cluster' from the list of summarized variables
if "cluster" in numeric_columns:
    numeric_columns.remove("cluster")

columns = numeric_columns
categorical = []  # No categorical variables in this example

# Create the TableOne object, stratified by cluster
table1 = TableOne(
    df_clustered,
    columns=columns,
    categorical=categorical,
    groupby="cluster",
    label_suffix=True
)

print("Table 1: Descriptive statistics of customer behavior by cluster")
display(table1)
Table 1: Descriptive statistics of customer behavior by cluster
Table 2

Customer behavior by segment

Table 2. Mean and standard deviation for each behavioral variable, stratified by cluster and produced by the tableone package. The surrounding text calls it a Table 1, the package’s term for such a table. An overall column and a missing count sit alongside. The segments are unequal in size: 3,981, 3,354, 1,219 and 396 customers. Segment 3 averages 7815.6 in purchases against 271.9 for segment 0, and segment 2 averages 4465.3 in cash advances, the highest of the four. Raw values, left unstandardized, summarize the customers the model was fit on, so the table profiles the segments without confirming them. The balance, purchase, cash-advance and payment amounts, and the transaction counts, are strongly right skewed. Overall, their standard deviations exceed their means. That lifts each segment’s mean above its typical customer, and medians would run lower. Credit limit, tenure, balance frequency and purchase frequency do not follow that pattern.
Grouped by cluster
MissingOverall0123
n8950398133541219396
BALANCE, mean (SD)01564.5 (2081.5)995.7 (1085.2)907.1 (1225.4)4571.0 (2746.6)3594.8 (3366.1)
BALANCE_FREQUENCY, mean (SD)00.9 (0.2)0.8 (0.3)0.9 (0.2)1.0 (0.1)1.0 (0.1)
PURCHASES, mean (SD)01003.2 (2136.6)271.9 (466.8)1253.5 (1055.7)489.7 (838.0)7815.6 (6028.6)
ONEOFF_PURCHASES, mean (SD)0592.4 (1659.9)209.8 (447.2)603.0 (880.2)311.9 (638.9)5213.1 (5426.5)
INSTALLMENTS_PURCHASES, mean (SD)0411.1 (904.3)62.4 (157.8)650.8 (635.8)177.8 (412.8)2604.1 (2760.0)
CASH_ADVANCE, mean (SD)0978.9 (2097.2)583.6 (908.2)214.3 (613.0)4465.3 (3599.3)696.0 (2015.9)
PURCHASES_FREQUENCY, mean (SD)00.5 (0.4)0.2 (0.2)0.9 (0.1)0.3 (0.4)0.9 (0.1)
ONEOFF_PURCHASES_FREQUENCY, mean (SD)00.2 (0.3)0.1 (0.1)0.3 (0.4)0.1 (0.2)0.7 (0.3)
PURCHASES_INSTALLMENTS_FREQUENCY, mean (SD)00.4 (0.4)0.1 (0.2)0.7 (0.3)0.2 (0.3)0.8 (0.3)
CASH_ADVANCE_FREQUENCY, mean (SD)00.1 (0.2)0.1 (0.1)0.0 (0.1)0.5 (0.2)0.1 (0.2)
CASH_ADVANCE_TRX, mean (SD)03.2 (6.8)2.1 (2.9)0.8 (2.0)14.2 (12.2)2.2 (6.4)
PURCHASES_TRX, mean (SD)014.7 (24.9)3.0 (4.0)22.4 (16.2)7.5 (13.8)90.4 (56.7)
CREDIT_LIMIT, mean (SD)14494.4 (3638.8)3270.8 (2649.6)4240.5 (3287.5)7481.9 (3751.7)9747.5 (4821.0)
PAYMENTS, mean (SD)01733.1 (2895.1)968.2 (1571.9)1351.5 (1298.9)3436.1 (4179.7)7413.2 (6955.0)
MINIMUM_PAYMENTS, mean (SD)313864.2 (2372.4)553.6 (1259.3)653.2 (1804.2)2035.8 (3961.4)2000.7 (5148.2)
PRC_FULL_PAYMENT, mean (SD)00.2 (0.3)0.1 (0.2)0.3 (0.4)0.0 (0.1)0.3 (0.4)
TENURE, mean (SD)011.5 (1.3)11.4 (1.4)11.6 (1.2)11.4 (1.5)11.9 (0.5)

Cluster Heatmap of Feature Means

To better understand the behavioral profile of each cluster, we create a heatmap that compares the mean value of each numerical variable across clusters.

To make the patterns more interpretable, values are standardized (z-scored) down each column, across the four cluster means, so that warmer colors represent higher values relative to other clusters.

This visualization helps reveal:

  • Which clusters spend more (e.g., PURCHASES, ONEOFF_PURCHASES)
  • Which clusters rely on cash advances
  • Which clusters maintain higher balances
  • Differences in payment behavior (PAYMENTS, PRC_FULL_PAYMENT)
  • Overall engagement and activity

The heatmap provides a compact, high-level overview of each segmentโ€™s financial behavior.

Python
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from matplotlib.colors import LinearSegmentedColormap

# Compute cluster-wise means for all numerical behavioral variables
cluster_means = (
    df_clustered
.groupby("cluster")[numeric_columns]
.mean()
.round(2)
)

# Standardize each variable across the four cluster means for better visualization
# (z-score transformation down each column)
cluster_means_scaled = (
    (cluster_means - cluster_means.mean()) /
    cluster_means.std()
 )

# Create a custom colormap (white for 0, red for 1, and blue for -1)
cmap = LinearSegmentedColormap.from_list(
    'custom_colormap', ['blue', 'white', 'red'], N=256
)

plt.figure(figsize=(14, 8))
sns.heatmap(
    cluster_means_scaled,
    cmap=cmap,
    annot=True,
    fmt=".2f",
    linewidths=0.5,
    cbar_kws={"label": "Standardized Mean Value"}
 )
plt.title("Heatmap of Standardized Mean Feature Values by Cluster")
plt.xlabel("Variables")
plt.ylabel("Cluster")
plt.tight_layout()
plt.savefig("cluster_heatmap.png", dpi=250, bbox_inches="tight")
plt.show()
Figure 7

Segment profiles against the four-segment average

Clustering Heatmap Finance
Figure 7. Four rows, one per segment, crossed with the 17 behavioral variables, each cell annotated with its value. The standardization runs down each column. Every variable is centered and scaled across just the four cluster means. A cell therefore reads as how far one segment sits from the four-segment average on that variable, in standard deviations of those four means. That is what puts dollars and frequencies on one scale. The colormap spans the data range, so white falls where that range centers. Here the data run from −1.46 to 1.50, which puts white within 0.02 of the average.

Table 2 summarizes key behavioral variables by cluster:

  • BALANCE and CREDIT_LIMIT help differentiate customers by typical outstanding balance and available credit.
  • PURCHASES, ONEOFF_PURCHASES, and INSTALLMENTS_PURCHASES indicate spending style and purchase patterns.
  • CASH_ADVANCE and CASH_ADVANCE_TRX highlight segments that rely heavily on cash advances.
  • PAYMENTS, MINIMUM_PAYMENTS, and PRC_FULL_PAYMENT describe payment behavior (e.g., transactors vs. revolvers).
  • TENURE suggests how long customers have been with the card issuer.

By comparing means and distributions across clusters, you can identify:

  • High-spending, high-limit customers with frequent full payments
  • Revolvers who maintain higher balances and pay minimum amounts
  • Cash-advance users who may be riskier, though a small credit line is not the reason. Their average credit limit of 7481.9 sits well above the 4494.4 overall average, and they carry the highest balances of the four segments
  • Low-activity customers with low purchase volume and limited engagement
Figure 8 ยท Interactive Meet the four customer segments

The four clusters K-means found in the credit-card dataset, profiled on eight behaviors from Table 2. Segment 0, low-activity holders, is 44.5 percent of customers with low purchases and light engagement. Segment 1, everyday transactors, is 37.5 percent with frequent purchases and higher full repayment. Segment 2, cash-advance revolvers, is 13.6 percent with high balances and frequent cash advances. Segment 3, high spenders, is 4.4 percent with very high purchases and payments on the largest credit limits. Each bar shows how a segment compares with the four-segment average for one behavior, standardized so behaviors on different units are comparable.

โ—€ Below averageFour-segment averageAbove average โ–ถ
  • Low-activity holdersSegment 0 ยท 44.5% ยท 3,981 customers

    Buy infrequently, keep modest balances, and show light overall engagement.

  • Everyday transactorsSegment 1 ยท 37.5% ยท 3,354 customers

    Frequent purchasers who often pay in full and rarely take cash advances.

  • Cash-advance revolversSegment 2 ยท 13.6% ยท 1,219 customers

    High balances from frequent cash advances, low purchases, seldom pay in full.

  • High spendersSegment 3 ยท 4.4% ยท 396 customers

    Very high purchases and payments on the largest credit limits.

Figure 8. The four segments K-means found, profiled on eight behaviors from Table 2. Each bar shows how a segment compares with the four-segment average for that behavior, standardized so behaviors measured on different units line up. Because these bars divide by the number of segment means (a population standard deviation) rather than by one less (the sample standard deviation behind Figure 7’s heatmap), the same segment reads slightly larger here. The tallest bars reach about 1.7 where Figure 7 shows about 1.5. Select a segment to see its profile. The numbers 0 to 3 are the labels K-means assigns, and the names summarize what the values show.

Summary

In this notebook, we:

  • Performed K-means clustering on a credit card customer dataset
  • Used the elbow method to narrow the number of clusters to roughly 3 to 6, then assumed (k = 4)
  • Applied PCA to visualize clusters and gain intuition about separation
  • Generated a Table 1 using tableone to describe each clusterโ€™s behavioral profile

From a marketing and product strategy perspective, this segmentation can support:

  • Targeted offers for high-value customers (e.g., premium rewards, higher credit limits)
  • Risk management strategies for cash-advance heavy or high-balance customers
  • Engagement campaigns for low-activity or dormant segments
  • Personalized communication based on spending and payment behavior

This workflow provides a reproducible template for building and interpreting unsupervised segmentation models on customer-level data.

Summary

Key Takeaways

K is a modeling choice

K-means takes the number of clusters as an input and partitions the data into whatever K you supply. The elbow here bends somewhere around 3 to 6, and K = 4 is chosen within that range for interpretability. The data leaves the choice open.

Inertia is the quantity being minimized

Standard K-means minimizes the within-cluster sum of squared distances to the centroids. Inertia never increases across the assignment and update steps, which is why the loop settles, and its best achievable value cannot rise as K grows, which rules out minimizing it to pick K.

Convergence is local, so initialization matters

The algorithm converges to a local optimum that depends on where the centroids start, which lets different starting points yield different segments. The example runs k-means++ from ten initializations, which makes a poor solution less likely without ruling one out.

Squared distance makes scale decisive

An observation far from its centroid contributes disproportionately, and a feature on a wide numeric range crowds out the rest. The example standardizes all 17 features to mean 0 and standard deviation 1, putting each one into the distance on comparable terms. Equal scale is not equal weight, though: PURCHASES is very nearly one-off plus installment purchases (Table 2 gives 592.4 + 411.1 against a mean of 1003.2, so a handful of records aside the columns are near-collinear), and three columns describe cash advances, letting those behaviors enter the distance several times over.

Cluster shape is part of the fit

Squared Euclidean distance favors compact, roughly spherical clusters of similar size, and elongated, non-convex or very unequal groups sit further from that shape. PURCHASES is strongly right-skewed, with a median of 361.28, a mean of 1003.20 and a maximum of 49039.57.

Read the profiles off the table

K-means emits arbitrary labels 0 to 3 with no order. The profiles come from Table 2, where segment 3 averages 7815.6 in purchases against 271.9 for segment 0. The 2D PCA view holds 27.30 and 20.32 percent of the variance, so it separates only in part.

Data & License

Dataset

Credit Card Dataset for Clustering by Arjun Bhasin (Kaggle) — the file CC_GENERAL.csv, about 8,950 anonymized card-holder records across 18 fields: a customer identifier and 17 behavioral variables. Source: kaggle.com/datasets/arjunbhasin2013/ccdata.License: CC0 1.0 Universal (Public Domain Dedication) — no rights reserved; credited here as a courtesy. CC0 1.0

Article

© 2025 Philip Sarajlic. All rights reserved for the article’s original text and figures; the third-party data remains under the license shown above.

Code

Code examples in this article are licensed under the Common Public Attribution License Version 1.0 (CPAL-1.0), an OSI-approved copyleft license based on the Mozilla Public License 1.1. Initial Developer: Philip Sarajlic.

Attribution required by CPAL Exhibit B: © 2025 Philip Sarajlic · “Based on code by Philip Sarajlic” · philipsarajlic.com · no graphic image. This attribution must be displayed in Larger Works.

Modifications must be released in Source Code form under CPAL-1.0. Making the code usable by anyone other than you over a network is External Deployment under the license and is treated as distribution, so the Source Code must be made available to those users.

Full text: opensource.org/license/cpal-1-0 (SPDX identifier CPAL-1.0)

Scroll to Top

Free diagnostic

Would your model hold up to an external review?

Answer 39 questions in about nine minutes and get a clear picture of where your model stands.

You’ll receive a readiness score, a breakdown across 15 areas, your biggest evidence gaps, and the five questions an external reviewer would be most likely to ask first.

The assessment draws on guidance from:

  • TRIPOD+AI
  • PROBAST+AI
  • FDA GMLP
  • NIST AI RMF
  • SR 11-7

We’ll send you one email with your link, and your results when you finish. Your email address is carried through when you complete the assessment, so you won’t need to enter it again.

We don’t ask for your data, and there are no free-text fields in the assessment. See how we use your email address.