本案例利用K-Means聚类算法来进行青少年市场细分,使用R语言编程对数据进行清洗,然后使用R中的stats包训练模型,并对聚类结果进行分析。

市场细分(market segmentation)是指营销者通过市场调研,依据消费者的需要和欲望、购买行为和购买习惯等方面的差异,把某一产品的市场整体划分为若干消费者群的市场分类过程。 每一个消费者群就是一个细分市场,每一个细分市场都是具有类似需求倾向的消费者构成的群体。 市场细分能够对企业的生产、营销起到极其重要的作用:

  • 有利于选择目标市场和制定市场营销策略

  • 有利于发掘市场机会,开拓新市场

  • 有利于集中人力、物力投入目标市场

  • 有利于企业提高经济效益

随着Facebook, Twitter等社交网络平台的流行,越来越多的青少年用户会在这些平台发布消息。 这些文本数据能够反映青少年的行为、兴趣爱好,结合社交网络平台上用户的性别、年龄、好友数等信息,对于挖掘青少年细分市场具有很大的价值。

细分市场都是具有类似需求倾向的消费者,而聚类算法很适合用来完成这一任务的。 本案例中,我们将使用一份从社交网络平台抽取的描述青少年基本信息和兴趣爱好的数据集,利用K-Means聚类算法来进行青少年市场细分。

1 数据源

我们使用一份包含30000个样本的美国高中生社交网络信息数据集。 数据均匀采样于2006年到2009年,对应的高中生年级有高中一年级、二年级、三年级和四年级。 每个样本包含40个变量,其中 gradyeargenderagefriends四个变量代表高中生的毕业年份、性别、年龄和好友数等基本信息。 其余36个变量代表36个词语,这36个词语代表高中生的5大兴趣类:课外活动、时尚、宗教、浪漫和反社会行为。 每个词语变量的取值代表对应词语在高中生的社交网络服务平台发布的消息中出现的频次。 36个词语的列表如下:

  • basketball (篮球)

  • football (足球)

  • soccer (英式足球)

  • softball (垒球)

  • volleyball (排球)

  • swimming (游泳)

  • cheerleading (带领拉拉队)

  • baseball (棒球)

  • tennis (网球)

  • sports (运动)

  • cute (可爱的)

  • sex (性)

  • sexy (性感)

  • hot (火辣)

  • kissed (吻)

  • dance (跳舞)

  • band (乐队)

  • marching (游行)

  • music (音乐)

  • rock (摇滚)

  • god (上帝)

  • church (教堂)

  • jesus (耶稣)

  • bible (圣经)

  • hair (头发)

  • dress (服装)

  • blonde (金发女郎)

  • mall (商业街)

  • shopping (购物)

  • clothes (衣服)

  • hollister (hollister品牌,美国时尚休闲大牌)

  • abercrombie (abercrombie品牌,美国青少年最青睐的品牌)

  • die (死亡)

  • deat (死亡)

  • drunk (醉酒)

  • drugs (毒品)

2 数据探索和预处理

首先,使用R中的read.csv()函数将数据加载到数据框中:

In [182]:
teenager_sns <- read.csv("./input/teenager_sns.csv")
head(teenager_sns, n = 10)
gradyeargenderagefriendsbasketballfootballsoccersoftballvolleyballswimmingblondemallshoppingclotheshollisterabercrombiediedeathdrunkdrugs
2006 M 18.980 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
2006 F 18.801 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0
2006 M 18.335 69 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0
2006 F 18.875 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
2006 NA 18.995 10 0 0 0 0 0 0 0 0 2 0 0 0 0 0 1 1
2006 F NA142 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0
2006 F 18.930 72 0 0 0 0 0 0 0 2 0 0 2 0 0 0 0 0
2006 M 18.322 17 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
2006 F 19.055 52 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
2006 F 18.708 39 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0

通过观察,发现genderage变量存在缺失值(missing value)。 很多机器学习模型不能直接处理带有缺失值的数据,例如我们将要使用的 K-Means聚类算法。 因此在正式构建模型之前,需要对缺失值进行处理:删除或者以某种方法进行填补。 在对缺失值进行处理之前,我们先分别统计genderage这两个变量存在缺失值的样本数量。 对于gender变量,我们使用R中的table()函数来完成这一分析,注意table()函数默认不对缺失值进行统计,需要将useNA参数设置成 “ifany”。 对于连续型变量age,使用summary()函数。

In [183]:
table(teenager_sns$gender, useNA = "ifany")
summary(teenager_sns$age)
    F     M  <NA> 
