Cổng thông tin học sinh

Kho tàng kiến thức và tài nguyên học tập hữu ích

Data Driven Growth (P.2): Phân khúc khách hàng theo RFM

07 May, 2025 #data-driven-growth Data • Chill mỗi ngày
Data Driven Growth (P.2): Phân khúc khách hàng theo RFM

Trong bài viết trước, chúng ta đã phân tích các chỉ số chính trong các hoạt động kinh doanh bán lẻ trực tuyến. Trong bài viết này, chúng ta sẽ tập trung vào khách hàng và sự phân khúc khách hàng.

Đến lúc này sẽ có nhiều bạn đặt ra câu hỏi, tại sao chúng ta cần phải thực hiện giai đoạn này?  

Vì mỗi khách hàng cần có sự đối xử khác nhau tùy vào tính cách, hành vi và độ tuổi của họ, không thể cùng đối xử với mọi khách hàng theo cùng một cách giống nhau.

Như cùng một nội dung, một kênh và cùng một tầm quan trọng được. Vì như vậy khách hàng họ sẽ tìm đến một thương hiệu khác hiểu và tôn trọng họ hơn.

Image related to customer segmentation

Bạn có thể thực hiện nhiều phân đoạn khác nhau tùy theo nhu cầu mà bạn đang tìm hiểu. Chẳng hạn như, bạn muốn tăng tỷ lệ giữ chân khách hàng, bạn có thể thực hiện phân đoạn dựa trên xác suất churn. Và bạn còn chần chừ gì nữa mà không bắt tay vào việc hành động ngay!

Ngoài ra cũng có những phương pháp phân đoạn rất phổ biến và hữu ích khác. Dưới đây chúng ta sẽ tìm hiểu một trong số chúng là: RFM

Bạn có thể tham khảo thêm bài viết này để biết rõ hơn về khái niệm RFM là gì nhé!

Về phương pháp luận, chúng ta cần tính toán lần truy cập gần đây, tần suất, giá trị tiền tệ cũng như áp dụng machine learning không giám sát để xác định các nhóm (cụm) khác nhau. Hãy bắt đầu viết mã và xem cách thực hiện phân cụm RFM dưới đây nhé!

Code
# import libraries
from datetime import datetime, timedelta
import pandas as pd
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from __future__ import division

import plotly.plotly as py
import plotly.offline as pyoff
import plotly.graph_objs as go

#inititate Plotly
pyoff.init_notebook_mode()

#load our data from CSV
tx_data = pd.read_csv('data.csv')

#convert the string date field to datetime
tx_data['InvoiceDate'] = pd.to_datetime(tx_data['InvoiceDate'])

#we will be using only UK data
tx_uk = tx_data.query("Country=='United Kingdom'").reset_index(drop=True)

Lần truy cập gần đây

Để tính toán lần truy cập gần đây, chúng ta cần tìm hiểu  xem ngày mua hàng gần đây nhất của từng khách hàng và xem xem họ đã không hoạt động trong bao nhiêu ngày.

Sau đó chúng ta sẽ áp dụng công thức sau: K-means * clustering để kiếm ra số lần truy cập gần đây của khách hàng.

Trước khi chuyển sang tính toán lần truy cập gần đây, hãy cùng tóm tắt lại công việc dữ liệu mà chúng ta đã thực hiện trước đây.

Bây giờ chúng ta có thể tính toán lần truy cập gần đây:

Code
#create a generic user dataframe to keep CustomerID and new segmentation scores
tx_user = pd.DataFrame(tx_data['CustomerID'].unique())
tx_user.columns = ['CustomerID']

#get the max purchase date for each customer and create a dataframe with it
tx_max_purchase = tx_uk.groupby('CustomerID').InvoiceDate.max().reset_index()
tx_max_purchase.columns = ['CustomerID','MaxPurchaseDate']

#we take our observation point as the max invoice date in our dataset
tx_max_purchase['Recency'] = (tx_max_purchase['MaxPurchaseDate'].max() - tx_max_purchase['MaxPurchaseDate']).dt.days

#merge this dataframe to our new user dataframe
tx_user = pd.merge(tx_user, tx_max_purchase[['CustomerID','Recency']], on='CustomerID')

tx_user.head()

#plot a recency histogram

plot_data = [
    go.Histogram(
        x=tx_user['Recency']
    )
]

plot_layout = go.Layout(
        title='Recency'
    )
fig = go.Figure(data=plot_data, layout=plot_layout)
pyoff.iplot(fig)

Khung dữ liệu mới tx_user của chúng ta hiện chứa dữ liệu lần truy cập gần đây:

