K-Means 算法

826 阅读25分钟

一、聚类算法概念

所谓聚类问题,就是给定一个元素集合 D,其中每个元素具有 n 个可观察属性,使用某 种算法将 D 划分成 k 个子集,要求每个子集内部的元素之间相异度尽可能低,而不同子集的 元素相异度尽可能高。其中每个子集叫做一个簇。与分类不同,分类是示例式学习,要求分 类前明确各个类别,并断言每个元素映射到一个类别,而聚类是观察式学习,在聚类前可以 不知道类别甚至不给定类别数量,是无监督学习的一种。目前聚类广泛应用于统计学、生物 学、数据库技术和市场营销等领域,如:

在商务上,聚类能帮助市场分析人员从客户基本库中发现不同的客户群,并且用不同的 购买模式来刻画不同的消费群体的特征

给定医学数据,通过肿瘤的大小来预测该肿瘤是恶性瘤还是良性瘤,这就是一个聚类问 题,它的输出是 0 或者 1 两个离散的值。(0 代表良性,1 代表恶性)

在生物学上,聚类能用于帮助推导植物和动物的种类,基因和蛋白质的分类,获得对种 群中固定结构的认识

聚类在地球观测数据中相似地区的确定,根据房屋的类型、价值和位置对一个城市中房 屋的分类发挥作用

聚类也能用来对 web 上的文档进行分类,以发现有用的信息。聚类分析能作为一种独立 的工具来获得数据分布的情况,观察每个簇的特点,并对某些特定的节点进一步分析

为此,相应的算法也非常多。本文仅介绍一种最简单的聚类算法——k 均值(k-means) 算法

二、K-Means 算法原理

K-Means 聚类原理:

K-Means 聚类原理概述:

1、从 D 中随机取 k 个元素,作为 k 个簇的各自的中心(质心)

2、分别计算剩下的元素到 k 个簇中心的相异度,将这些元素分别划归到相异度最低的簇

3、根据聚类结果,重新计算 k 个簇各自的中心,计算方法是取簇中所有元素各自维度 的算术平均数

4、将 D 中全部元素按照新的中心重新聚类

5、重复第 4 步,直到聚类结果不再变化

6、将结果输出, n: 元素个数,k:第一步中选取的元素个数,m: 每个元素的特征项个数,T: 第 5 步中 迭代的次数

三、K-Means 算法原理自实现

案例说明:通过给定的超市顾客购物信息,可以按照顾客的消费水平,运用聚类算法, 将顾客分为三种等级

表 xx 超市顾客购物信息表

客户年龄平均每次消费金额平均消费周期(天)
2331710
2214713
2417217
2719467
3778935

超市顾客聚类结果展示:

四、K-Means 算法 API

sklearn.cluster.KMeans(n_clusters=8,init='k-means++')

**表 xx KMeans 函数参数说明 **

参数名称说明
n_clusters接收 int,表示开始的聚类中心的数量
init初始化聚类中心的算法方式,默认为'k-means++'

五、K-Means 算法评估

如何去评估聚类的效果呢?

Kmeans 性能评估指标----轮廓系数

注意: 对于每个点i 为已聚类数据中的样本 ,b_i 为i 到其它族群的所有样本的距离最小值, a_i 为i 到本身簇的距离平均值。最终计算出所有的样本点的轮廓系数平均值

轮廓系数分析

(1)分析过程(我们以一个蓝 1 点为例) 计算出蓝 1 离本身族群所有点的距离的平均值 a_i 蓝 1 到其它两个族群的距离计算出平均值红平均,绿平均,取最小的那个距离作为b_i 根据公式:极端值考虑:如果b_i >> a_i : 那么公式结果趋近于 1;如果 a_i >> b_i : 那么公式结果趋近于-1

(2)结论: 如果b_i >> a_i :趋近于 1 效果越好, b_i << a_i :趋近于-1,效果不好。轮廓系数 的值是介于 [-1,1] ,越趋近于 1 代表内聚度和分离度都相对较优

(3)轮廓系数 API: sklearn.metrics.silhouette_score(X, labels)

计算所有样本的平均轮廓系数

X:特征值

labels:被聚类标记的目标值

代码实现:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

from sklearn.cluster import KMeans

from sklearn.metrics import silhouette_score  # 轮廓系数


def min_max_sca(data):
    """
    离差标准化来标准化数据
    :param data: 需要标准化的数据
    :return: 标准化之后的数据
    """
    data = (data - data.min()) / (data.max() - data.min())

    return data


