天池数据分析达人赛3:汽车产品聚类(含代码)

目录

一、赛题介绍

二、数据描述性统计

2.1. 查看缺失值、重复值

2.2. 查看数据统计信息

2.3. 查看数据分布

2.4. 查看异常值

2.5. 数据相关性

三、特征处理

四、数据建模

4.1  变量选取及处理

4.2  确定聚类数(肘部法及平均轮廓系数)

4.3  模型训练

五、特征可视化 

5.1  雷达图

5.2  饼图结合柱形图

六、竞品分析思路

总结


一、赛题介绍

赛题以竞品分析为背景,通过数据的聚类,为汽车提供聚类分类。对于指定的车型,可以通过聚类分析找到其竞品车型。通过这道赛题,鼓励学习者利用车型数据,进行车型画像的分析,为产品的定位,竞品分析提供数据决策。

天池数据分析达人赛3:汽车产品聚类(含代码)_第1张图片

二、数据描述性统计

2.1. 查看缺失值、重复值

import pandas as pd
df=pd.read_csv("/car_price.csv")
print(df.isnull().any(axis=1).sum())
df[df.duplicated()==True]
# 无缺失值及重复值

2.2. 查看数据统计信息

# 分离数值变量与分类变量
Nu_feature = list(df.select_dtypes(exclude=['object']).columns)  # 数值变量
Ca_feature = list(df.select_dtypes(include=['object']).columns)
print(len(Nu_feature),Nu_feature)
print(len(Ca_feature),Ca_feature)
df.describe().T

天池数据分析达人赛3:汽车产品聚类(含代码)_第2张图片

205个样本,16个数值变量, 10个分类变量

2.3. 查看数据分布

import seaborn as sns
import warnings
warnings.filterwarnings("ignore")
plt.figure(figsize=(30,25))
i=1
for col in Nu_feature:
    ax=plt.subplot(4,4,i)
    ax=sns.distplot(df[col],color='red',hist=False)
    ax.set_xlabel(col)
    ax.set_ylabel('Frequency')
    i+=1
plt.show()
#  大部分的数据都服从正态分布

天池数据分析达人赛3:汽车产品聚类(含代码)_第3张图片

 2.4. 查看异常值

import matplotlib.pyplot as plt
plt.figure(figsize=(30,30))   
j=1
for col in Nu_feature:
    ax=plt.subplot(4,4,j)
    ax=sns.boxplot(data=df[col])
    ax.set_xlabel(col)
    ax.set_ylabel('Frequency')
    j+=1
plt.show()
# 图中呈现的异常值是受样本数量的影响,对比原始数据,都非真正异常数据,比如豪车的价格及相关配置在图中可能就是异常值,但其实正常

天池数据分析达人赛3:汽车产品聚类(含代码)_第4张图片

2.5. 数据相关性

correlation_matrix=df.corr()
plt.figure(figsize=(12,10))
sns.heatmap(correlation_matrix,vmax=0.9,linewidths=0.05,cmap="RdGy")
# 轴距与排量的相关性高于马力,所以选择马力
# 风险评级与车价有负相关关系,个人理解,以盗抢为例,四川是全国盗抢车最高的地区,高价格车辆的防盗措施肯定要优于低价格的,
# 然后高价格的车辆一般不会随意停放路边,有的甚至住在别墅,安保措施较好

天池数据分析达人赛3:汽车产品聚类(含代码)_第5张图片

三、特征处理

df['doornumber'] = df['doornumber'].map({'two':2,'four':4})
# 将车门数转化为数字,用于后面的建模

四、数据建模

4.1 变量选取及处理

import numpy as np
from sklearn.metrics import silhouette_score  # 导入轮廓系数指标
from sklearn.cluster import KMeans  # KMeans模块
from sklearn.preprocessing import MinMaxScaler, OneHotEncoder,StandardScaler
df1=df[['symboling','wheelbase','horsepower','price','doornumber']]
cols=['carbody','drivewheel']
# 分类变量转化
model_ohe = OneHotEncoder(sparse=False) 
ohe_matrix = model_ohe.fit_transform(df[cols])
# 数值标准化
model_scaler = MinMaxScaler()  
data_scaled = model_scaler.fit_transform(df1)  
# 合并所有维度
X = np.hstack((data_scaled, ohe_matrix))
X.shape 
# 分类变量选取个人看法:选取区分度较大的变量,不要选用太多的分类变量(效果会变差,特征区分度也不明显),可以结合业务经验选取。

