小文 | 公众号: 小文的数据之旅
五一假期过去了,不知道大家过得怎么样呢?假期的这几天,小文没有选择出去旅游(不想成为人海中的一员-。-),而是待在家里好好地阅读了我的新书--陈哲老师的《活用数据,驱动业务的数据分析实战》,可谓收获满满。当然说的不是spss的使用技巧,而是分析的思路。书里的案例用的spss,小文并不会,因此小文用python实现了一下。
这个案例来自于第六章--甲保险公司客户分类分析。文中使用了stp法进行分析,即客户细分,目标客户选取,目标客户定位。
1.客户细分
客户细分根据客户的分类维度进行细分,分类的维度包括5类,分别是自然属性因素、社会特征因素、行为特征因素、态度偏好因素和生活状态与个性因素。
前三者是表露在外的因素,即通过接触就可知道的因素,属于事前分类维度;后两者需要通过调研才能了解,是客户内在本质的区别,属于事后分类维度。因此,往往通过事后分类维度做客户分类,以保证分类的深入性,再通过事前分类维度进行描述与验证,以保证分类客户的差异性和可接触性。
import pandas as pd
import prince # 对应分析
from scipy.cluster import hierarchy # 层次聚类
from statsmodels.formula.api import ols
from statsmodels.stats.anova import anova_lm # 方差分析
import matplotlib.pyplot as plt
from factor_analyzer import FactorAnalyzer, calculate_kmo, calculate_bartlett_sphericity # 因子分析
%matplotlib inline
# 读取数据
X = pd.read_csv('./Desktop/第6章保险公司客户分类数据.csv', engine='python',
encoding='utf-8-sig') # 注意设置encoding参数,不然中文字体会乱码
x = X.iloc[:, -9:]
x.info()
RangeIndex: 712 entries, 0 to 711
Data columns (total 9 columns):
对自己的生活很满意 712 non-null int64
为享受而产生的浪费是必要的 712 non-null int64
买房子前要先有车 712 non-null int64
不惜金钱和时间装修房子 712 non-null int64
买衣服都买便宜的 712 non-null int64
休息时经常进行户外活动 712 non-null int64
尝试生活充满变化 712 non-null int64
喜欢独自享受安静的生活 712 non-null int64
下班后尽快回家 712 non-null int64
读取数据,将事后分类维度取出来并查看类型,发现9个维度都是数字类型并且部分维度之间似乎存在一定的相关性,这种相关性可能会造成重叠信息的扩大化,增加分类偏差,因此先对这9个维度进行因子分析。
1.1 因子分析
因子分析是指通过少数不相关的因子反映多个具有相关性的原始信息,起到降维和剔除相关性的作用。
因子分析的前提是具有一定的相关性,因此必须通过了kmo和bartlett球形度检验的数据才能进行因子分析。
#因子分析适用性检验
kmo = calculate_kmo(x) # kmo值要大于0.7
bartlett = calculate_bartlett_sphericity(x) # bartlett球形度检验p值要小于0.05
print('kmo:{},bartlett:{}'.format(kmo[1], bartlett[1]))
kmo:0.7164804529238993,bartlett:2.40899758533843e-221
通过了适用性检验后进行因子分析,格式为:FactorAnalyzer(rotation= None,n_factors=n,method='principal')
(1)rotation:旋转的方式,包括None:不旋转,'varimax':最大方差法,'promax':最优斜交旋转;
(2)n_factors:公因子的数量;
(3)method:因子分析的方法,包括'minres':最小残差因子法,'principal':主成分分析法;
# 使用主成分分析法,9个因子维度拟合
fa = FactorAnalyzer(rotation=None, n_factors=9, method='principal')
fa.fit(x)
fa_9_sd = fa.get_factor_variance()
fa_9_df = pd.DataFrame(
{'特征值': fa_9_sd[0], '方差贡献率': fa_9_sd[1], '方差累计贡献率': fa_9_sd[2]})
#各个因子的特征值以及方差贡献率
print(fa_9_df)
特征值 方差贡献率 方差累计贡献率
0 2.779391 0.308821 0.308821
1 1.302006 0.144667 0.453489
2 1.157716 0.128635 0.582124
3 1.034961 0.114996 0.697119
4 0.659054 0.073228 0.770348
5 0.597498 0.066389 0.836736
6 0.571816 0.063535 0.900271
7 0.486350 0.054039 0.954310
8 0.411207 0.045690 1.000000
查看9个公因子的特征值以及方差贡献率,一般选择方差累计贡献率大于0.8的公因子,而文中选择了特征值大于1的公因子,即方差累计贡献率为0.697的前4个公因子。接着根据4个公因子重新拟合。
#公因子数设为4个,重新拟合
fa_4 = FactorAnalyzer(rotation=None, n_factors=4, method='principal')
fa_4.fit(x)
#查看公因子提取度
print(fa_4.get_communalities())
[0.74797278 0.67110816 0.74233283 0.73808703 0.66567718 0.77689271
0.69607415 0.61523932 0.62069047]
查看公因子的提取度,发现当使用4个公因子时,4个公因子对9个维度的解释率都超过0.6,说明提取的4个公因子对原始维度有一定的解释力。
接着查看4个公因子的因子载荷,看看是否需要旋转。因子载荷即公因子对原始维度的解释力。
#查看因子载荷
print(fa_4.loadings_)
[[ 0.41758577 -0.04630919 0.69667789 0.29341146]
[ 0.5458075 -0.36656161 0.20321007 0.44445539]
[ 0.64142608 -0.2555359 -0.50646667 0.09538522]
[ 0.65217692 -0.36074267 -0.3699891 0.21383425]
[ 0.54104332 0.4901776 -0.28576184 -0.22586587]
[ 0.58866609 -0.09674514 0.23956478 -0.60300418]
[ 0.64268497 -0.19004547 0.23352467 -0.43861045]
[ 0.48559443 0.56693683 -0.10335096 0.21757431]
[ 0.42690002 0.60240529 0.18274222 0.20532898]]
以第一个维度为例,我们发现4个公因子对原始的第一个维度的解释程度分别为:0.418,-0.046,0.697,0.293,表明公因子1与公因子3之间存在一定的相关性,达不到因子分析的既定效果,因此需要进行旋转,使得各个公因子具有差异化的特征。
#使用最大方差法旋转因子
fa_4_rotate = FactorAnalyzer(
rotation='varimax', n_factors=4, method='principal')
fa_4_rotate.fit(x)
#查看z旋转后的因子载荷
print(fa_4_rotate.loadings_)
[[-0.06853212 0.15285685 0.20333033 0.8237522 ]
[ 0.4912233 0.00448056 0.05911173 0.6529116 ]
[ 0.83076037 0.1490026 0.16448475 -0.05397252]
[ 0.83463555 0.07159505 0.1317772 0.13776588]
[ 0.21485233 0.67063434 0.31903511 -0.26073327]
[ 0.08489552 0.09023972 0.87007017 0.06723208]
[ 0.20807596 0.06273611 0.78272561 0.1902192 ]
[ 0.15963168 0.76368161 -0.01467611 0.07957422]
[-0.06747767 0.74047386 0.03975419 0.25740106]]
还是以第一个维度为例,我们发现经过最大方差法旋转之后,4个公因子对原始的第一个维度的解释程度分别为:-0.069,0.153,0.203,0.824,即公因子4对第一个维度的解释力较大。旋转后4个公因子在原始维度上被明显的区别出来,即4个公因子具有差异性的特征。
# 以因子最大值的因子为因子类型
facotor_result = pd.DataFrame(
fa_4_rotate.fit_transform(x), columns=list('1234'))
facotor_result['因子类型'] = facotor_result.idxmax(axis=1)
#查看结果
print(facotor_result.head())
1 2 3 4 因子类型
0 -0.011259 1.616764 1.398204 0.938451 2
1 -0.179805 -0.855833 -0.061380 -2.978798 3
2 -1.105833 0.729183 -0.664757 -0.664573 2
3 1.524168 -0.264195 -0.187541 -1.982722 1
4 0.437858 -0.520359 -0.091027 -0.654643 1
#将因子类型合并到原数据中
result = pd.concat([X.iloc[:, :-9], facotor_result['因子类型']], axis=1)
1.2 聚类分析
经过因子分析之后,我们把所有的客户分成了具有差异性特征的4类客户(代表了9个事后分类维度),接着我们通过因子类型以及保费金额两个维度进行聚类分析。常用的聚类分析方法有kmeans、DBSCAN以及层次聚类,文中使用了层次聚类,因为层次聚类对数据的类型要求不高且事先不需要知道分为几类,缺点在于计算量大。
# 因子类型以及保费金额的量纲不一致,需进行标准化处理
result['因子类型'] = result['因子类型'].astype('int64')
result['Z因子类型'] = (result['因子类型']-result['因子类型'].mean())/result['因子类型'].std()
result['Z保费金额'] = (result['保费金额']-result['保费金额'].mean())/result['保费金额'].std()
result = result.set_index(result['问卷编号'])
#层次聚类分析
Z = hierarchy.linkage(result[['Z保费金额', 'Z因子类型']],
method='ward', metric='euclidean')
hierarchy.dendrogram(Z, labels=result.index)
# 看效果图,分为5类比较合适,即高度大概在13左右
label = hierarchy.cut_tree(Z, height=13)
label = label.reshape(label.size,)
result['细分类型'] = list(label)
通过层次聚类分析,我们将所有客户分为了5类,那么分类的效果如何呢?接下来我们通过方法分析检验分类的效果。我们发现无论是保费金额还是因子类型,通过层次聚类分组后,p值都小于0.05,即组间存在显著性差异,聚类效果良好。
#使用方差分析检验 聚类效果
a = anova_lm(ols('保费金额~C(细分类型)', data=result[['保费金额', '细分类型']]).fit())[:1]
b = anova_lm(ols('因子类型~C(细分类型)', data=result[['因子类型', '细分类型']]).fit())[:1]
f_oneway_result = pd.concat([a.iloc[:, 3:], b.iloc[:, 3:]])
f_oneway_result['列名'] = ['保费金额', '因子类型']
print(f_oneway_result)
F PR(>F) 列名
C(细分类型) 306.108565 1.157673e-152 保费金额
C(细分类型) 742.643495 1.999808e-251 因子类型
通过单因素方差分析,我们知道细分类型各组间存在显著性差异,那么这种差异怎么表现出来呢?对于数值型数据我们用均值进行比较,对于类别型数据我们用占比比较,然后根据保费金额以及因子类别对细分类型命名。
#保费金额使用均值比较,因子类别使用占比比较
nor = pd.crosstab(result['细分类型'], result['因子类型'],
normalize=0) # normalize = 0按行求占比
mean = result.groupby('细分类型')['保费金额'].mean()
result_xf = pd.concat([nor, mean], axis=1)
print(result_xf)
1 2 3 4 保费金额
细分类型
0 0.000000 0.603774 0.396226 0.000000 1481.796226
1 0.657407 0.342593 0.000000 0.000000 2098.268056
2 0.000000 0.000000 0.578947 0.421053 2779.996241
3 0.000000 0.000000 0.000000 1.000000 1708.326829
4 0.530864 0.259259 0.197531 0.012346 3780.096296
#各细分类型命名
result['细分类型'] = result['细分类型'].map(
{0: '低端居家型客户', 1: '中端享受型客户', 2: '中端外向型客户', 3: '中端自信型客户', 4: '高端享受型客户'})
2. 目标客户选取
细分客户之后,要选取目标客户。选取目标客户主要从两个维度度量,分别是客户吸引力和企业竞争力。客户吸引力包括两个方面,一是客户规模,二是保费金额,根据其公司需要,按权重6:4进行计算,得出客户吸引力。企业吸引力主要体现在各个保险公司拥有各个细分类型客户的数量,即市场占有率。
#统计客户吸引力和企业竞争力
result_final = pd.DataFrame()
result_final['客户数量'] = result.groupby('细分类型')['问卷编号'].count()
result_final['保费金额'] = result.groupby('细分类型')['保费金额'].mean()
result_final['客户规模'] = result_final['客户数量']/result_final['客户数量'].sum()
result_final['客户规模标准化'] = (
result_final['客户规模']-result_final['客户规模'].mean())/result_final['客户规模'].std()
result_final['保费金额标准化'] = (
result_final['保费金额']-result_final['保费金额'].mean())/result_final['保费金额'].std()
result_final['客户吸引力'] = 0.6*result_final['客户规模标准化']+0.4*result_final['保费金额标准化']
result2 = pd.crosstab(result['细分类型'], result['保险公司的选择'], normalize=0)
result2.columns = ['甲', '乙', '丙', '丁']
result_final['企业竞争力'] = result2['甲']
print(result_final)
客户数量 保费金额 客户规模 客户规模标准化 保费金额标准化 客户吸引力 企业竞争力
细分类型
中端享受型客户 216 2098.268056 0.303371 1.477388 -0.291968 0.769645 0.240741
中端外向型客户 133 2779.996241 0.186798 -0.188688 0.441347 0.063326 0.458647
中端自信型客户 123 1708.326829 0.172753 -0.389420 -0.711415 -0.518218 0.162602
低端居家型客户 159 1481.796226 0.223315 0.333215 -0.955087 -0.182106 0.119497
高端享受型客户 81 3780.096296 0.113764 -1.232494 1.517124 -0.132647 0.320988
#矩阵分析图
plt.rcParams['font.sans-serif'] = 'Simhei'
plt.rcParams['axes.unicode_minus'] = False
plt.subplot(1, 1, 1)
plt.scatter(result_final['企业竞争力'],
result_final['客户吸引力'], s=200, c='r', marker='o')
plt.hlines(y=0, xmin=0, xmax=0.5)
plt.vlines(x=0.25, ymin=-1.2, ymax=1.2)
plt.xlabel('企业竞争力')
plt.ylabel('客户吸引力')
for a, b, c in zip(result_final['企业竞争力'], result_final['客户吸引力'], result_final.index):
plt.text(a, b, c, ha='center', va='bottom', fontsize=10)
由上图可知,中端外向型客户是甲公司的首选客户,其次是中端享受型客户和高端享受型客户,而低端居家型客户和中端自信型客户在资源不足的情况下暂可放弃。
3. 目标客户定位
目标客户定位包括两部分,一是目标客户长什么样,也就是用户画像,通过事前分类维度描述目标客户画像;二是目标客户需求是什么,针对需求进行精准营销。分析过程依然是先进行方差分析,通过方差分析的维度用均值或者占比表现差异性,最后通过对应分析展示效果。
3.1 目标客户画像
事前分类维度一共由6个,分别是性别、年龄、城市、家庭月收入、汽车价格、学历以及职业。通过方差分析发现,学历以及职业在细分类型组间没有显著性差异,故忽略这两个维度继续分析。
#事前分类维度方差分析
result['职业'] = result['职业'].replace(' ', '6').astype('int64')
target_sd = []
for i in ['性别', '年龄', '城市', '家庭月收入', '汽车价格', '学历', '职业']:
formula = '(' + str(i) + '~' + 'C(' + '细分类型)' + ')'
a = anova_lm(ols(formula, data=result[[i, '细分类型']]).fit())[:1]
target_sd.append(pd.DataFrame(
{'c': str(i), 'F': a['F'], 'PR(>F)': a['PR(>F)']}))
target_result = pd.concat(target_sd)
target_result = target_result[target_result['PR(>F)'] < 0.05]
#具有显著性差异的维度结果展示
print(target_result)
c F PR(>F)
C(细分类型) 性别 57.940193 2.614665e-42
C(细分类型) 年龄 553.274636 4.801252e-216
C(细分类型) 城市 3629.629395 0.000000e+00
C(细分类型) 家庭月收入 268.460859 3.193752e-140
C(细分类型) 汽车价格 901.193079 7.780527e-276
#具有显著性差异的维度命名
Y = result[['性别', '年龄', '城市', '家庭月收入', '汽车价格', '细分类型']]
Y['性别'] = Y['性别'].map({1: '男', 2: '女'})
Y['年龄'] = Y['年龄'].map({1: '18-30岁', 2: '31-40岁', 3: '41岁以上'})
Y['城市'] = Y['城市'].map(
{1: '北京', 2: '上海', 3: '武汉', 4: '沈阳', 5: '广州', 6: '西安', 7: '成都'})
Y['家庭月收入'] = Y['家庭月收入'].map(
{1: '小于7000元', 2: '7000-10000元', 3: '10000-15000元', 4: '15000-20000元', 5: '20000元以上'})
Y['汽车价格'] = Y['汽车价格'].map(
{1: '10万元以下', 2: '10-20万元', 3: '20-30万元', 4: '30万元以上'})
# 多因子对应分析
mca = prince.MCA(n_components=2, n_iter=10, random_state=1)
mca = mca.fit(Y)
ax = mca.plot_coordinates(
X=Y,
ax=None,
figsize=(10, 6),
show_row_points=False,
show_column_points=True,
column_points_size=100,
show_column_labels=True,
legend_n_cols=1
)
从对应分析效果图可知,甲公司首选目标客户中端外向型客户,主要是在分布在北京,武汉的分布比例也比其他细分类型要高,年龄集中在31-40之间,性别为男,家庭月收入在15000到20000元之间,汽车价格在20-30万元间。
3.2 目标客户需求分析
依然是选定各细分类型客户,然后对各个维度进行方差分析,通过方差分析检验后的维度用均值或者占比进行比较;而未通过方差分析的维度,则直接用中端外向型客户进行各个维度的比较,数值型用均值,类型型用占比。
#方差分析
result['从什么渠道收集信息'] = result['从什么渠道收集信息'].replace(' ', '4').astype('int64')
target_demand_sd = []
for i in result.iloc[:, 9:21].columns.values:
formula = '(' + str(i) + '~' + 'C(' + '细分类型)' + ')'
a = anova_lm(ols(formula, data=result[[i, '细分类型']]).fit())[:1]
target_demand_sd.append(pd.DataFrame(
{'c': str(i), 'F': a['F'], 'PR(>F)': a['PR(>F)']}))
target_demand = pd.concat(target_demand_sd)
target_demand1 = target_demand[target_demand['PR(>F)'] < 0.05]
target_demand2 = target_demand[target_demand['PR(>F)'] > 0.05]
print('有显著差异的维度:{}'.format(target_demand1['c'].values))
print('没有显著差异的维度:{}'.format(target_demand2['c'].values))
有显著差异的维度:['保险公司的选择' '保费金额' '一站式服务考虑程度' '网上投保考虑程度' '产品个性化考虑程度' '选择保险公司的考虑因素' '满意度']
没有显著差异的维度:['决策时间' '是否收集信息' '从什么渠道收集信息' '投保渠道' '索赔经历']
#保险公司的选择
target_demand1_result1 = pd.crosstab(
result['细分类型'], result['保险公司的选择'], normalize=0)
target_demand1_result1.columns = list('甲乙丙丁')
print(target_demand1_result1)
甲 乙 丙 丁
细分类型
中端享受型客户 0.240741 0.254630 0.259259 0.245370
中端外向型客户 0.458647 0.142857 0.187970 0.210526
中端自信型客户 0.162602 0.333333 0.268293 0.235772
低端居家型客户 0.119497 0.283019 0.358491 0.238994
高端享受型客户 0.320988 0.209877 0.098765 0.370370
#选择保险公司的考虑因素分析
target_demand1_result2 = pd.crosstab(
result['细分类型'], result['选择保险公司的考虑因素'], normalize=0)
target_demand1_result2.columns = [
'服务态度好', '公司知名度高', '产品价格便宜', '服务网点多', '亲朋推荐', '信任销售人员', '理赔服务效率高']
print(target_demand1_result2)
服务态度好 公司知名度高 产品价格便宜 服务网点多 亲朋推荐 信任销售人员 理赔服务效率高
细分类型
中端享受型客户 0.013889 0.041667 0.125000 0.231481 0.273148 0.254630 0.060185
中端外向型客户 0.052632 0.022556 0.112782 0.248120 0.323308 0.180451 0.060150
中端自信型客户 0.081301 0.024390 0.146341 0.260163 0.178862 0.252033 0.056911
低端居家型客户 0.056604 0.044025 0.163522 0.308176 0.238994 0.150943 0.037736
高端享受型客户 0.024691 0.037037 0.061728 0.209877 0.296296 0.246914 0.123457
#满意度分析
target_demand1_result3 = pd.crosstab(
result['细分类型'], result['满意度'], normalize=0)
target_demand1_result3.columns = ['满意', '还可以', '不满意']
print(target_demand1_result3)
满意 还可以 不满意
细分类型
中端享受型客户 0.125000 0.509259 0.365741
中端外向型客户 0.015038 0.360902 0.624060
中端自信型客户 0.162602 0.626016 0.211382
低端居家型客户 0.364780 0.591195 0.044025
高端享受型客户 0.000000 0.024691 0.975309
#剩下的维度均值比较
target_demand1_result4 = pd.DataFrame()
target_demand1_result4['保费金额'] = result.groupby('细分类型')['保费金额'].mean()
target_demand1_result4['一站式服务考虑程度'] = result.groupby('细分类型')[
'一站式服务考虑程度'].mean()
target_demand1_result4['网上投保考虑程度'] = result.groupby('细分类型')['网上投保考虑程度'].mean()
target_demand1_result4['产品个性化考虑程度'] = result.groupby('细分类型')[
'产品个性化考虑程度'].mean()
print(target_demand1_result4)
保费金额 一站式服务考虑程度 网上投保考虑程度 产品个性化考虑程度
细分类型
中端享受型客户 2098.268056 4.712963 4.717593 5.356481
中端外向型客户 2779.996241 4.548872 4.729323 5.436090
中端自信型客户 1708.326829 4.121951 4.373984 4.926829
低端居家型客户 1481.796226 4.459119 4.572327 5.226415
高端享受型客户 3780.096296 4.950617 5.000000 5.382716
由上面的分析可知:
甲公司的目标客户中端外向型客户在选择保险公司考虑的因素中,比较关注服务网点多、亲朋推荐和信任销售人员,其中尤其关注亲朋的推荐。
在满意度分析中,发现中端外向型客户对目前购买的车险并不满意,满意度只有1.5%,不满意的具体原因还需进一步调研。
中端外向型客户车险平均保费在2780元,比其他细分客户更注重产品个性化。
最后对细分类型间没有显著性差异的维度接着进行分析,都是类别数据,直接value_counts()就可以了,这里就不一一展示了。
#不具有显著性差异的维度分析
target_demand2_result = result[result['细分类型'] == '中端外向型客户'][[
'决策时间', '是否收集信息', '从什么渠道收集信息', '投保渠道', '索赔经历']]
target_demand2_result['决策时间'].value_counts()
target_demand2_result['是否收集信息'].value_counts()
target_demand2_result['从什么渠道收集信息'].value_counts()
target_demand2_result['投保渠道'].value_counts()
target_demand2_result['索赔经历'].value_counts()
今天的分享到此结束,希望各位看官有所收获!!