機械学習でマーケティング セグメンテーションを計算してみる【k-means, PCA】

hanako
市場を整理しろと言われたけどやり方がわからない。。
moku
k-means法を使えば簡単にグループ分けできるよ。
マーケティング担当だと
  • 市場にはどんなお客様がいるんですか?
  • ターゲットはどこですか?
  • 何故そこを狙うの?
というのはよく問われることだと思います。

今はちょっと古い感じもありますが、マーケティングの基本とされる
  • Segmentation : 対象を大枠分類する
  • Targeting   : 目標を定める
  • Positioning  : 立ち位置を明確化する
のSegmentationとTargetingの話ですね。
市場参入のステージだと特に重要な考え方だと思います。

この記事ではデータサイエンス、機械学習を実際のマーケティング現場で使っていくイメージをシナリオベースでお話しさせて頂きます。

今回は
  • k-means : グループ分けの計算をする
  • pca : データの次元を削減し、2次元平面でデータを表現する
  • Radar chart : レーダーチャート (機械学習とは関係ないですが描くのがちょっと面倒)
を使ってみようと思います。

    目次
  • 1: セグメンテーションとは細分化のこと
  • 2: セグメンテーションはよく失敗する
  • 3: 今回のシナリオ:足の形ってどんなグループに分けられる?
    • 3.1: 分析の準備
      • 3.1.1: データ入手
      • 3.1.2: データの標準化
    • 3.2: k-meansを使ったクラス分け計算
      • 3.2.1: クラスタ数の決定
      • 3.2.2: 計算
    • 3.3: 説明用可視化
      • 3.3.1: データの可視化でグループのイメージをつかんでもらう
      • 3.3.2: レーダーチャートでグループの特徴を比較
  • 4最後に

セグメンテーションとは細分化のこと

そもそもSegmentaitonとはなんでしょうか?

マーケットセグメンテーション (英: market segmentation) とは、市場細分化の意味で、特定商品(サービスを含む)における市場を異質とみなし、顧客市場を細分化することによって特定カテゴリに対して集中的にアプローチすることを目的に行われる。

出典: フリー百科事典『ウィキペディア(Wikipedia)
要するに”若者/高齢者”とか”文系/理系”といったように特徴によってグループ分けすることですね。
全ての人にピッタリなモノやサービスを考えるのは大変なので”高齢者向けサービス”など、ある程度ターゲットを絞りましょうということです。

セグメンテーションはよく失敗する

このセグメンテーション、マーケティング担当としてはもちろんやるのですがよく失敗します。
パターンは2つあって
  1. 担当が自分の感覚で分けるので分け方がおかしい
  2. セグメントはちゃんと分けたが”購買意欲”等知りたいことに違いが表れなかった
特に①の失敗はあるあるですよね。
だからデータドリブンでどうこうという話になるんでしょうね。

今回は①の対策として”k-means法”を使ったセグメンテーションを行っていきます。
k-meansの簡単な説明や使い方についてはこちらの記事をご確認ください
②については別の記事にしてトライしたいと思います。

今回のシナリオ:足の形ってどんなグループに分けられる?

あなたはシューズメーカーの新しいマーケティング担当になりました。
担当することになるアマチュア向けランニングシューズについて来期のアクションプランを提示する必要があります。
そこで、そもそも市場にはどのような足の形の人々がいてどんな方向性でアプローチしていくべきか考える足掛かりを作ろうと思います。

分析の準備

データ入手

今回はあらかじめ適当なデータを作成しておきました。
足の形を長さ、幅、甲の厚さで定義しています。

データの標準化

レーダーチャートを作った時に比較しやすいようデータを標準化しておきます。
標準化にはscikit-learnのStandardScalerを使いました。
# 標準化
ライブラリのインポート
from sklearn.preprocessing import StandardScaler

# dat : オリジナルデータ
# new_dat : 標準化後のデータ
scaler = StandardScaler()
new_dat = pd.DataFrame(scaler.fit_transform(dat), columns=dat.columns)