4.2 确定聚类数(肘部法及平均轮廓系数)

sse = []
scores = []
silhouette_int = -1  # 初始化的平均轮廓系数阀值
for n_clusters in range(2, 8):
    model_kmeans = KMeans(n_clusters=n_clusters)  # 建立聚类模型对象
    labels_tmp = model_kmeans.fit_predict(X)  # 训练聚类模型
    silhouette_tmp = silhouette_score(X, labels_tmp)  # 得到每个K下的平均轮廓系数
    if silhouette_tmp > silhouette_int:  # 如果平均轮廓系数更高
        best_k = n_clusters  # 保存K将最好的K存储下来
        silhouette_int = silhouette_tmp  # 保存平均轮廓得分
        best_kmeans = model_kmeans  # 保存模型实例对象
        cluster_labels_k = labels_tmp  # 保存聚类标签
    scores.append(silhouette_tmp)
    sse.append(model_kmeans.inertia_) #整体簇内平方和

x = [i for i in range(2,8)]
plt.figure(figsize=(10, 5))
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.subplot(1,2,1)
plt.title('KMeans聚类SSE系数')
plt.xlabel('聚类数')
plt.plot(x, sse, 'o-')
 
plt.subplot(1,2,2)
plt.title('KMeans聚类平均轮廓系数')
plt.xlabel('聚类数')
for i in range(2, 8):
    plt.text(i, scores[i-2], (str(round(scores[i-2], 2))))    
plt.plot(x, scores, 'o-')
# 拐点出现在5,轮廓系数0.53,效果还可以,初步决定分为5类

天池数据分析达人赛3:汽车产品聚类(含代码)_第6张图片

4.3  模型训练

model_kmean = KMeans(n_clusters=5)
cluster_labels_ = model_kmean.fit_predict(X)
# 将类别结合原文件导出
cluster_labels = pd.DataFrame(cluster_labels_, columns=['clusters'])  # 获得训练集下的标签信息
merge_data = pd.concat((df, cluster_labels), axis=1) 
merge_data.to_csv('/merge_data.csv')导出文件

五、特征可视化 

5.1  雷达图

# 将相关特征合并 
cols1=['carbody','drivewheel','clusters']
df2=pd.read_csv("/merge_data.csv",usecols=cols1)
merge_data1=pd.concat((df1, df2), axis=1)
merge_data1

天池数据分析达人赛3:汽车产品聚类(含代码)_第7张图片

clustering_count = pd.DataFrame(merge_data['car_ID'].groupby(merge_data['clusters']).count()).T.rename({'car_ID': 'counts'})  # 计算每个聚类类别的样本量
clustering_ratio = (clustering_count / len(merge_data)).round(2).rename({'counts': 'percentage'}) # 计算每个聚类类别的样本量占比
# 统计各类别下的特征信息
cluster_features = []  # 空列表,用于存储最终合并后的所有特征信息
for line in range(5):  # 读取每个类索引
    label_data = merge_data1[merge_data1['clusters'] == line]  # 获得特定类的数据
    part1_data = label_data.iloc[:, 0:5]  # 获得数值型数据特征
    part1_desc = part1_data.describe().round(3)  # 得到数值型特征的描述性统计信息的均值
    merge_data2 = part1_desc.iloc[1, 0:5] # 将clusters这一列去掉
    part2_data = label_data.iloc[:, 5:-1]  # 获得字符串型数据特征
    part2_desc = part2_data.describe(include='all')  # 获得分类变量特征的描述性统计信息,include='all'统计分类变量
    merge_data3 = part2_desc.iloc[2, :]  # 取分类变量的众数
    merge_line = pd.concat((merge_data2, merge_data3), axis=0)  # 将数值型和分类特征沿行合并
    cluster_features.append(merge_line) 
cluster_pd = pd.DataFrame(cluster_features).T
all_cluster_set = pd.concat((clustering_count, clustering_ratio, cluster_pd),axis=0)  # 将每个聚类类别的所有信息合并
print(all_cluster_set)
num_sets = cluster_pd.iloc[:5, :].T.astype(np.float64)  # 获取要绘图的数据
print(num_sets)
#每个类别下的特征就比较明显的展示出来了

 天池数据分析达人赛3:汽车产品聚类(含代码)_第8张图片