def build_data():
    """
    加载并处理数据
    :return: data
    """
    # 加载数据
    data = pd.read_csv('./company.csv', encoding='gbk')
    # print('data:\n', data)

    # 使用 平均每次消费金额 平均消费周期(天) 进行聚类
    # 筛选数据
    data = data.loc[:, ['平均每次消费金额', '平均消费周期(天)']]
    # print('data:\n', data)

    # 检测缺失值
    res_null = pd.isnull(data).sum()
    # print('缺失值检测结果:\n', res_null)

    # 异常值 ---无异常值

    # 标准化数据
    # 离差标准化
    for column in data.columns:
        data.loc[:, column] = min_max_sca(data.loc[:, column])
    # print('标准化之后的数据:\n', data)

    # 转化为 ndarray
    data = data.values

    # print('data:\n', data)

    return data


def center_init(data, k):
    """
    聚类中心初始化
    :param data: 数据
    :param k: 聚类的类别数目即聚类中心数目
    :return: center
    """
    # 获取data的行数
    index_num = data.shape[0]
    # 构建行下标数组
    index = np.arange(index_num)
    # 随机初始化聚类中心
    # 随机在 data 选中 k个样本 作为k个中心
    # 随机选择  k 行
    # 参数1: 选择的数组
    # 参数2:选择的数据的个数
    # replace=False 不重复
    mask = np.random.choice(index, k, replace=False)
    print('mask:\n', mask)

    # 筛选数据
    center = data[mask, :]
    # print('center:\n', center)

    return center


def distance(v1, v2):
    """
    利用欧式距离来计算两点之间的距离
    :param v1: 点1
    :param v2: 点2
    :return: dst
    """
    dst = np.sqrt(np.sum(np.power((v1 - v2), 2)))

    return dst


def k_means_owns(data, k):
    """
    自实现超市用户聚类
    :param data: 数据
    :param k: 聚类的类别
    :return: center, new_data
    """
    # 初始化聚类中心
    center = center_init(data, k)
    # 获取 data的行数
    index_num = data.shape[0]
    # 构建一个占位数组 --- (每一个样本的最小距离,该样本所属的簇的下标)
    new_data = np.zeros(shape=(index_num, 2))
    # 设置开关
    flag = True
    while flag:
        # 打开开关
        flag = False
        # 计算每一个样本与每一个聚类中心的距离
        for i in range(index_num):
            # i :代表的是样本的行下标
            # 构建一个 min_dst
            min_dst = 1000000
            #  构建一个 min_index(该样本最小的距离属于哪一个聚类中心)
            min_index = -1
            for j in range(k):
                # j 代表的是 聚类中心的行下标
                dst = distance(data[i, :], center[j, :])
                print('dst:\n', dst)
                # 比较
                if dst < min_dst:
                    min_dst = dst
                    min_index = j
            print('min_dst:\n', min_dst)
            print('min_index:\n', min_index)

            # 判断,如果该样本上一次的聚类结果与此次的聚类结果不一致时,才保存新的类别
            # 如果该样本的聚类类别与上一次一致,不需要保存
            if min_index != new_data[i, 1]:
                # 将 最小距离 以及所属的簇 保存起来
                new_data[i, 0] = min_dst
                new_data[i, 1] = min_index
                # 闭合开关,重新聚类
                flag = True

        print('new_data:\n', new_data)
        if flag:
            # 划分为不同的簇,计算不同簇的中心
            for p in range(k):
                # 确定bool数组
                mask = new_data[:, 1] == p
                # 获取各个簇的点
                p_cluster = data[mask, :]
                # 计算各个簇的中心
                center[p, :] = p_cluster[:, 0].mean(), p_cluster[:, 1].mean()
                # 如果上次的聚类 所有样本的聚类类别  = 以新中心为聚类中心的聚类的类别 ---->聚类结束

    return center, new_data