tx_user dataframe head

Để có một cái nhìn nhanh về lần gần đây trông như thế nào, chúng ta có thể sử dụng phương thức .describe () của pandas. Nó sẽ hiển thị giá trị trung bình, tối thiểu, tối đa, số lượng và phần trăm dữ liệu.

tx_user describe method output

Đoạn mã ở trên có biểu đồ để cho chúng ta thấy sự phân bổ lần truy cập gần đây của các khách hàng.

Recency Histogram

Tiếp theo, chúng ta sẽ đến với một phần khá là thú vị.

Chúng ta sẽ áp dụng phân cụm K-mean để chỉ định điểm số lần truy cập gần đây. Nhưng chúng ta cần biết có bao nhiêu cụm đối với thuật toán K-mean. Để tìm ra nó, chúng ta sẽ áp dụng phương pháp Elbow.

Phương pháp Elbow chỉ đơn giản cho biết số cụm tối ưu để có quán tính tối ưu. Đoạn mã và biểu đồ quán tính như sau:

Code
from sklearn.cluster import KMeans

sse={}
tx_recency = tx_user[['Recency']]
for k in range(1, 10):
    kmeans = KMeans(n_clusters=k, max_iter=1000).fit(tx_recency)
    tx_recency["clusters"] = kmeans.labels_
    sse[k] = kmeans.inertia_ 
plt.figure()
plt.plot(list(sse.keys()), list(sse.values()))
plt.xlabel("Number of cluster")
plt.show()
Inertia Graph
Đồ thị quán tính

Ở đây có vẻ như số 3 là cái tối ưu. Dựa theo yêu cầu kinh doanh, chúng ta có thể tiếp tục với ít hoặc nhiều cụm hơn. Chúng ta sẽ chọn  số4 cho ví dụ này:

Code
#build 4 clusters for recency and add it to dataframe
kmeans = KMeans(n_clusters=4)
kmeans.fit(tx_user[['Recency']])
tx_user['RecencyCluster'] = kmeans.predict(tx_user[['Recency']])

#function for ordering cluster numbers
def order_cluster(cluster_field_name, target_field_name,df,ascending):
    new_cluster_field_name = 'new_' + cluster_field_name
    df_new = df.groupby(cluster_field_name)[target_field_name].mean().reset_index()
    df_new = df_new.sort_values(by=target_field_name,ascending=ascending).reset_index(drop=True)
    df_new['index'] = df_new.index
    df_final = pd.merge(df,df_new[[cluster_field_name,'index']], on=cluster_field_name)
    df_final = df_final.drop([cluster_field_name],axis=1)
    df_final = df_final.rename(columns={"index":cluster_field_name})
    return df_final

tx_user = order_cluster('RecencyCluster', 'Recency',tx_user,False)

Chúng ta đã tính toán các cụm và gán chúng cho từng khách hàng trong dataframe tx_user.

tx_user dataframe with RecencyCluster

Chúng ta có thể thấy các cụm với lần truy cập gần đây có các đặc điểm khác nhau như thế nào. Khách hàng ở cụm 1 rất gần so với cụm 2.

Chúng ta đã thêm một hàm vào mã, đó là order_cluster (). K-mean chỉ định các cụm dưới dạng số nhưng không theo cách có thứ tự. Chúng ta không thể nói cụm 0 là kém nhất và cụm 4 là tốt nhất.

Phương thức order_cluster () thực hiện điều này và khung dữ liệu mới của chúng ta đã trông gọn gàng hơn rất nhiều:

tx_user dataframe with ordered RecencyCluster

Các bạn hãy áp dụng tương tự cho tần suất và doanh thu.

Tính thường xuyên

Để tạo cụm tần suất, chúng ta cần tìm tổng số đơn đặt hàng cho mỗi khách hàng. Trước tiên, hãy cùng tính xem tần suất sẽ trông như thế nào trong cơ sở dữ liệu khách hàng của chúng ta:

Code
#get order counts for each user and create a dataframe with it
tx_frequency = tx_uk.groupby('CustomerID').InvoiceDate.count().reset_index()
tx_frequency.columns = ['CustomerID','Frequency']

#add this data to our main dataframe
tx_user = pd.merge(tx_user, tx_frequency, on='CustomerID')

#plot the histogram
plot_data = [
    go.Histogram(
        x=tx_user.query('Frequency < 1000')['Frequency']
    )
]

plot_layout = go.Layout(
        title='Frequency'
    )
fig = go.Figure(data=plot_data, layout=plot_layout)
pyoff.iplot(fig)
Frequency Histogram

