1. 程式人生 > >K-means演算法(三種方法)

K-means演算法(三種方法)

 客戶分類:

1、將客戶分為三類:超級VIP、vip、普通使用者

2、需要你將不同的類的資料,在圖上顯示出來,用不同的顏色

3、返回三個類中,各包含哪些點

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()