客户分类:

1、将客户分为三类:超级VIP、vip、普通用户

2、需要你将不同的类的数据,在图上显示出来,用不同的颜色

3、返回三个类中,各包含哪些点

K-means算法(三种方法)

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


def decimal_clean(arr):
    '''
    将输入序列进行小数定标标准化
    :param arr:输入的待优化的序列
    :return:标准化后的序列
    '''
    k = np.ceil(np.log10(np.max(np.abs(arr))))
    return arr / 10 ** k


def K_means(xy_line, n_type, n=None, thr=None):
    '''
    K_means算法函数
    :param xy_line: 第一列为x坐标第二列为y坐标的二维序列
    :param n_type: 分类数目
    :param n: 最大迭代次数
    :param thr: 阈值
    :return: 调用各自函数后的返回结果直接返回到外部
    '''
    # 判断如果输入参数中如果n值和thr值都为None, 则调用自我迭代函数, 迭代退出条件, 新的参考点位置与上一次参考点位置一致
    if n is None and thr is None:
        return iter_self(xy_line, n_type)
    elif n is not None:  # 判断如果n值不为None, 则调用按次数迭代函数, 迭代退出条件, 迭代次数等于给定次数
        return iter_n(xy_line, n_type, n)
    else:  # 如果前两种条件都不符合, 那么只有一种可能, 就是thr不为None, 调用阈值迭代函数, 迭代退出条件, 新的参考点与旧的参考点求欧式距离, 其最大值小于阈值
        return iter_thr(xy_line, n_type, thr)


def iter_self(xy_line, n_type):
    '''
    自我迭代函数, 自我生成n_type个随机参考点进行循环迭代, 每次迭代的最后利用本次分区结果求新的参考点
    迭代退出条件, 新的参考点跟上一次的参考点位置一致
    :param xy_line: 输入的x_y序列
    :param n_type: 分类数目
    :return: 划分完成的区域
    '''
    # 进入函数, 初始化旧参考点位置序列与新参考点位置序列
    old_pos = [[random.random(), random.random()] for i in range(n_type)]
    new_pos = [[] for i in range(n_type)]
    # 进入循环迭代
    while True:
        # 每一次的循环都刷新一次分区列表
        area = [[] for i in range(n_type)]
        # 对每一个点遍历
        for pos in range(len(xy_line)):
            # 获得这个点的坐标
            this_pos = [xy_line.iloc[pos, 0], xy_line.iloc[pos, 1]]
            # 创建空列表,用于存放该点与各个参考点的欧式距离
            distance = []
            # 针对每一个点的坐标对所有中心点求欧式距离
            for i in range(n_type):
                distance.append(Oup_distance(this_pos, old_pos[i]))
            # 找到所有距离中最小点的索引, 即该点属于第几区
            min_index = distance.index(min(distance))
            # 根据该点的区号, 进行分区
            area[min_index].append(this_pos)
        # 判断是否有空区域, 如果有, 重新调用本函数, 得到返回结果之后直接break
        if [] in area:
            area = iter_self(xy_line, n_type)
            break
        # 来到此步, 证明各个分区都是有值的, 用均值法求出各区对应的新的参考点坐标
        new_pos = reflush_pos(area, n_type, new_pos)
        # 判断新参考点与就参考点是否一致, 一致即可退出循环
        if new_pos == old_pos:
            break
        else:  # 否则将新的点序列深拷贝给旧的点序列, 新的点序列置空
            old_pos = new_pos.copy()
            new_pos = [[] for i in range(n_type)]
    return area


def iter_n(xy_line, n_type, n):
    '''
    按次数迭代函数, 自我生成n_type个随机参考点进行循环迭代, 每次迭代的最后利用本次分区结果求新的参考点
    :param xy_line:输入的x_y序列
    :param n_type:分类数目
    :param n:迭代次数
    :return:划分完成的区域序列
    '''
    # 进入函数, 初始化旧参考点序列, 因为本函数按照给定次数迭代, 所以求出新的参考点直接修改到旧参考点本身即可
    old_pos = [[random.random(), random.random()] for i in range(n_type)]
    # tt为已迭代次数
    tt = 0
    while True:
        # 每一次的循环都刷新一次分区列表
        area = [[] for i in range(n_type)]
        # 对每一个点遍历
        for pos in range(len(xy_line)):
            # 获得这个点的坐标
            this_pos = [xy_line.iloc[pos, 0], xy_line.iloc[pos, 1]]
            # 创建空列表,用于存放该点与各个参考点的欧式距离
            distance = []
            # 针对每一个点的坐标对所有中心点求欧式距离
            for i in range(n_type):
                distance.append(Oup_distance(this_pos, old_pos[i]))
            # 找到所有距离中最小点的索引, 即该点属于第几区
            min_index = distance.index(min(distance))
            # 根据该点的区号, 进行分区
            area[min_index].append(this_pos)
        # 判断是否有空区域
        if [] in area:
            area = iter_n(xy_line, n_type, n)
            break
        # 各个点都分好区之后, 用均值法求出新的参考坐标点, 新参考点的值赋给旧参考点
        old_pos = reflush_pos(area, n_type, old_pos)
        # 迭代了一次, tt + 1
        tt += 1
        # 判断tt是否大于等于给点迭代次数n, 如果是则退出迭代
        if tt >= n:
            break
    return area