def show_res(data, center, new_data):
    """
    结果可视化
    :param data: 数据
    :param center: 聚类中心
    :param new_data: 聚类的所属的簇
    :return: None
    """
    # 创建画布
    plt.figure()
    # 默认不支持中文 ---修改RC参数
    plt.rcParams['font.sans-serif'] = 'SimHei'
    # 增加字体之后变得不支持负号,需要修改RC参数让其继续支持负号
    plt.rcParams['axes.unicode_minus'] = False
    # 绘图
    # 构建一个color_list
    color_list = ['r', 'g', 'k']
    # 构建一个 marker_list
    marker_list = ['d', 'o', '*']
    # 点的分布 --散点图
    for i in range(data.shape[0]):
        # i : data的行下标
        plt.scatter(data[i, 0],  # 横坐标
                    data[i, 1],  # 纵坐标
                    c=color_list[int(new_data[i, 1])],  # 颜色
                    marker=marker_list[int(new_data[i, 1])]  # 点的样式
                    )
    # 描绘 聚类中心
    # 绘制标记
    plt.plot(center[:, 0], center[:, 1], 'bX', markersize=12)

    # 增加 标题
    plt.title('超市用户聚类结果')

    # 横轴
    plt.xlabel('平均每次消费金额')

    # 纵轴
    plt.ylabel('平均消费周期(天)')

    # 保存
    plt.savefig('./超市用户聚类结果.png')

    # 展示
    plt.show()


def k_means_sklearn(data, k):
    """
    基于sklearn中KMeans来实现用户聚类
    :param data: 数据
    :param k: 聚类的类别数目
    :return:
    """
    # 1、实例化算法对象
    # n_clusters :聚类类别的数目
    # 'k-means++' :教优的方式初始化聚类中心,加速迭代
    km = KMeans(n_clusters=k)

    # 2、训练数据并构建模型
    km.fit(data)

    # 3、进行预测
    y_predict = km.predict(data)

    # 获取最终的聚类中心
    center = km.cluster_centers_

    return y_predict, center


def show_res_sklearn(data, y_predict, center):
    """
    sklearn中k-means聚类结果可视化
    :param data: 数据
    :param y_predict:预测值
    :param center: 聚类中心
    :return: None
    """
    # 创建画布
    plt.figure()
    # 默认不支持中文 ---修改RC参数
    plt.rcParams['font.sans-serif'] = 'SimHei'
    # 增加字体之后变得不支持负号,需要修改RC参数让其继续支持负号
    plt.rcParams['axes.unicode_minus'] = False
    # 绘图
    # 构建一个color_list
    color_list = ['r', 'g', 'k']
    # 构建一个 marker_list
    marker_list = ['d', 'o', '*']
    # 点的分布 --散点图
    for i in range(data.shape[0]):
        # i : data的行下标
        plt.scatter(data[i, 0],  # 横坐标
                    data[i, 1],  # 纵坐标
                    c=color_list[y_predict[i]],  # 颜色
                    marker=marker_list[y_predict[i]]  # 点的样式
                    )
    # 描绘 聚类中心
    # 绘制标记
    plt.plot(center[:, 0], center[:, 1], 'bX', markersize=12)

    # 增加 标题
    plt.title('超市用户聚类结果')

    # 横轴
    plt.xlabel('平均每次消费金额')

    # 纵轴
    plt.ylabel('平均消费周期(天)')

    # 保存
    plt.savefig('./超市用户聚类结果_sklearn.png')

    # 展示
    plt.show()


def main():
    """
    主函数
    :return:
    """
    # 1、加载数据,并对数据进行数据处理
    data = build_data()
    print('数据:\n', data)

    # # 2、进行使用k-means算法进行用户聚类
    # # 确定聚类类别
    k = 3
    # center, new_data = k_means_owns(data, k)
    #
    # print('最终的聚类中心center:\n', center)
    # print('所有样本所属的类别:\n', new_data[:, 1])
    #
    # # 3、结果可视化
    # show_res(data, center, new_data)

    # 2、基于sklearn中的KMeans实现超市用户聚类
    y_predict, center = k_means_sklearn(data, k)
    print('获取预测值:\n', y_predict)
    print('获取聚类中心:\n', center)

    # 聚类算法的评估
    # 轮廓系数 ----越趋于1,效果越好
    # 计算量非常,容易出现内存不够这样的错误
    # 参数1 数据的特征值
    # 参数2 预测值
    score = silhouette_score(data, y_predict)
    print('此时的轮廓系数为:\n', score)

    # 3、结果可视化
    show_res_sklearn(data, y_predict, center)


if __name__ == '__main__':
    main()

六、案例:航空公司客户价值分析案例

信息时代的来临使得企业营销焦点从产品转向了客户,客户关(Customerrelationship management,CRM)成为企业的核心问题。客户关系管理的关键问题是客户分群。通过客户分 群,区分无价值客户和高价值客户。企业针对不同价值的客户制订优化的个性化服务方案, 采取不同营销策略,将有限营销资源集中于高价值客户,实现企业利润最大化目标。准确的 客户分群结果是企业优化营销资源分配的重要依据,客户分群越来越成为客户关系管理中亟 待解决的关键问题之一