# 雷达图展示
model_scaler1=StandardScaler() # 相比MinMaxScaler归一化,可以更明确区分特征
num_sets_max_min = model_scaler1.fit_transform(num_sets)  # 获得标准化后的数据
print(num_sets_max_min)
# part2 画布基本设置
fig = plt.figure(figsize=(8,8))  # 建立画布
ax = fig.add_subplot(111, polar=True)  # 增加子网格,注意polar参数
labels = np.array(merge_data2.index)  # 设置要展示的数据标签
cor_list = ['b', 'k', 'r', 'y', 'g', 'y', 'm', 'w','r','b']  # 定义不同类别的颜色
angles = np.linspace(0, 2 * np.pi, len(labels), endpoint=False)  # 计算各个区间的角度
angles = np.concatenate((angles, [angles[0]]))  # 建立相同首尾字段以便于闭合
radar_labels = np.concatenate((labels, [labels[0]])) # 新版的matplotlib,标签要建立相同首尾字段以便于闭合
# part3 画雷达图
for i in range(len(num_sets)):  # 循环每个类别
    data_tmp = num_sets_max_min[i, :]  # 获得对应类数据
    data = np.concatenate((data_tmp, [data_tmp[0]]))  # 建立相同首尾字段以便于闭合
    ax.plot(angles, data, 'o-', c=cor_list[i], label=i)  # 画线
    ax.fill(angles, data,color=cor_list[i],alpha=0.25)  # 填充背景
# part4 设置图像显示格式
ax.set_thetagrids(angles * 180 / np.pi, radar_labels, fontproperties="SimHei",fontsize=14)  # 设置极坐标轴
ax.set_title("各类别特征对比", fontproperties="SimHei",fontsize=16)  # 设置标题放置
ax.set_rlim(-2.5, 2)  # 设置坐标轴尺度范围
plt.legend(loc=0)  # 设置图例位置

天池数据分析达人赛3:汽车产品聚类(含代码)_第9张图片

 上图看出:类别3的平均轴距最长、平均价格最高、风险评级最低,类别2的平均马力最高、风险评级最高,类别4以四门为主,类别0以两门为主。类别之间还是有一定的区分度,下面进一步将每个类别特征进行细分。

5.2  饼图结合柱形图

# 读取分类好的数据
import pandas as pd
df=pd.read_csv("/merge_data.csv")
# 计算不同聚类类别的样本量和占比
label_count = df.groupby(['clusters'])['car_ID'].count()  # 计算频数
label_count_rate = label_count/ df.shape[0] # 计算占比
kmeans_record_count = pd.concat((label_count,label_count_rate),axis=1)
kmeans_record_count.columns=['record_count','record_rate']
# 计算不同聚类类别数值型特征
kmeans_numeric_features = df.groupby(['clusters'])['price','wheelbase','horsepower'].mean()
# 计算不同聚类类别分类型特征
doornumber_list=[]
carbody_list = []
drivewheel_list = []
for each_label in range(5):
    each_data = df[df['clusters']==each_label]
    doornumber_list.append(each_data.groupby(['doornumber'])['car_ID'].count()/each_data.shape[0])
    carbody_list.append(each_data.groupby(['carbody'])['car_ID'].count()/each_data.shape[0])
    drivewheel_list.append(each_data.groupby(['drivewheel'])['car_ID'].count()/each_data.shape[0])
kmeans_doornumber = pd.DataFrame(doornumber_list)    
kmeans_carbody = pd.DataFrame(carbody_list)
kmeans_drivewheel = pd.DataFrame(drivewheel_list)
kmeans_string_features = pd.concat((kmeans_doornumber,kmeans_carbody,kmeans_drivewheel),axis=1)
kmeans_string_features.index = range(5)  # 行索引为类别
kmeans_string_features=kmeans_string_features.fillna(0).round(2) # 空值部分填充为0
# 合并所有类别的分析结果
features_all = pd.concat((kmeans_record_count,kmeans_numeric_features,kmeans_string_features),axis=1)
features_all.rename(columns={2:'two',4:'four'},inplace=True) 
print(features_all)

天池数据分析达人赛3:汽车产品聚类(含代码)_第10张图片

import matplotlib.pyplot as plt
import numpy as np
# part 1 全局配置
fig = plt.figure(figsize=(18, 14))
titles = ['RECORD_RATE','AVG_price','AVG_wheelbase','AVG_horsepower','doornumber','carbody','drivewheel'] # 共用标题
line_index,col_index = 5,7 # 定义网格数
ax_ids = np.arange(1,36).reshape(line_index,col_index) # 生成子网格索引值