def iter_thr(xy_line, n_type, thr):
    '''
    阈值迭代函数, 自我生成n_type个随机参考点进行循环迭代, 每次迭代的最后利用本次分区结果求新的参考点
    :param xy_line: 输入的x_y序列
    :param n_type: 分类数目
    :param thr: 阈值
    :return: 划分完成的区域序列
    '''
    old_pos = [[random.random(), random.random()] for i in range(n_type)]
    new_pos = [[] for i in range(n_type)]
    while True:
        # 每一次的循环都刷新一次分区列表
        area = [[] for i in range(n_type)]
        # 对每一个点遍历
        for pos in range(len(xy_line)):
            # 获得这个点的坐标
            this_pos = [xy_line.iloc[pos, 0], xy_line.iloc[pos, 1]]
            # 创建空列表,用于存放该点与各个参考点的欧式距离
            distance = []
            # 针对每一个点的坐标对所有中心点求欧式距离
            for i in range(n_type):
                distance.append(Oup_distance(this_pos, old_pos[i]))
            # 找到所有距离中最小点的索引, 即该点属于第几区
            min_index = distance.index(min(distance))
            # 根据该点的区号, 进行分区
            area[min_index].append(this_pos)
        # 判断是否有空区域
        if [] in area:
            area = iter_thr(xy_line, n_type, thr)
            break
        # 各个点都分好区之后, 用均值法求出新的参考坐标点
        new_pos = reflush_pos(area, n_type, new_pos)
        # 对新旧参考各点求对应欧式距离, 最大值小于阈值时退出循环
        diff_n = []
        for i in range(n_type):
            diff = Oup_distance(new_pos[i], old_pos[i])
            diff_n.append(diff)
        # 判断如果距离序列中的最大值都比阈值小, 即可退出迭代
        if max(diff_n) < thr:
            break
    return area


def reflush_pos(area, n_type, pos):
    '''
    刷新参考点位置
    :param area: 分区序列
    :param n_type: 种类数目
    :param pos: 用于存放新参考点的序列
    :return: 新的参考点序列
    '''
    # 对area进行遍历
    for i in range(n_type):
        # 将area[i]深拷贝赋值给this, 在进行numpy数组转换
        this = area[i].copy()
        # 如果不进行深拷贝, 此步会将整个area转换成numpy数组
        this_area = np.array(this)
        # 求x序列和y序列的均值, 并赋值给pos
        x_mean = np.mean(this_area[:, 0])
        y_mean = np.mean(this_area[:, 1])
        pos[i] = [x_mean, y_mean].copy()
    return pos


def Oup_distance(pos1, pos2):
    '''
    计算两点之间的欧式距离
    :param pos1:点1的(x, y)坐标
    :param pos2:点2的(x, y)坐标
    :return:两点之间的欧式距离值
    '''
    Oup = np.sqrt((pos1[1] - pos2[1]) ** 2 + (pos1[0] - pos2[0]) ** 2)
    return Oup


company = pd.read_csv(r"C:\Users\Administrator\Desktop\company.csv", encoding="gbk")
# 这里用的是iloc的提取方法, 也可以根据个人习惯使用其他提取方法
# 此处特意将时间和消费金额两列作为x和y使用concat函数合并到一个dataframe, 方便后面的处理
x_y = pd.concat([decimal_clean(company.iloc[:, 2]), decimal_clean(company.iloc[:, 1])], axis=1, join="outer")

AREA = K_means(x_y, 3)

# 画图
plt.figure()
for area in AREA:
    # area是二维列表,将其变为二维数组
    area_np = np.array(area)
    x = area_np[:, 0]
    y = area_np[:, 1]
    # 将x,y联系起来
    plt.scatter(x, y)
# 显示画图
plt.show()
 

相关文章:

  • 2021-12-27
  • 2021-06-23
  • 2021-11-21
猜你喜欢
  • 2022-12-23
  • 2022-02-25
  • 2021-10-02
  • 2021-05-27
相关资源
相似解决方案