22054  5222  2724 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
  3.086  16.312  17.287  17.994  18.259 106.927    5086 

可见,在30000个样本中,有2724个样本(约9%)缺少性别数据,5086个样本(约17%)缺少年龄数据。 进一步观察 age 变量的描述统计信息发现,年龄的最小值为3.086,最大值为106.9。 因为我们的样本是青少年样本,所以该最小值和最大值似乎不可信,因为现实中不太可能会有一个3岁或者106岁的人就读高中。 这种异常数据往往会影响最终的建模分析结果,因此需要进行异常值处理。 高中生的合理年龄区间为13~20岁,因此对于我们的数据集,如果年龄在13~20岁之外,我们将其标记为空值NA。 使用R中的 ifelse() 函数来完成这一处理:

In [184]:
teenager_sns$age <- ifelse(teenager_sns$age >= 13 & teenager_sns$age < 20, teenager_sns$age, NA)
summary(teenager_sns$age)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
  13.03   16.30   17.27   17.25   18.22   20.00    5523 

现在,对于异常值的处理完毕,但是这样引入了更多的缺失值,下一步我们需要对缺失值进行处理。

2.1 通过虚拟编码来处理分类变量的缺失值

对于样本中的缺失值,一个最简单的方法是删除带有缺失值的样本。 然而直接删除缺失值会使我们的数据变少,特别是我们的数据中一共有40个变量,只有两个变量存在缺失值。 缺失值在数据中整体不多,但是如果直接删除会导致我们失去很多的可用数据。

对于gender这种分类变量,如果缺失值的样本跟其他样本的差别明显,我们可以为gender变量增加一个单独的分类 "unkown"。 由于K-Means聚类算法需要计算样本之间的距离,因此我们还需要对分类变量进行虚拟编码(也称为OneHot编码)。 虚拟编码将一个有K个取值的分类变量转换成K个二元变量。 我们的gender变量现在有“M”,“F”和“unkown”三种取值,我们可以将其转换成三个变量:gender_Mgender_Fgender_unkown。 这三个变量取值为0和1,分别代表某一个高中生是否是某一性别类型。 对于一个样本,在这三个变量下同时只能一个变量取值为1,其他变量取值为0。

In [185]:
teenager_sns$gender_M <- ifelse(teenager_sns$gender == "M" & !is.na(teenager_sns$gender),1,0)
teenager_sns$gender_F <- ifelse(teenager_sns$gender == "F" & !is.na(teenager_sns$gender),1,0)
teenager_sns$gender_unkown <- ifelse(is.na(teenager_sns$gender),1,0)

2.2 使用填补方法来处理数值变量的缺失值

对于age这种数值变量的缺失值,我们可以使用一个特殊的值对缺失值进行填补(imputation),常用的填补值包括给定值、均值、中位数等。 在案例中,我们使用最具有代表性的均值填补法。 所以,我们需要先计算 age 变量的均值。 均值的计算可以使用R中的 mean() 函数,但是需要注意默认情况下 mean() 是无法对包含缺失值的数据计算均值的。 我们需要给 mean() 函数传入一个额外的参数 na.rm,将其值设置为 TRUE

In [186]:
age_mean <- mean(teenager_sns$age, na.rm = TRUE)
age_mean
17.2524288515749

现在,我们使用均值对缺失值进行填补:

In [187]:
teenager_sns$age_avg_imputated <- ifelse(is.na(teenager_sns$age), age_mean, teenager_sns$age)
head(teenager_sns , n = 10)
gradyeargenderagefriendsbasketballfootballsoccersoftballvolleyballswimminghollisterabercrombiediedeathdrunkdrugsgender_Mgender_Fgender_unkownage_avg_imputated
2006 M 18.980 7 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 18.98000
2006 F 18.801 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 18.80100
2006 M 18.335 69 0 1 0 0 0 0 0 0 0 1 0 0 1 0 0 18.33500
2006 F 18.875 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 18.87500
2006 NA 18.995 10 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 18.99500
2006 F NA 142 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 17.25243
2006 F 18.930 72 0 0 0 0 0 0 2 0 0 0 0 0 0 1 0 18.93000
2006 M 18.322 17 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 18.32200
2006 F 19.055 52 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 19.05500
2006 F 18.708 39 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 18.70800

观察上表中的第6行的age变量和age_avg_imputated变量, 发现该样本的age缺失值已经被正确填补为均值17.2523。

2.3 数据标准化