print(new_dat)
平均を基準にそこから標準偏差の何倍離れていますか?という見方ですね。
なんだそれとなったらこちらの本が書き方は堅いですが内容はわかりやすいです。
ここでざっくりデータを眺めておきましょう。
項目が多い時は散布図行列が見やすくて便利です。散布図行列はseabornのpairplotがわかりやすいです。
import seaborn as sns

sns.pairplot(new_dat)

少なくても2つのグループには分けられそうですね。

k-meansを使ったクラス分け計算

クラスタ数の決定

scikit-learnのチートシートを確認してみるとk-meansはクラス(クラスタ)数がわかっているときに使うものですね。

なので、k-meansは分けるクラスタ数をあらかじめ指定する必要があります。

前述の記事にも書きましたが、いくつにするのが妥当かを決めるのにエルボー法というものを使います。
ここではk-meansの練習も含めてクラスタ数2で計算してみます
# Scikit learnのClusterからKMeansをimport
from sklearn.cluster import KMeans

# km2 : クラスター数2の計算結果
km2 = KMeans(n_clusters=2, random_state=0).fit(new_dat)

# lab2 : km2から計算後のラベル情報を抜いたもの
lab2 = km2.labels_

# 表示
print(lab2)
とりあえず計算はできましたね。
エルボー法はこの計算をクラスタ数を増やしていきながらデータの収まり具合(クラスタ重心と各点の距離の二乗総和)のバランスをみるという話です。実際に計算してみましょう。
# 図示用にmatplotlibをいれておく
import matplotlib.pyplot as plt

num_cluster = range(1,7) # xとしてクラスタ数
sum_of_squares = [] # yとしてクラスタ重心と各点の距離の二乗和のリスト
change =[] # クラスタが1つの時のsum of squaresを100としたときの比率のリスト

# km1 : クラスタ数が1の時の計算
# first : km1のクラスタ重心と各点の距離の二乗和
km1 = KMeans(n_clusters=1,random_state=0).fit(new_dat)
first = km1.inertia_

# for文で想定クラスタ数分計算してappendで箱に入れていく
for i  in num_cluster:
    km = KMeans(n_clusters=i, random_state=0).fit(new_dat)
    sum_of_squares.append(km.inertia_)
    change.append(km.inertia_/first*100)

# Dataframeに結果を格納
dfsum = pd.DataFrame({"sum_of_squares":sum_of_squares,
                   "change":change}, index=num_cluster)
print(dfsum.round().astype(int))