本章将使用航空公司客户数据,结合 LRFMC 模型,采用 K-Means 聚类算对客户进行分 群,比较不同类别客户的客户价值,从而制定相应的营销策略

项目学习目标:

(1)熟悉航空客户价值分析的步骤和流程

(2)了解 RFM 模型的基本原理

(3)掌握 K-Means 算法的基本原理与使用方法

(4)比较不同类别客户的客户价值,制定相应的营销策略

七、了解航空公司现状与客户价值分析

**1、任务描述 **

面对激烈的市场竞争,各个航空公司都推出了更多的优惠来吸引客户。国内某航空司面 临着常旅客流失、竞争力下降和资源未充分利用等经营危机。通过建立合理的客户价值评估模型,对客户进行分群,分析及比较不同客户群的客户价值,并制定相应的营销策略,对不 同的客户群提供个性化的服务

**2、任务分析 **

(1)了解航空公司现状 目前该航空公司已积累了大量的会员档案信息和其乘坐航班记录。以 2014 年 3 月 3 日 为结束时间,选取宽度为两年的时间段作为分析观测窗口,抽取观测窗口内有乘机记录的所 有客户的详细数据,形成历史数据,总共 62988 条记录。其中包含了会员卡号、入会时间、 性别、年龄、会员卡级别、工作地城市、工作地所在省份、工作地所在国家、观测的窗口结 束时间、总累计积分、观测窗口的总飞行千米数、观测窗口内的飞行次数、平均乘机时间间 隔和平均折扣系数等特征

表航空公司数据特征说明

表名特征名称特征说明
客户基本信息MEMBER_NO会员卡号
客户基本信息FFP_DATE入会时间
客户基本信息FIRST_FLIGHT_DATE第一次飞行日期
客户基本信息GENDER性别
客户基本信息FFP_TIER会员卡级别
客户基本信息WORK_CITY工作地城市
客户基本信息WORK_PROVINCE工作地所在省份
客户基本信息WORK_COUNTRY工作地所在国家
客户基本信息AGE年龄
乘机信息FLIGHT_COUNT观测窗口内的飞行次数
乘机信息LOAD_TIME观测窗口的结束时间
乘机信息LAST_TO_END最后一次乘机时间至观测窗口结束时长
乘机信息AVG_DISCOUNT平均折扣率
乘机信息SUM_YR观测窗口的票价收入
乘机信息SEG_KM_SUM观测窗口的总飞行公里数
乘机信息LAST_FLIGHT_DATE末次飞行日期
乘机信息AVG_INTERVAL平均乘机时间间隔
乘机信息MAX_INTERVAL最大乘机间隔
积分信息EXCHANGE_COUNT积分兑换次数
积分信息EP_SUM总精英积分
积分信息PROMOPTIVE_SUM促销积分
积分信息PARTNER_SUM合作伙伴积分
积分信息POINTS_SUM总累计积分
积分信息POINT_NOTFLIGHT非乘机的积分变动次数
积分信息BP_SUM总基本积分

结合目前航空公司的数据情况,可以实现以下目标

a、借助航空公司客户数据,对客户进行分群

b、对不同的客户类别进行特征分析,比较不同类别客户的客户价值

c、对不同价值的客户类别提供个性化服务,制定相应的营销策略

(2)认识客户价值分析

全球经济环境和市场环境已经悄然改变,企业的业务逐步从以产品为主导转向以客户需 求为主导。一种全新的“以客户为中心”的业务模式正在形成并被提升到前所未有的高度。 然而与客户保持关系需要花费成本,企业所拥有的客户中只有一部分能为企业带来利润。企 业的资源也是有限的,忽视高潜力的客户而对所有客户都提供同样的服务,将使企业的资源 无法发挥其最大效用去创造最大化的利润。任何企业要想生存和发展,都必须获得利润,追 求利润最大化是企业生存和发展的宗旨之一。所以企业不可能也不应该和所有的客户都保持 同样的关系。客户营销战略的倡导者 Jay& Adam Curry 从对国外数百家公司进行的客户营销 实施的经验中提炼了如下经验

a、公司收入的 80%来自顶端的 20%的客户

b、20%的客户其利润率为 100%

c、90%以上的收入来自现有客户

d、大部分的营销预算经常被用在非现有客户上