# part 2 画出5个类别的占比
pie_fracs = features_all['record_rate'].tolist()
for ind in range(len(pie_fracs)):
    ax = fig.add_subplot(line_index, col_index, ax_ids[:,0][ind]) # ax_ids[:,0][ind]取出对应值的索引
    init_labels = ['','','','',''] # 初始化空label标签,5个类别,所以是5个空值
    init_labels[ind] = 'cluster_{0}'.format(ind) # 设置标签
    init_colors = ['lightgray', 'lightgray', 'lightgray','lightgray','lightgray']
    init_colors[ind] = 'g' # 设置目标面积区别颜色
    ax.pie(x=pie_fracs, autopct='%3.0f %%',labels=init_labels,colors=init_colors)
    ax.set_aspect('equal') # 设置饼图为圆形
    if ind == 0:
        ax.set_title(titles[0])
        
# part 3  画出AVG_price均值
avg_orders_label = 'AVG_price'
avg_orders_fraces = features_all['price']
for ind, frace in enumerate(avg_orders_fraces):
    ax = fig.add_subplot(line_index, col_index, ax_ids[:,1][ind])
    ax.bar(x=range(5),height=[0,0,avg_orders_fraces[ind],0,0])# 画出柱形图
    ax.set_ylim((0, max(avg_orders_fraces)*1.2))
    ax.set_xticks([])
    ax.set_yticks([])
    if ind == 0:# 设置总标题
        ax.set_title(titles[1])
    # 设置每个柱形图的数值标签和x轴label
    ax.text(1,frace+0.3,s='{:.0f}'.format(frace),ha='center',va='top')
    
# part 4  画出AVG_wheelbase均值
avg_money_label = 'AVG_wheelbase'
avg_money_fraces = features_all['wheelbase']
for ind, frace in enumerate(avg_money_fraces):
    ax = fig.add_subplot(line_index, col_index, ax_ids[:,2][ind])
    ax.bar(x=range(5),height=[0,0,avg_money_fraces[ind],0,0])# 画出柱形图
    ax.set_ylim((0, max(avg_money_fraces)*1.2))
    ax.set_xticks([])
    ax.set_yticks([])
    if ind == 0:# 设置总标题
        ax.set_title(titles[2])
    # 设置每个柱形图的数值标签和x轴label
    ax.text(1,frace+4,s='{:.0f}'.format(frace),ha='center',va='top')

# part 5  画出AVG_horsepower均值
avg_money_label = 'AVG_horsepower'
avg_money_fraces = features_all['horsepower']
for ind, frace in enumerate(avg_money_fraces):
    ax = fig.add_subplot(line_index, col_index, ax_ids[:,3][ind])
    ax.bar(x=range(5),height=[0,0,avg_money_fraces[ind],0,0])# 画出柱形图
    ax.set_ylim((0, max(avg_money_fraces)*1.2))
    ax.set_xticks([])
    ax.set_yticks([])
    if ind == 0:# 设置总标题
        ax.set_title(titles[3])
    # 设置每个柱形图的数值标签和x轴label
    ax.text(1,frace+4,s='{:.0f}'.format(frace),ha='center',va='top')

# part 6  画出车门数
num_labels = ['two','four']
x_ticket = [i for i in range(len(num_labels))]
num_data = features_all[num_labels]
ylim_max = np.max(np.max(num_data)) # 获取Y轴最大值
for ind,each_data in enumerate(num_data.values):
    ax = fig.add_subplot(line_index, col_index, ax_ids[:,4][ind])
    ax.bar(x=x_ticket,height=each_data) # 画出柱形图
    ax.set_ylim((0, ylim_max*1.2))  # 将图形高度提升
    ax.set_xticks([])
    ax.set_yticks([])    
    if ind == 0:# 设置总标题
        ax.set_title(titles[4])
    # 设置每个柱形图的数值标签和x轴label
    num_values = ['{:.1%}'.format(i) for i in each_data]
    for i in range(len(x_ticket)):
        ax.text(x_ticket[i],each_data[i]+0.1,s=num_values[i],ha='center',va='top') # 图形添加数值
        ax.text(x_ticket[i],-0.15,s=num_labels[i],ha='center',va='bottom') # 底部添加标签