#描画
plt.plot(num_cluster,sum_of_squares,marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('Sum of Squares')
plt.show()
あまりきれいに出ませんでしたが、エルボー法はいくつならどうとういうものではないのでグラフを見ながら決めます。
ここではクラスタ数を3から4に増やしてもグラフの傾きがほとんど変わらないので3かな?といった感じでしょうか。
moku
グループは多過ぎると話がややこしくなるので精度とクラスタ数はトレードオフだね

計算

では改めてクラスタ数を3として計算してみましょう。
# km3 : クラスター数3の計算結果
km3 = KMeans(n_clusters=3, random_state=0).fit(new_dat)

# lab3 : km3から計算後のラベル情報を抜いたもの
lab3 = km3.labels_
これでセグメンテーションの計算は終わりです。
クラスタは3つあって、それぞれのデータがどのクラスタに分類されたかは lab3 でわかります

他にもk-meansを使うことで
  • 計算につかった各データはどのグループになるのか
  • この足の形の人はどのグループに所属しそうか
  • 各グループの代表値 (重心) はどこか
等がわかります。

説明用可視化

自分はわかったのですが、これでは人に説明するの難しいですよね。計算過程をみせてもわかりにくいし、次にどうするのか示唆していないので「だから何?」と言われるパターンです。

人に説明しやすいように2つのことをします。
  • データの塊をグループがわかるようにプロット
  • グループの特徴をレーダーチャートで表現

データの可視化でグループのイメージをつかんでもらう

今回は足の長さ、幅、甲の厚さという3次元ですが、これを紙や画面で説明できるよう2次元平面で表現したいと思います。

次元削減には主成分分析 PCA (Principal component analysis) を使います。
新たに軸を取り直して表現するというものですね。

多変量解析についてはこちらの本お勧めです。特に主成分の寄与率と固有値の関係性の部分がわかりやすかったです。

Scikit-learnのPCAを使いましょう。
# PCAをimport
from sklearn.decomposition import PCA

# pca : 成分(軸)を2つに設定
pca = PCA(n_components=2)

# new : 計算結果
new = pca.fit(new_dat)

# 新しい2次元平面に落とす
# value : 3次元データを2次元に投影した結果
value = new.transform(new_dat)

# Dataframeに変換して列名をつける
value = pd.DataFrame(value,columns=["x","y"])

# 先程計算したクラス分けの結果を足す
value["Label"]=lab3

# 表示
print(value)


これで新しい軸で表現した各点の座標とどのクラスに分けられたかがわかりました。

グループ別に色分けしてプロットしてみます。
# 散布図を描く
sns.scatterplot(data=value,x="x", y="y", hue="Label")
3つの塊があることをイメージできるかと思います。

ちなみにこの軸で3次元あったデータをどのくらい表現できているか確認します。
先ほど計算したPCAの計算結果newから寄与率を抜き出します。
# 寄与率を確認
new.explained_variance_ratio_
array([0.58546886, 0.25161807])

第一軸で58%, 第二軸で25%ということで3次元データを2次元平面で表現しても8割以上表現できているのでイメージを掴むには十分でしょう。

レーダーチャートでグループの特徴を比較

主成分分析で作成した新しい軸で各グループの説明をするのは結構難しいのでレーダーチャートで特徴を可視化してみましょう。

各グループの重心を求めれば比較可能だと思います。
# radar : 3つのクラスタの重心
radar = pd.DataFrame(km3.cluster_centers_, columns=dat.columns)

print(radar)
レーダーチャートはマイナスを表現できないのでこのままは使えません。
そもそも標準化で便宜上平均を基準にしただけなので全部プラスになるようにずらしてしまいましょう。最小値は-0.8なので1ずらせばOKですね。
# 1ずらす
# new_radar : ずらした後の重心データ
new_radar = radar + 1
print(new_radar)
レーダーチャートの最大値は2.5にしておけば良さそうですね。

では最後にプロットです。

# データを定義。ラベルと行数,グラフの最大メモリ
labelname = new_radar.columns # ラベルの名前
rownum = len(new_radar) # クラスタの数
max = 2.5 # レーダーチャートの最大メモリ

# 極座標を準備
fig = plt.figure()
ax = fig.add_subplot(111, polar=True)

# 角度設定
angles = np.linspace(0, 2 * np.pi, len(labelname) + 1)

# 北を起点に
ax.set_theta_zero_location("N")
# 時計回りに
ax.set_theta_direction(-1)
# Label名入れ
ax.set_thetagrids(angles[:-1] * 180 / np.pi, labelname) 
# Max入れ
ax.set_rlim(0 ,max)

# データプロット
for i in range(0,rownum):
  values = new_radar.loc[i]

  # データ準備。最初の値を最後に加えることで一周させる
  values = np.concatenate((values, [values[0]])) 

  # データプロット
  ax.plot(angles, values, 'o-')
  ax.fill(angles, values, alpha=0.1)
3つのグループ足の形の特徴をみると
  • 細長いグループ
  • 幅広グループ
  • 小さくて甲高グループ
と特徴付けられますね。

3つのグループにわかれて、それぞれの特徴について理解できるかと思います。

最後に

マーケティングの基本であるSTPのSegmentationを機械学習をつかって行ってみました。
データサイエンスもよくある横文字言葉の独り歩きで、すでに人気に陰りが出ているように思いますが、小難しい計算をしてもしなくても客観的に判断できるようにしておくのはいいですよね。

特にマーケティングは偉い人の声で方針が大きく変わってしまうことが多いのでこういった事実に基づいた考え方で誰がやっても同じというのは成功しても失敗しても次のアクションにつなげられることが重要なんだと思います。