因为K-Means聚类算法需要计算样本的距离,在构建模型之前,我们需要进行数据标准化。 常用的标准化方法有 min-max 标准化和 z-score 标准化等。 在本例中,我们直接采用 z-score 标准化方法。

In [188]:
filtered_columns = c("gradyear","friends","basketball",
                     "football","soccer","softball","volleyball","swimming",
                     "cheerleading","baseball","tennis","sports","cute","sex",
                     "sexy","hot","kissed","dance","band","marching","music",
                     "rock","god","church",
                      "jesus","bible","hair","dress","blonde","mall","shopping","clothes",
                     "hollister","abercrombie","die","death","drunk","drugs",
                     "gender_M","gender_F","gender_unkown","age_avg_imputated")

teenager_sns_zscore <- as.data.frame(lapply(teenager_sns[filtered_columns], scale))

head(teenager_sns_zscore)
gradyearfriendsbasketballfootballsoccersoftballvolleyballswimmingcheerleadingbaseballhollisterabercrombiediedeathdrunkdrugsgender_Mgender_Fgender_unkownage_avg_imputated
-1.341618 -0.6345171-0.3322117-0.3576914-0.2428701-0.2179242-0.2236659-0.2599662-0.2073236-0.2011273-0.2014729-0.1830287-0.2947883-0.2615258-0.220399 -0.1749047 2.1782486-1.6659508-0.31601371.652385
-1.341618 -0.8261358-0.3322117 1.0600312-0.2428701-0.2179242-0.2236659-0.2599662-0.2073236-0.2011273-0.2014729-0.1830287-0.2947883-0.2615258-0.220399 -0.1749047-0.4590691 0.6002378-0.31601371.481176
-1.341618 1.0626773-0.3322117 1.0600312-0.2428701-0.2179242-0.2236659-0.2599662-0.2073236-0.2011273-0.2014729-0.1830287-0.2947883 2.0278743-0.220399 -0.1749047 2.1782486-1.6659508-0.31601371.035456
-1.341618 -0.8261358-0.3322117-0.3576914-0.2428701-0.2179242-0.2236659-0.2599662-0.2073236-0.2011273-0.2014729-0.1830287-0.2947883-0.2615258-0.220399 -0.1749047-0.4590691 0.6002378-0.31601371.551955
-1.341618 -0.5523948-0.3322117-0.3576914-0.2428701-0.2179242-0.2236659-0.2599662-0.2073236-0.2011273-0.2014729-0.1830287-0.2947883-0.2615258 2.285084 2.7192710-0.4590691-1.6659508 3.16431381.666732
-1.341618 3.0609868-0.3322117-0.3576914-0.2428701-0.2179242-0.2236659-0.2599662-0.2073236-0.2011273-0.2014729-0.1830287-0.2947883-0.2615258 2.285084 -0.1749047-0.4590691 0.6002378-0.31601370.000000

3 模型训练

为了将我们的青少年数据进行市场细分,我们使用stats包中的 kmeans() 函数。 kmeans() 函数有两个主要的参数:

  • x: 需要聚类的数据集,格式为矩阵或数据框
  • centers: 聚类数目

我们将细分市场的个数centers设置为5。

In [189]:
set.seed(4)
teenager_clusters <- kmeans(teenager_sns_zscore, centers = 5)

4 聚类结果分析

聚类结果的定量性能评价指标有互信息同质性完备性等,但是这些指标并不能指示聚类结果是否达到我们的预期分析目标。 在本案例中,我们的分析目标是确定具有相似特质和兴趣爱好的青少年的分类,以达到向不同类的青少年做区别营销的目的。 因此,很大程度上,我们需要的不是定量的评价指标结果,而是定性地对聚类结果进行分析。 首先,我们来观察我们K-Means聚类出来的每一个类中样本的数目。

In [190]:
teenager_clusters$size
  1. 1690
  2. 5086
  3. 11506
  4. 1229
  5. 10489

在我们的聚类的5个类中,最大的类中有11482名青少年,最小的类中有1216名青少年。 需要注意的是,因为K-Means聚类会随机选取初始的聚类中心,因此每次运行的结果可能会不同。 为了更好地理解每一个类所代表的青少年群体的特点,我们观察每一个类的聚类中心(cluster center)。 聚类中心结果保存在 teenager_clusterscenters 属性。