Áp dụng cùng một logic để có các cụm tần số và gán điều này cho từng khách hàng:

Code
#k-means
kmeans = KMeans(n_clusters=4)
kmeans.fit(tx_user[['Frequency']])
tx_user['FrequencyCluster'] = kmeans.predict(tx_user[['Frequency']])

#order the frequency cluster
tx_user = order_cluster('FrequencyCluster', 'Frequency',tx_user,True)

#see details of each cluster
tx_user.groupby('FrequencyCluster')['Frequency'].describe()

Đặc điểm của các cụm tần số của chúng ta sẽ trông giống như dưới đây:

Frequency Cluster Description

Theo ký hiệu tương tự như các cụm lần truy cập gần đây, số tần suất cao cho biết khách hàng hoạt động tốt hơn.

Doanh thu

Tiếp đến chúng ta hãy xem cơ sở dữ liệu khách hàng sẽ trông như thế nào khi chúng ta phân nhóm chúng dựa trên doanh thu. Chúng ta sẽ tính toán doanh thu cho từng khách hàng, vẽ biểu đồ và áp dụng cùng một phương pháp phân nhóm.

Code
#calculate revenue for each customer
tx_uk['Revenue'] = tx_uk['UnitPrice'] * tx_uk['Quantity']
tx_revenue = tx_uk.groupby('CustomerID').Revenue.sum().reset_index()

#merge it with our main dataframe
tx_user = pd.merge(tx_user, tx_revenue, on='CustomerID')

#plot the histogram
plot_data = [
    go.Histogram(
        x=tx_user.query('Revenue < 10000')['Revenue']
    )
]

plot_layout = go.Layout(
        title='Monetary Value'
    )
fig = go.Figure(data=plot_data, layout=plot_layout)
pyoff.iplot(fig)
Monetary Value Histogram

Chúng ta cũng có một số khách hàng có doanh thu âm. Hãy tiếp tục và áp dụng k-means clustering:

Code
#apply clustering
kmeans = KMeans(n_clusters=4)
kmeans.fit(tx_user[['Revenue']])
tx_user['RevenueCluster'] = kmeans.predict(tx_user[['Revenue']])


#order the cluster numbers
tx_user = order_cluster('RevenueCluster', 'Revenue',tx_user,True)

#show details of the dataframe
tx_user.groupby('RevenueCluster')['Revenue'].describe()
Revenue Cluster Description

Tổng điểm

Thật là đáng kinh ngạc khi chúng ta có điểm số(số cụm) cho lần truy cập gần đây cùng với tần suất và doanh thu. Hãy cùng tạo ra một điểm tổng thể từ chúng:

Code
#calculate overall score and use mean() to see details
tx_user['OverallScore'] = tx_user['RecencyCluster'] + tx_user['FrequencyCluster'] + tx_user['RevenueCluster']
tx_user.groupby('OverallScore')['Recency','Frequency','Revenue'].mean()
Overall Score Metrics

Việc cho điểm ở trên, cho chúng ta thấy rõ rằng khách hàng có điểm 8 là khách hàng tiềm năng nhất và khách hàng có điểm 0 là khách hàng ít tiềm năng nhất.

Để giữ cho mọi thứ đơn giản, chúng ta sẽ quy định giá trị cho những con số này:

  • 0 đến 2: giá trị thấp
  • 3 đến 4: giá trị trung bình
  • Trên 5: giá trị cao

Chúng ta có thể dễ dàng áp dụng cách đặt giá trị này trên khung dữ liệu của mình:

Code
tx_user['Segment'] = 'Low-Value'
tx_user.loc[tx_user['OverallScore']>2,'Segment'] = 'Mid-Value'
tx_user.loc[tx_user['OverallScore']>4,'Segment'] = 'High-Value'

Bây giờ hãy xem các phân đoạn của chúng ta sẽ được phân phối như thế nào trên biểu đồ phân tán:

Segments scatter plot 1
Segments scatter plot 2
Segments scatter plot 3

Bạn có thể thấy các phân đoạn được phân biệt rõ ràng với nhau như thế nào về RFM. Bạn có thể tìm thấy các đoạn mã đồ thị bên dưới:

Code
#Revenue vs Frequency
tx_graph = tx_user.query("Revenue < 50000 and Frequency < 2000")