e、5%~30%的客户在客户金字塔中具有升级潜力

f、客户金字塔中的客户升级 2%,意味着销售收入增加 10%,利润增加 50%

这些经验也许并不完全准确,但是它揭示了新时代客户分化的趋势,也说明了对客户价 值分析的迫切性和必要性。如果把客户的盈利性加以分析,就会发现客户盈利结构已发生了 重大变化,只有特定的一部分客户给企业带来了利润。企业如果想获得长期发展,必须对这 类客户做到有效的识别和管理。如果用同样的方法应对所有与个业有业务往来的客户,必然 不会获得成功

众多的企业管理者虽然知道客户价值分析的重要性,但对如何进行客户价值分析却知之 甚少。如何全方位、多角度地考虑客户价值因素,进行有效的客户价值分析,这是摆在所有 企业面前需要认真思索的一个问题。只有甄选出有价值的客户并将精力集中在这些客户身上, 才能有效地提升企业的竞争力,使企业获得更大的发展

在客户价值分析领域,最具影响力并得到实证检验的理论与模型有客户终生价值理论客 户价值金字塔模型、策略评估矩阵分析法和 RFM 客户价值分析模型等。本章将运用改进的客 户价值 RFM 模型进行分析

(3)熟悉航空客户价值分析的步骤与流程

航空客户价值分析项目的总体流程下图所示:

主要包括以下 4 个步骤:

a、抽取航空公司 2012 年 4 月 1 日至 2014 年 3 月 31 日的数据

b、对抽取的数据进行数据清洗、特征构建和标准化等操作

c、基于 RFM 模型,使用 K-Means 算法进行客户分群

d、针对模型结果得到不同价值的客户,采用不同的营销手段,提供定制化的服务

八、预处理航空客户数据

1、任务描述

航空公司客户原始数据存在少量的缺失值和异常值,需要清洗后才能用于分析。同时由 于原始数据的特征过多,不便直接用于客户价值分析,因此需要对特征进行筛选,挑选出衡 量客户价值的关键特征

2、任务分析

对航空客户数据进行预处理可以分为以下 3 个步骤:

(1)处理数据缺失值与异常值

通过对数据观察发现原始数据中存在票价为空值、票价最小值为 0、折扣率最小值为 0 总飞行千米数大于 0 的记录。票价为空值的数据可能是由于不存在乘机记录造成的。其他的 数据可能是由于客户乘坐 0 折机票或者积分兑换造成的。由于原始数据量大,这类数所占比 例较小,对问题影响不大,因此对其进行丢弃处理。具体处理方法如下:

a、丢弃票价为空的记录

b、丢弃票价为 0、平均折扣率不为 0、总飞行千米数大于 0 的记录

(2)构建航空客户价值分析关键特征

本项目的目标是客户价值分析,即通过航空公司的客户数据识别不同价值的客户,客户 价值应用最广泛的模型是 LRFMC 模型

(3)RFM 模型介绍

R( Recency)指的是最近一次消费时间与截止时间的间隔,通常情况下,最近一次消费 时间与截止时间的间隔越短,客户对即时提供的商品或是服务也最有可能感兴趣。这也是为 什么消费时间间隔为 0~6 个月的顾客收到的沟通信息多于一年以上的客户

最近一次消费时间与截止时间的间隔不仅能够为确定促销客户群体提供依据,还能够从 中得出企业发展的趋势。如果分析报告显示距离最近一次消费时间很近的客户在增加,则表 示该公司是个稳步上升的公司。反之,距离最近一次消费时间很近的客户越来越少,则说明 该公司需要找到问题所在,及时调整营销策略

F(Frequency)指某段时回内所消费的次数。消费频率越高的顾客,也是满意度越高的顾 客,其忠诚度越高,顾客价值也就越大。增加顾客购买的次数意味着从竞争对手处抢得市场 占有率,赚取营业额。商家需要做的,是通过各种营销方式去不断地刺激顾客消费,提高他 们的消费频率,提升店铺的复购率

M( Monetary)指顾客在某段时间内所消费的金额。消费金额越大的顾客,他们的消费能 力自然也就越大,这就是所谓“20%的顾客贡献了 80%的销售额”的二八法则。而这批顾客 也必然是商家在进行营销活动时需要特别照顾的群体,尤其是在商家前期资源不足的时候。 不过需要注意一点,不论采用哪种营销方式,以不对顾客造成骚扰为大前提,否则营销只会 产生负面效果