# part 7  画出车类型
carbody_labels = ['convertible',  'hardtop',  'hatchback' , 'sedan',  'wagon']
x_ticket = [i for i in range(len(carbody_labels))]
carbody_data = features_all[carbody_labels]
ylim_max = np.max(np.max(carbody_data)) # 获取Y轴最大值
for ind,each_data in enumerate(carbody_data.values):
    ax = fig.add_subplot(line_index, col_index, ax_ids[:,5][ind])
    ax.bar(x=x_ticket,height=each_data) # 画出柱形图
    ax.set_ylim((0, ylim_max*1.2))  # 将图形高度提升
    ax.set_xticks([])
    ax.set_yticks([])    
    if ind == 0:# 设置总标题
        ax.set_title(titles[5])
    # 设置每个柱形图的数值标签和x轴label
    carbody_values = ['{:.1%}'.format(i) for i in each_data]
    for i in range(len(x_ticket)):
        ax.text(x_ticket[i],each_data[i]+0.1,s=carbody_values[i],ha='center',va='top',fontsize=8)
        ax.text(x_ticket[i],-0.3,s=carbody_labels[i],ha='center',va='bottom',fontsize=8,rotation=-45)

# part 8  画出驱动方式
drivewheel_labels = ['4wd','fwd','rwd']
x_ticket = [i for i in range(len(drivewheel_labels))]
drivewheel_data = features_all[drivewheel_labels]
ylim_max = np.max(np.max(drivewheel_data)) # 获取Y轴最大值
for ind,each_data in enumerate(drivewheel_data.values):
    ax = fig.add_subplot(line_index, col_index, ax_ids[:,6][ind])
    ax.bar(x=x_ticket,height=each_data) # 画出柱形图
    ax.set_ylim((0, ylim_max*1.2))  # 将图形高度提升
    ax.set_xticks([])
    ax.set_yticks([])    
    if ind == 0:# 设置总标题
        ax.set_title(titles[6])
    # 设置每个柱形图的数值标签和x轴label
    drivewheel_values = ['{:.1%}'.format(i) for i in each_data]
    for i in range(len(x_ticket)):
        ax.text(x_ticket[i],each_data[i]+0.1,s=drivewheel_values[i],ha='center',va='top') # 图形添加数值
        ax.text(x_ticket[i],-0.2,s=drivewheel_labels[i],ha='center',va='bottom') # 底部添加标签
plt.tight_layout(pad=0.8) #设置默认的间距

天池数据分析达人赛3:汽车产品聚类(含代码)_第11张图片

每个类别下的特征一目了然。

六、竞品分析思路

对于汽车竞品,首先要确定类型和价格,只有同价位、同类型的汽车才能算竞品,其次才是品牌、外观、配置、油耗等因素,如果要从竞品角度出发,可以将类别数设置多一些,将类别分得更加细一些,同时对竞品车辆价位进行分箱筛选,再将筛选出的车辆进行分类统计,看看属于哪个类别,这样得出的竞品才可能更具代表性,再通过可视化的方式将竞品的数据展示出来,更加方便解读。下面简单做一下尝试。

import pandas as pd
df=pd.read_csv("/merge_data.csv")
# 分箱,由于目标品牌vokswagen rabbit价格是7775,所以区间选择7500-8000
m_bins = [0,7500,8000,310000]
df['m_score'] = pd.cut(df['price'], m_bins, labels=[i+1 for i in range(len(m_bins)-1)]) 
# 筛选出vokswagen rabbit所在区间数据
df_car=df[df['m_score']==2]
# 分组聚合
df_car.groupby(by=['clusters','CarName']).size()

天池数据分析达人赛3:汽车产品聚类(含代码)_第12张图片

这样就得出每个类别下的竞品,再结合聚类特征就可以看出目标品牌的优劣。


总结

1. 聚类算法是一种无监督类算法,对凸簇数据区分比较好,适用于非大数据(数据量100万以下),但对非凸数据比较难以处理,非凸数据用DBSCAN算法更好一些。

2. 聚类特征的选择是一个难点,特征太多,聚类效果不好;特征太少,没有聚类的必要。这个可以从业务需求出发,选择相应的特征进行建模。

3. 可视化方面可以选择power bi、tableau等第三方工具,比用Python要方便很多。

你可能感兴趣的:(数据挖掘,Python,python,数据分析,聚类,数据挖掘)