In [191]:
centers <- teenager_clusters$centers
centers
gradyearfriendsbasketballfootballsoccersoftballvolleyballswimmingcheerleadingbaseballhollisterabercrombiediedeathdrunkdrugsgender_Mgender_Fgender_unkownage_avg_imputated
1 0.1391896 0.12008487 0.41707722 0.48623103 0.22096755 0.02925515 0.05187657 0.40043334 0.34719503 0.3160455 0.8086673 0.84353780 1.16717233 0.73551471 1.17466569 1.70031566-0.2499563 0.2918216 -0.1183147 -0.1293485
2-0.1150109 -0.15295141 0.04968371 0.27813738-0.01800452-0.20729196-0.17942284-0.10708036-0.19165024 0.3249706 -0.1158584 -0.11128960-0.06086746-0.06256496-0.06916357-0.08840946 2.1751374 -1.6659508 -0.3119080 0.1766908
3-0.7667724 -0.09506694-0.17107064-0.15598692-0.08955743-0.13191851-0.13117868-0.06411819-0.06639557-0.1248318 -0.1132533 -0.12271590-0.08395232-0.05499032-0.06143821-0.11051141-0.4590691 0.1452669 0.3827136 0.6770968
4 0.2696336 0.32618249 1.17943795 0.34828599 0.09777580 2.91924568 2.64986288 0.16025409 0.09483661 0.2028020 0.1762911 0.14877791-0.07329862 0.04025029-0.05119224-0.07128896-0.4461937 0.4822264 -0.1517672 -0.2157701
5 0.8428657 0.12088182-0.04182880-0.08290544 0.05991202-0.10153998-0.08794608 0.03896177 0.09870976-0.0953233 0.0294632 0.03523321-0.05786156-0.03256397-0.08233347-0.10150868-0.4585662 0.5449280 -0.2317347 -0.7823000

因为数据已经使用z-score方法进行标准化,我们可以直接通过观察聚类中心在每一个变量上的取值情况来分析每一个聚类中心的含义。

如果聚类中心在某一个变量取值大于0,代表该聚类所代表的群体在该变量取值大于群体平均水平。

首先对上述聚类结果数据框进行转置,然后对每一个聚类中心的变量取值从大到小进行排序。

通过观察每个聚类前10个变量来分析聚类所代表的群体:

In [192]:
centers_t <- t(centers)
sort(centers_t[,1],decreasing = TRUE)[1:10]
hair
2.20143133559718
kissed
1.97215316332346
drugs
1.70031565682762
clothes
1.47858272817764
sex
1.4215431518536
drunk
1.17466569329569
die
1.16717232577849
music
1.10799104365085
rock
1.10048490732763
cute
0.943389958846439

第一个聚类所代表的青少年群体特点为:喜欢时尚、懂浪漫、爱好音乐、喜酗酒,似乎有些叛逆。

In [193]:
sort(centers_t[,2],decreasing = TRUE)[1:10]
gender_M
2.17513737574073
baseball
0.324970603399437
football
0.278137377087333
age_avg_imputated
0.17669078961489
sports
0.110241620004467
basketball
0.0496837084542808
tennis
0.0288379968326787
band
0.0230634571075844
marching
0.00993628598335256
bible
0.00336143605905002

第二个聚类所代表的青少年群体的特点为:男生居多,爱好体育运动。

In [194]:
sort(centers_t[,3],decreasing = TRUE)[1:10]
age_avg_imputated
0.677096783551148
gender_unkown
0.382713590805234
gender_F
0.145266878004706
dress
0.038717689738704
marching
-0.00609808729564561
bible
-0.0137860747092055
jesus
-0.0239706478794952
tennis
-0.025362055470485
blonde
-0.0263700694779259
god
-0.0296174127706951

第三个聚类所代表的青少年群体为宗教群体。

In [195]:
sort(centers_t[,4],decreasing = TRUE)[1:10]
softball
2.91924567571441
volleyball
2.64986288021221
basketball
1.17943794965039
sports
0.654591700590269
gender_F
0.482226372271981
shopping
0.404552017820269
football
0.348285986150887
friends
0.326182492960431
gradyear
0.269633646160732
church
0.220486431522546

第四个聚类所代表的青少年群体为爱好运动、爱好购物,其中以女生居多。

In [196]:
sort(centers_t[,5],decreasing = TRUE)[1:10]
gradyear
0.842865689654367
gender_F
0.544928041060839
shopping
0.130082340102719
friends
0.120881820573767
cheerleading
0.0987097611883275
hot
0.0935367370679203
dance
0.0795807626376446
mall
0.0783086655388826
soccer
0.0599120224175274
cute
0.0474588026698043

第五个聚类所代表的青少年群体特点为:高年级,女生居多,喜欢购物和时尚,好友较多。