在 RFM 模型理论中,最近一次消费时间与截时间的间隔、消费频率、消费金额是测算客 户价值最重要的特征。这 3 个特征对营销活动具有十分重要的意义。其中,最近一次消费时 间与截止时间的间隔是最有力的特征

(4)RFM 模型结果解读

RFM 模型包括 3 个特征,无法用平面坐标系来展示,所以这里使用三维坐标系进行展示, 如下图所示,x 轴表示 R 特征( Recency),y 轴表示 F 特征( Frequency),z 轴表示 M 指标 ( Monetary)。每个轴一般会用 5 级表示程度, 1 为最小, 5 为最大。需要特别说明的是 R 特征。在 x 轴上,R 值越大,代表该类客户最近一次消费与截止时间的消费间隔越短,客户 R 维度上的质量越好。在每个轴上划分 5 等级,等同于将客户划分成 5×5×5=125 种类型。 这里划分为 5 级并不是严格的要求,一般是根据实际研究需求和顾客的总量进行划分的,对 于是否等分的问题取决于该维度上客户的分布规律

下图中,左上角方框的客户 RFM 特征取值为 155。R 值是比较小的,说明该类客户最近 都没有来店消费,原因可能是最近比较忙,或者对现有的产品或服务不满意,或者是找到了 更好的商家。R 特征数值变小需要企业管理人员引起重视,说明该类客户可能流失,对企业 造成损失。消费频率 F 很高,说明客户很活跃,经常到商家店里消费。消费金额 M 值很高, 说明该类客户具备一定的消费能力,为店里贡献了很多的营业额。这类客户总体分析比较优 质,但是 R 特征时间近度值较小,其往往是需要营销优化的客户群体

同理,若客户 RFM 特征取值为 55 则可以判定该客户为最优质客户,即该类客户最近到 商家消费过,消费频率很高,消费金额很大。该类客户往往是企业利益的主要贡献者,需要 重点关注与维护

(5)航空客户价值分析的 LRFMC 模型

在 RFM 模型中,消费金额表示在一段时间内客户购买该企业产品金额的总和。由于航空 票价受到运输距离、舱位等级等多种因素的影响,同样消费金额的不同旅客对航空公司的价 值是不同的,比如一位购买长航线、低等级舱位票的旅客与一位购买短航线、高等级舱位票 的旅客相比,后者对于航空公司而言价值可能更高。因此这个特征并不适合用于航空公司的 客户价值分析。本章选择客户在一定时间内累积的飞行里程 M 和客户在一定时间内乘坐舱位所对应的折扣系数的平均值 C 两个特征代替消费金额。此外,航空公司会员入会时间的长短 在一定程度上能够影响客户价值,所以在模型中增加客户关系长度 L,作为区分客户的另一 特征

本节将客户关系长度 L、消费时间间隔 R、消费频率 F、飞行里程 M 和折扣系数的平均 值 C 这 5 个特征作为航空公司识别客户价值的特征(如下表所示),记为 LRFMC 模型

表 xx 特征含义

模型LRFMC
航空公司 LRFMC 模型会员入会时间 距观测窗口结 束的月数客户最近一次乘 坐公司飞机距观 测窗口结束的月 数客户在观测窗口内乘坐公司飞机的次数客户在观测窗口内累计的飞行里程客户在观测窗口内乘坐舱位所对 应的折扣系数的平均值

根据航空公司客户价值 LRFMC模型,选择与 LRFMC特征相关的 6个特征 FFP DATE、LOAD TIME、 FLIGHT COUNT、 AVG DISCOUNT、 SEG KM SUM、LAST TO END。删除与其不相关、弱 相关或冗余的特征,例如会员卡号、性别、工作地城市、工作地所在国家、年龄等特征。经 过特征选择后的数据集下表所示

表 xx 特征选取后的数据集

LOAD_TIMEFFP_DATELAST_TO_ENDFLIGHT_COUNTSEG_KM_SUMAVG_DISCOUNT
2014/3/312013/3/1623141268501.02
2014/3/312012/6/266651847300.76
2014/3/312009/12/8233603871.27
2014/3/312009/12/101236622591.02
2014/3/312011/8/251422547301.36

由于原始数据中并没有直接给出 LRFMC 模型的 5 个特征,需要通过原始数据提取这 5 个特征

会员入会时间距观测窗口结束的月数 L=观测窗口的结束时间一入会时间(单位月),如 下式所示

L=LOAD TIME-FFP DATE