plot_data = [
    go.Scatter(
        x=tx_graph.query("Segment == 'Low-Value'")['Frequency'],
        y=tx_graph.query("Segment == 'Low-Value'")['Revenue'],
        mode='markers',
        name='Low',
        marker= dict(size= 7,
            line= dict(width=1),
            color= 'blue',
            opacity= 0.8
           )
    ),
        go.Scatter(
        x=tx_graph.query("Segment == 'Mid-Value'")['Frequency'],
        y=tx_graph.query("Segment == 'Mid-Value'")['Revenue'],
        mode='markers',
        name='Mid',
        marker= dict(size= 9,
            line= dict(width=1),
            color= 'green',
            opacity= 0.5
           )
    ),
        go.Scatter(
        x=tx_graph.query("Segment == 'High-Value'")['Frequency'],
        y=tx_graph.query("Segment == 'High-Value'")['Revenue'],
        mode='markers',
        name='High',
        marker= dict(size= 11,
            line= dict(width=1),
            color= 'red',
            opacity= 0.9
           )
    ),
]

plot_layout = go.Layout(
        yaxis= {'title': "Revenue"},
        xaxis= {'title': "Frequency"},
        title='Segments'
    )
fig = go.Figure(data=plot_data, layout=plot_layout)
pyoff.iplot(fig)

#Revenue Recency

tx_graph = tx_user.query("Revenue < 50000 and Frequency < 2000")

plot_data = [
    go.Scatter(
        x=tx_graph.query("Segment == 'Low-Value'")['Recency'],
        y=tx_graph.query("Segment == 'Low-Value'")['Revenue'],
        mode='markers',
        name='Low',
        marker= dict(size= 7,
            line= dict(width=1),
            color= 'blue',
            opacity= 0.8
           )
    ),
        go.Scatter(
        x=tx_graph.query("Segment == 'Mid-Value'")['Recency'],
        y=tx_graph.query("Segment == 'Mid-Value'")['Revenue'],
        mode='markers',
        name='Mid',
        marker= dict(size= 9,
            line= dict(width=1),
            color= 'green',
            opacity= 0.5
           )
    ),
        go.Scatter(
        x=tx_graph.query("Segment == 'High-Value'")['Recency'],
        y=tx_graph.query("Segment == 'High-Value'")['Revenue'],
        mode='markers',
        name='High',
        marker= dict(size= 11,
            line= dict(width=1),
            color= 'red',
            opacity= 0.9
           )
    ),
]

plot_layout = go.Layout(
        yaxis= {'title': "Revenue"},
        xaxis= {'title': "Recency"},
        title='Segments'
    )
fig = go.Figure(data=plot_data, layout=plot_layout)
pyoff.iplot(fig)

# Revenue vs Frequency
tx_graph = tx_user.query("Revenue < 50000 and Frequency < 2000")

plot_data = [
    go.Scatter(
        x=tx_graph.query("Segment == 'Low-Value'")['Recency'],
        y=tx_graph.query("Segment == 'Low-Value'")['Frequency'],
        mode='markers',
        name='Low',
        marker= dict(size= 7,
            line= dict(width=1),
            color= 'blue',
            opacity= 0.8
           )
    ),
        go.Scatter(
        x=tx_graph.query("Segment == 'Mid-Value'")['Recency'],
        y=tx_graph.query("Segment == 'Mid-Value'")['Frequency'],
        mode='markers',
        name='Mid',
        marker= dict(size= 9,
            line= dict(width=1),
            color= 'green',
            opacity= 0.5
           )
    ),
        go.Scatter(
        x=tx_graph.query("Segment == 'High-Value'")['Recency'],
        y=tx_graph.query("Segment == 'High-Value'")['Frequency'],
        mode='markers',
        name='High',
        marker= dict(size= 11,
            line= dict(width=1),
            color= 'red',
            opacity= 0.9
           )
    ),
]

plot_layout = go.Layout(
        yaxis= {'title': "Frequency"},
        xaxis= {'title': "Recency"},
        title='Segments'
    )
fig = go.Figure(data=plot_data, layout=plot_layout)
pyoff.iplot(fig)

Chúng ta sẽ cùng bắt tay vào việc thực hiện các phân đoạn này cùng với các chiến lược chính khá rõ ràng như:

  • Giá trị cao: cải thiện tỷ lệ giữ chân khách hàng
  • Giá trị trung bình: cải thiện tỷ lệ giữ chân khách hàng + tăng tần suất
  • Giá trị thấp: tăng tần suất

Càng vào chuyên sâu sẽ càng thú vị và hấp dẫn hơn!

Trong phần tiếp theo (phần 3), chúng ta sẽ cùng tìm hiểu về việc tính toán và cùng dự đoán giá trị vòng đời của khách hàng. Hẹn gặp lại các bạn trong phần tiếp theo nhé.

Bài viết được dịch từ đây.

⬅ Về trang chủ