こんにちは。オイシックス・ラ・大地のデータチームです。
今回は、Oisixサービスページのトラフィックログを用いて行なった、お客様のクラスタリングについてお話しします。
背景
弊社のサービスは現在22万人を超えるお客様にご利用いただいています。
サブスクリプション(定期購入)型のビジネスモデルで、多くの方にリピートいただいています。(ありがとうございます!)しかし、その一方で、一部のお客様は、購入頻度が低くなってしまっている*1という課題を抱えています。
この、「購入頻度が低くなっているお客様」を一括りで捉えるのではなく、何らかの傾向をもつ、いくつかの(意味のある)グループにわけ、それぞれをより具体的に捉えることができれば、潜在的なニーズの抽出などができるのではと考え、クラスタリングを行いました。
具体的な活動は以下の3ステップになります。
1. 対象となるお客様(後述)の購入サイトのトラフィックログ等を用いて、クラスタリングを行う
2. 得られた各クラスタの特性を分析する
3. 各クラスタの属するお客様に適切な施策を考える
今回は、このうち主に1, 2についてお話ししようと思います。
データ
保有するデータ
最初に、オイシックス・ラ・大地のもつデータをいくつか紹介します。
会員情報
- 登録時の会員情報
- ご利用コース区分(旬・Kitコースなど)
受注履歴情報
- いつ何をどのくらい購入いただいたか
- お支払金額
- 顧客ごとの過去1年間の購入商品(いつその商品を購入されたか・1年でいくつ購入されたか)についての情報
- 受注商品を4週以内に再購入いただいたか
サイトアクセスログ
- アクセス日時
- どのページを閲覧いただいたか
- ご利用デバイス
- アクション内容(表示、お気に入り追加・削除、レビュー投稿、注文確定・キャンセル・削除、カート操作...)
その他
- 予約
- 請求
- アンケート
- お友達紹介
- 商品レビュー情報
など
今回使用するデータセット
1. 対象となるお客様
着目している時点(週)において、
①購入頻度が低くなっており
②購入されなかった
お客様(約3万人)を対象としています。
2. 使用する情報
会員情報
- ご利用コース区分
→ご利用コースによってサービスの使用方法(回数・金額・購入商品)の傾向が異なる可能性があると考えたため
- プライム会員*2のフラグ
→プライム会員とそうでない会員では、購入意欲の傾向が異なる可能性があると考えたため
受注履歴情報
- 最終購入週
→直近購入があったお客様とそうでないお客様でサイトのログの傾向が変わる可能性があると考えたため
サイトアクセスログ(着目している週のアクセスに関する情報)
- ご利用デバイス
→デバイスとサイトログとの関係性があるか確認したかったため
- キャンセルを確定するまでにかかった時間
→キャンセルをもともと決めていたか、そうでないかの傾向を読み取れると考えたため
- サイト滞在時間・サイト訪問回数・閲覧ページ数
→どのくらい興味を持っていただいたか、悩まれたかを読み取れる可能性があると考えたため
その他
過去数週間を集計した情報も使用し、
- 商品をご覧にならずにキャンセルされた割合
→もともとキャンセルを決められていることがどのくらいの頻度で起こりうるのか捉えたかったため
- 購入のあった割合
→(購入頻度が低くなっているお客様に着目しているが、)たまに購入いただいているお客様とそうでないお客様で傾向が変わる可能性があると考えたため
- 購入商品の内訳x個数
→興味を持たれる商品の違いがログの傾向として現れる可能性があると考えたため
クラスタリングについて
クラスタリングとは
簡単に言うと、データの集合を類似性に基づいて、グループわけを行うことです。
今回の例で言い換えると、「対象となるお客様全体を、ログの傾向が似ているお客様ごとにグループ分けを行うこと」になります。
クラスタリングの手法について
ここで、クラスタリングを行うにあたって、代表的な手法をいくつか紹介します。
以下で用いる画像はscikit-learnの公式ドキュメントのものを使用しています。
K-means
おそらく一番有名なアルゴリズム。
類似するデータの中心や出現頻度の高い点を捉えて、クラスタ内のばらつき(クラスタ慣性)*3を最小化します。
また、予めクラスタ数を設定する必要があるという特徴を持ちます。*4。
指定したクラスタ数分の中心点を決めて、そこからの距離でどのクラスタに属するかを決定するイメージ
階層型クラスタリング
データの近い(類似度の高い)ものから統合していき、最終的に1つの塊になるまで繰り返していくアルゴリズム。
以下のようなデンドログラムができあがります。
「近いものは同じクラスタとみなす」を最終的に1つの塊になるまで繰り返すという手法なので直感的にわかりやすく、また、最終的に得られるデンドログラムを元にクラスタ数を自由に設定できる*5という特徴があります。
DBSCAN(Density-based Spatial Clustering of Application with Noise)
和訳すると、「ノイズを伴うアプリケーションの密度ベースの空間クラスタリング」、、で良いのかな?
データを密度をもとに、各データがどのクラスタに属するかを決定するアルゴリズム。
外れ値は外れ値として処理でき、階層型と同じく、予めクラスタ数を設定しなくてよいという特徴があります。
同じ塊は1つのグループというイメージ。
その他手法(K-modes/K-prototype)
代表的なクラスタリング手法を3つ前述したが、補足で2つ紹介します。
K-modesとK-prototypeを簡単に説明すると、K-meansの変更版(K-modes)/K-meansの拡張版(K-prototype)です。
K-modes:カテゴリ変数のクラスタリングを行うために、クラスタ内のばらつきを計算するための距離指標を変更したもの*6。
K-prototype:K-meansとK-modesを組み合わせることで、数値変数・カテゴリ変数が混在するデータでクラスタリングができるようにしたもの。
こちらのgithubの実装コードを利用すると、scikit-learnチックに使用できます。
クラスタリング結果イメージ
上記で説明した手法(KMeans、 階層型クラスタリング、DBSCAN)のいくつかのデータセットで実行して得られる結果のイメージは以下のようになります。
画像はscikit-learnの公式ドキュメントよりお借りしています。
※階層型クラスタリングは、いくつかの手法があるため、複数の結果を掲載しています。
KMeansは、クラスタ中心からの距離(正確にはクラス内分散を最小化するように)で各クラスタを決定づける。そのため、ある境界できっぱり分離しているような結果が得られています。
DBSCANは、密度の高い部分は同じクラスタと決定づける。そのため、塊を1つのクラスタとしているような結果が得られています。
階層型クラスタリングについて補足しておきます。
近い観測点は同じクラスタとしますが、近いの基準をどうするかで手法がいくつか分かれています。
Ward Linkage: クラスタ内の差の2乗の合計を最小化する。(KMeansと目的関数*7が似ているので、上図で似た結果が得られています。)
Average Linkage: クラスタ内の観測点の距離の平均値を最小化する。
Complete Linkage: クラスタ内の観測点の最大距離を最小化する。
Single Linkage: クラスタのペアの最も近い観測点の距離を最小化する。(より近いものほど同じクラスタになるので密度ベースの結果に近くなる傾向がありそうです。)
補足1
今回は
1. 階層型のアウトプットは特に必要としていなかった
2. データを確認(EDA)する中で、性質上、データ点の分布が、ある境界をもって分割されているようなものではなかったこと(塊を持って分布しているようなものではなかった)
以上の2点から、基本的にはk-meansのアルゴリズムで進めることにしました。
もちろん、2に関しては、多次元空間上でどうデータが分布しているかはわからないので、k-meansで上手くいかなかったら密度ベースのアルゴリズムも使用しようかなとは考えていました。
また、途中でカテゴリ変数も用いてクラスタリングするために、K-prototypeを一部試しました。
補足2
一般的に、クラスタリングを行う前に主成分分析を行なって次元削減をした方が良いという説もあります。
理由としては、
1. 「次元の呪い」を防ぐだけでなく、
2. 可能な限り重要な情報を残しながら、冗長な情報(ノイズ)を除去できる
などの利点があるためです。
しかし、今回に関しては、主成分分析は行わず*8、特徴量選択を行うことで、次元を減らしています。
実装コード
クラスタリングの実行
scikit-learn
を用いれば、クラスタリングアルゴリズムを使用することは簡単で、以下のようになります
import numpy as np from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler ... # data: お客様x特徴量の配列 X = np.array(data) # 標準化で各特徴量間のスケールを調整する scale = StandardScaler() X = scale.fit_transform(X) # n_clusters: クラスタ数を指定 n_clusters = 3 kmean = KMeans(n_clusters) kmean.fit_transform(X)
ちなみに、読み込ませたデータは以下のようになっています。
各行それぞれのレコードがお客様に対応しており、お客様それぞれに対応した特徴量が格納されています。
クラスタ数の決定
上記のn_clusters
(クラスタ数)の決定は、エルボー図*9を元に決定しました。
エルボー図は以下のようにして作成します。
import matplotlib.pyplot as plt # 探索範囲(今回は1~9クラス) n_clusters = 10 cost = [] for i in tqdm(range(1, n_clusters)): # 分離するクラスタ数の指定 kmean = KMeans(i) kmean.fit(X) cost.append(kmean.inertia_) plt.plot(cost, 'bx-')
得られたエルボー図は以下のようになりました。
上図より、クラスタ数は3が適当かなと判断しています。*10
特徴量エンジニアリング
サイトログは多くの情報(以下、特徴量)を持っていますが、
1. そのままでは、上手く結果に反映できない可能性がある。
2. 目的に関係のない特徴量が含まれる。
3. 特徴量が多すぎると上手くいかない原因となる。(次元の呪い)
そのため、効率の良い特徴量を作成し、また選択することが重要だと思います。
特徴量作成
データベースに格納されている値をそのまま使用するよりも、加工した方が上手くいった(と考えられる)例を紹介します。
今回、サイトのログで以下のようなものを使用していました。
duration: サイト総滞在時間 visit: サイト訪問回数 pageview: 閲覧ページ数
初めは、これらをそのまま(外れ値の処理のみ)アルゴリズムに渡すだけでしたが、クラスタごとの傾向の差があまりなく、そもそもクラスタの分離も曖昧でした。
そこで、以下のように、これらを変形してアルゴリズムに渡してみました。
duration/visit: 訪問ごとの滞在時間平均 duration/pageview: 1ページに費やす閲覧時間平均 pageview/ visit: 訪問ごとの閲覧ページ数平均
このようにすることで、クラスタごとの差が以前より明確になったように思います。
解釈例としては、例えば、「購買意欲が高く、多くのページを閲覧されているお客様」と「なんとなくページを閲覧していたお客様」や「間違ってページ遷移されたお客様」は同じページ遷移数でも 1ページに費やす閲覧時間 では差が出そうです。
こういった差をアルゴリズムが捉えやすくなったのでは?と考えています。
特徴量選択
基本的には、どの特徴量を使用するかは、業務的な知識を使用していくことになるかと思います。
(余談ですが)今回は、業務知識を補うために、簡易的な受注予測モデルをGBDTアルゴリズム*11で構築し、得られる重要度*12をもとに受注に関わる特徴がどういったものがあるかの確認をしました。
また、以下では、特徴量を選択する際に議論を交わした一部を紹介します。
KitOisix*13の購入点数は、商品の性質上、お客様間でよく差の出やすい情報の1つであると考えています。
この情報を使用する際に、以下のような特徴量が考えられました。
1. KitOisixの購入点数 2. KitOisix以外(野菜・魚・...)の購入点数 3. 総購入点数(1 + 2) 4. KitOisixの全体に占める割合 5. KitOisix以外の全体に占める割合
これらの特徴量を全て使用するかどうかが議論となりました。
以下、議論内容の一部ですが、
「特徴量としては5つあるけど、このうち2つが分かれば、他の数値は一意に決まるので、冗長なのでは?」
「KitOisixの購入に占める割合が同じ50%でも、5個買った場合(トータル10個)と10個買った場合(トータル20個)では、購買意欲等が異なりそう。それぞれの特徴量はそれぞれの意味合いが異なるので必要なのでは?」
「そもそもこれらを主成分分析すると2次元でほぼカバーできる」
「情報が抜け落ちそう」
なので、、、
一旦全部試しました。笑
最終的には、「情報量が冗長だとしても悪影響はなさそう。あるとするとこれらの数量特徴の影響が少し余計に反映された結果が得られるくらいだろう。また、結果の意味解釈性の観点からあった方が良いかもしれない」となり、5つの特徴量全てを使用した結果で最終的なアウトプットとしています。
結果
結果の可視化
用いたデータを2次元まで次元を削減して、クラスタリング結果をプロットすると以下のようになりました。
境界が曖昧な部分もあるようにも見えますが、全体的には傾向を捉えてクラスタ分けできてそうです。
クラスタの解釈
重要なのは、各クラスタの解釈です。
以下の項目を確認し、各クラスタについての傾向を深掘りしました。
特徴量の傾向1: 使用した特徴量
以下のように、各クラスタごとにクラスタリングに使用した特徴量のヒストグラムを確認し、クラスタごとの傾向を確認しました。
例1:トラフィックログ
下図に示す特徴量の説明 duration_per_visit: 訪問ごとの滞在時間 duration_per_pageview: 1ページごとの閲覧時間 pageview_per_visit: 訪問ごとの閲覧ページ数
クラスタ0(左端)は、他のクラスタと比較すると、訪問ごとの滞在時間、1ページごとの閲覧時間は長いデータが含まれます。
クラスタ1(中央)は、逆に、訪問ごとの滞在時間、1ページごとの閲覧時間が短いお客様が極端に多く含まれています。
クラスタ2(右端)は、上記2つの間くらいで、訪問ごとの滞在時間、1ページごとの閲覧時間が短いお客様も多いものの、そのお客様の占める比率は低いように読み取れます。
訪問ごとの閲覧ページ数の傾向差はこの結果ではあまり読み取れなさそうです。
例2:KitOisixとそれ以外の購入数・率
下図に示す特徴量の説明 kit: 過去一定期間で購入されたKitの個数 other: 過去一定期間で購入されたKitの個数 total: 過去一定期間で購入された商品数(kit+other)
クラスタ0(左端)は、KitOisixもそれ以外も比較的よく購入されてそう
クラスタ1(中央)は、KitOisixもそれ以外もあまり購入されていなそう
クラスタ2(右端)は、KitOisixはよく購入されているが、それ以外はあまり購入されていなそう
KitOisixは主に、「日々忙しい方生活を送られている方向けに、料理の負担を減らすことを目的としている商品」です。そのため、KitOisixを購入数とKitOisix以外(野菜・魚など)の購入数は、ある種、逆相関(反比例)の関係にありそうだと考えていました。*14
クラスタ0(KitOisixもそれ以外も比較的よく購入されていそうなクラスタ)のお客様を深掘りし、
(1)KitOisixをよく購入されるお客様とKitOisix以外の食材を購入されるお客様が混在している
(2)忙しい時はKitOisixを利用し、余裕がある時は食材から料理されるといった使い方をしていただいている
のどちらなのか深掘りしていくと、お客様に対する深堀りができるかな、と考えています。
あくまで、傾向しか確認できないので、「そのクラスタに属するからこうだ」と断言はできませんが、傾向は読み取れそうです。
特徴量の傾向2: 使用していない特徴量
クラスタリングに用いていない特徴量ごとに傾向を深掘りしました。
例えば、ご利用されるデバイス(iOS, Android)や閲覧ページ、購入された商品詳細など。
カテゴリデータなどは、(途中K-prototypeなどで対応してみましたが)最終的に使用していないので、それらが得られた結果で傾向があるかどうかを確認しました。
ご利用のデバイス
クラスタ0のみ他のクラスタと傾向が大きく異なり、(iOS)アプリの使用率は低く、PCやスマートフォンからのWebサイト経由でアクセスされる傾向がありました。
クラスタ0はサイト滞在時間が長い傾向があり、そのことと使用デバイスの関係性は別の機会に、慎重に調査する必要があるかもしれません。
閲覧ページ
閲覧ページに関しては、クラスタ間で大きな差は見られませんでした。
購入商品詳細
クラスタ0(左端)は、全体的に幅広い商品を購入されている。
クラスタ1(中央)は、調味料や加工食品を他のクラスタと比べよく購入される傾向がありそう。また優待券(クーポン)を比較的多く利用されていそう。
クラスタ2(右端)は、主にKitOisixを購入されており、それ以外の購入数は極端に低い。
将来の受注の傾向
使用したデータの時点よりも将来のデータから、クラスタごとに受注率の傾向の差がでているのかを確認しました。
将来の受注率が高い(もしくは低い)お客様を見つけることが目的ではありませんでしたが、どういった特徴をもつお客様が受注面ではどういう振る舞いをするかについて確認するために行いました。
①翌4週で1回以上購入されたお客様の含まれる割合 クラスタ0(左端)は、76.98% クラスタ1(中央)は、59.21% クラスタ2(右端)は、64.19%
上記は、翌4週以内に1度以上購入されたお客様が含まれる割合を表しており、クラスタ0に属するお客様の約77%は1ヶ月以内に何らかを購入される結果となり、逆にクラスタ1に属するお客様は約60%程度であったという結果が得られました。
②翌4週で平均受注率 クラスタ0(左端)は、36.89% クラスタ1(中央)は、22.12% クラスタ2(右端)は、28.38%
こちらは、翌4週における受注率の平均を算出したものです(基準としては、クラスタに含まれるお客様全員が1ヶ月以内に1度だけ購入されると25%になるという指標となります)。
上記を踏まえると、クラスタ0に含まれるお客様は平均して1度以上は購入されていますが、クラスタ1に含まれるお客様は1度買うか買わないかに収まっているという傾向があります。
クラスタごとの傾向まとめ
上記で説明したような各クラスタごとの傾向を整理してみます。
クラスタ0
訪問ごとの滞在時間、1ページごとの閲覧時間が長い傾向
KitOisixもそれ以外も比較的多く購入される傾向
商品詳細からも幅広く買われていることが読み取れる
PCやスマートフォンからのWebサイト経由が多い傾向
他のクラスタと比較すると受注率は高め
クラスタ1
訪問ごとの滞在時間、1ページごとの閲覧時間が短い傾向
KitOisixもそれ以外もあまり購入されないお客様
調味料や加工食品が他の商品よりも購入される傾向がありそう
クーポン券をよく利用される傾向がありそう
他のクラスタと比較すると受注率は低め
クラスタ2
訪問ごとの滞在時間、1ページごとの閲覧時間は他の2つのクラスタの中間程度。
KitOisixをメインに購入されているお客様
施策について
これまでの結果を踏まえて施策を考えてみます。
施策案1
クラスタ0のお客様は、Webサイトを比較的よく閲覧していただいており、商品も幅広く購入いただいている傾向にあります。しかし、裏を返すと、欲しいものを見つけるためにはそれだけ時間を使ってサイトを閲覧する必要があるのかもしれません。
現在、Oisixサイトは商品カテゴリごとに別々に商品を紹介していますが、カテゴリを跨いで(お客様の購入傾向に合わせて)商品の組み合わせ提案することで、Webサイトを閲覧する負担を減らすことができるだけでなく、カテゴリ別に閲覧していたら気に留めなかった可能性のある商品に目を止めていただく機会を作り出せる可能性があるのでは?と思います。
施策案2
クラスタ1のお客様は、あまりWebサイトを閲覧されずにキャンセルされてしまう傾向が見られます。また、他のクラスタのお客様と比較して、クーポン券を使用される傾向があります。
そのため、送料無料等のクーポンを発券し、少しでもWebサイトを閲覧していただけるきっかけを作ることができれば、受注率をあげることができるかも?と思います。
施策案3
クラスタ2のお客様は、KitOisixを中心にご利用いただいている傾向があります。
そのため、KitOisixまとめ買いで少しお得になるような施策等あれば、受注率が上がる可能性を秘めているのでは?と思います。
まとめ
「最も重要な点は,クラスタリングは探索的 (exploratory) なデータ解析手法であって,分割は必ず何らかの主観や視点に基づいているということです.」クラスタリング(クラスター分析)と神嶌先生も仰られている通り、どこに着目しているかによって(アプローチも結果も)変わるなと感じました。
アヤメの分類*15以来のクラスタリングだったのですが、意外と奥深く、楽しかったです。
やってみました!のような情報がネットにあまりありませんが、他社の行なっているクラスタリング事例も見てみたい。。。
少し長くなりましたが、最後まで、ご覧くださってありがとうございました!
*1:注文が月平均2回以下になっている
*2:毎月定額で以下の2つの特典が利用できるとってもお得なサービス。
*3:クラスタ内のばらつき:各クラスタの中心点から、各データまでのユークリッド距離の誤差平方和
*4:クラスタ数の決定には、エルボー図やシルエット分析を用いる
*5:枝をどこで切るかと言う話なので
*6:カテゴリ変数と数値変数ではデータの性質(尺度水準)が異なるため
*7:最小化する対象
*8:結果として得られる各クラスタの傾向を捉えやすくするため。次元削減すると必ずしも元の特徴量に戻した際に傾向が反映される保証がなかったため。
*9:クラスタ数を変化させた際のクラスタ内誤差平方和をプロットしたもの
*10:誤差の減少の下がり幅が大きく変化している点に着目
*11:Gradient Boosting Decision Tree(勾配ブースティング決定木アルゴリズム)
*12:各特徴量の分割がターゲットの予測にどれくらい寄与しているかを測る指標
*13:「20分で主菜と副菜が作れる必要な食材を必要な分だけ集めた食材セット」です。
*14:時間に余裕のあるお客様は食材を購入いただいて、調理される。忙しい方はKitOisixで時短で調理される。と考えたため
*15:機械学習における"Hello World"的なもの?