客户最近一次乘坐公司飞机距观测窗口结束的月数 R=最后一次乘机时间至观察窗口末 端时长(单位:月),如下式所示

R = RELAST TO END

客户在观测窗口内飞行里程 M= 观测窗口总飞行千米数(单位:千米)

F=FLIGHT-COUNT

客户在观测窗口乘坐舱位对应的折扣系数的平均值 c=平均折扣率(单位:无)

M=SEG_KM_SUM

(6)标准化 5 个特征:

完成五个特征的构建以后,对每个特征数据分布情况进行分析,其数据的取值范围如表 所示。从表中数据可以发现,五个特征的取值范围数据差异较大,为了消除数量级数据带来 的影响,需要对数据做标准化处理。(可以尝试:离差标准化)

九、项目结果分析

代码实现:

import pandas as pd
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
import matplotlib.pyplot as plt
import numpy as np


def load_data():
    """
    加载数据
    :return: air_data
    """
    air_data = pd.read_csv('./air_data.csv', encoding='ansi')

    return air_data


def min_max_sca(data):
    """
    离差标准化
    :param data:需要标准化的数据
    :return: 标准化之后的数据
    """
    data = (data - data.min()) / (data.max() - data.min())

    return data


def deal_data(air_data):
    """
    数据处理
    :param air_data: 需要处理的数据
    :return: 处理之后的air_data
    """
    # (1)缺失值、异常值处理
    # a、丢弃票价为空的记录。 ---保留票价不为空的记录
    # 票价 ---SUM_YR_1 SUM_YR_2
    # 观点1 :两列票价 都不为空,此时票价才不为空
    boo_index_1 = air_data.loc[:, 'SUM_YR_1'].notnull()
    boo_index_2 = air_data.loc[:, 'SUM_YR_2'].notnull()
    # 同时满足不为空,票价才不为空
    boo_index = boo_index_1 & boo_index_2
    # 筛选 保留票价不为空的记录
    air_data = air_data.loc[boo_index, :]
    # print('丢弃票价为空的记录的结果:\n', air_data.shape)
    # 观点2: 只要有一个票价不为空,此时就认为票价不为空

    # b、丢弃票价为 0、平均折扣率不为 0、总飞行千米数大于 0 的记录。
    # ---删除不盈利数据 ---保留盈利数据
    # 保留 票价> 0  折扣>0 飞行里程 >0
    # 观点1: 只要有一个票价 > 0  ---认为票价 >0
    boo_id_1 = air_data.loc[:, 'SUM_YR_1'] > 0
    boo_id_2 = air_data.loc[:, 'SUM_YR_2'] > 0
    # 观点2: 必须两个票价同时 > 0  -----认为票价> 0
    # 折扣 > 0
    boo_id_3 = air_data.loc[:, 'avg_discount'] > 0
    # 飞行里程 > 0
    bool_id_4 = air_data.loc[:, 'SEG_KM_SUM'] > 0
    #
    bool_id = (boo_id_1 | boo_id_2) & boo_id_3 & bool_id_4

    # 筛选数据 ---保留 票价> 0  折扣>0 飞行里程 >0
    air_data = air_data.loc[bool_id, :]
    # print('丢弃票价为 0、平均折扣率不为 0、总飞行千米数大于 0 的记录结果为:\n',
    #       air_data.shape)

    # (2)特征筛选--特征构建
    # LRFMC模型  ---构建LRFMC 5个特征,来使用这个5个特征对客户进行聚类
    # L  :会员入会时间距离窗口结束时间的月数
    # 入会时间---FFP_DATE   窗口结束时间 ----LOAD_TIME
    air_data.loc[:, 'L_days'] = pd.to_datetime(air_data.loc[:, 'LOAD_TIME']) - pd.to_datetime(
        air_data.loc[:, 'FFP_DATE'])
    # 由 日 计算月数
    air_data.loc[:, 'L'] = air_data.loc[:, 'L_days'].dt.days / 30

    # R  :最后一次乘坐飞机距离窗口结束的月数
    #  LAST_TO_END ---最后一次乘坐飞机距离窗口结束的时长  ---日
    air_data.loc[:, 'R'] = air_data.loc[:, 'LAST_TO_END'] / 30

    # F  :观察窗口内乘坐飞机的次数
    air_data.loc[:, 'F'] = air_data.loc[:, 'FLIGHT_COUNT']
    # M  :观察窗口内的飞行里程
    air_data.loc[:, 'M'] = air_data.loc[:, 'SEG_KM_SUM']
    # C  :观察窗口内的折扣系数
    air_data.loc[:, 'C'] = air_data.loc[:, 'avg_discount']

    # 筛选数据
    air_data = air_data.loc[:, ['L', 'R', 'F', 'M', 'C']]
    # print('air_data:\n', air_data)

    # (3)标准化
    # 离差标准化、标准差标准化、小数定标标准化
    for column in air_data.columns:
        air_data.loc[:, column] = min_max_sca(air_data.loc[:, column])

    # print('air_data:\n', air_data)

    return air_data


def k_means(air_data, k):
    """
    基于k-means对于航空用户进行聚类
    :param air_data: 数据
    :param k: 聚类的类别数目
    :return: y_predict, center, score
    """
    # 实例化算法对象
    km = KMeans(n_clusters=k)
    # 训练数据并构建模型
    km.fit(air_data)
    # 预测
    y_predict = km.predict(air_data)

    # 获取聚类中心
    center = km.cluster_centers_

    # 获取轮廓系数
    score = silhouette_score(air_data.iloc[30000:31000, :], y_predict[30000:31000])

    return y_predict, center, score


def show_res(center):
    """
    绘制雷达图
    :param center: 聚类中心
    :return: None
    """
    # 创建画布
    plt.figure()
    # 默认不支持中文 ---修改RC参数
    plt.rcParams['font.sans-serif'] = 'SimHei'
    # 增加字体之后变得不支持负号,需要修改RC参数让其继续支持负号
    plt.rcParams['axes.unicode_minus'] = False

    # 绘图
    datalength = 5
    # 准备角度
    angle = np.linspace(0, 2 * np.pi, datalength, endpoint=False)
    # 闭合角度
    angle = np.concatenate((angle, [angle[0]]), axis=0)
    print('angle:\n', angle)

    # 准备数据
    # 闭合数据
    data = np.concatenate((center, center[:, 0:1]), axis=1)
    print('data:\n', data)

    # 绘图
    for i in range(data.shape[0]):
        plt.polar(angle, data[i, :])

    # 修改刻度
    plt.xticks(angle[:-1], ['L', 'R', 'F', 'M', 'C'])
    # 增加标题
    plt.title('航空用户聚类结果')

    # 增加图例
    lenged = ['第一类客户', '第二类客户', '第三类客户', '第四类客户', '第五类客户']
    plt.legend(lenged, loc='upper right', bbox_to_anchor=(1.2, 1.1))

    # 保存
    plt.savefig('./航空用户聚类结果.png')

    # 展示
    plt.show()


# 6、分析可视化结果----划分客户为不同价值客户

# 7、根据不同价值的客户,----不同的营销策略

def main():
    """
    主函数
    :return:
    """
    # # 1、了解航空公司现状
    # # (1)常旅消失问题、竞争力下降、公司资源未充分利用
    # # (2)公司数据
    # # 2012-3-3 ---2014-3-3 两年的会员信息数据、乘坐航班信息数据
    # # 共计62988条,会员基本信息、乘坐航班信息
    # # (3) 客户价值
    # # a、公司收入的 80%来自顶端的 20%的客户。
    # # b、20%的客户其利润率为 100%。
    # # c、90%以上的收入来自现有客户。
    # # d、大部分的营销预算经常被用在非现有客户上。
    # # e、5%~30%的客户在客户金字塔中具有升级潜力。
    # # f、客户金字塔中的客户升级 2%,意味着销售收入增加 10%,利润增加 50%。

    # 2、加载数据
    air_data = load_data()
    print('air_data:\n', air_data)
    print('air_data:\n', air_data.columns)

    # 3、数据处理
    air_data = deal_data(air_data)
    print('数据处理之后的结果为:\n', air_data)

    # 4、用户聚类
    # 确定聚类类别
    k = 5
    y_predict, center, score = k_means(air_data, k)
    print('预测值:\n', y_predict)
    print('聚类中心:\n', center)
    print('轮廓系数:\n', score)

    # 5、结果可视化
    # 绘制雷达图
    # 使用聚类中心代替 各类用户
    show_res(center)


if __name__ == '__main__':
    main()

以下为航空用户聚类结果:

七、K-Means 算法特点

(1)聚类结果收到聚类类别数目的影响

(2)聚类具有随机性,容易收敛到局部最优解(多次聚类)

(3)最终的聚类结果中,簇内相似度较高,簇间相异性较高