広告/統計/アニメ/映画 等に関するブログ

広告/統計/アニメ/映画 等に関するブログ

技術的な知見がある人こそが「重要なマーケティング」をできる

最近立て続けに中国企業の成長を探る本を読み、モヤモヤとしていたことが瓦解しました。

このエントリーにまとめたことは上、2つの本を読めば実質的に解決します

マーケティングという言葉の幻想について

最近「マーケティング」という言葉が反乱していて、色々な人がポジショントークをしているように感じます。もともと定義がしっかり決まっている職種でもないのと、日本の場合は「なんか凄そう」というイメージもついているせいか、言葉を拡大解釈して自分のセールスに使っている人も多い印象があります。

様々な立場にとっての「マーケティング

私が関わって来た中で、マーケティングという言葉は大きく以下の3つの使われ方があるように思います

以降、簡潔に紹介しますが、少し横道なので以下3ブロックは読み飛ばしても問題ありません

戦略コンサルタントの使う「マーケティング

マッキンゼーやボストンコンサルティングといった所謂戦略コンサルタントは、製品のラインナップを確認し、

  • ライフサイクル上、衰退期に居る商品部門を縮小する
  • カニバリゼーションを起こしている商品部門を解体する
  • 販売チャンネルを見直し営業の人員配分を変える
  • 競合優位性の薄いブランドの売却する

といった形で、大きな視点での改善を提案していきます。彼らはこのようなレベルで「マーケティング」という言葉を使います。よくみるSWOT分析や製品のライフサイクルなどは彼らの提唱によるものだったと記憶していますが、会社全体を視野に入れて事業部の力配分にメスを入れることを期待されています。

コンサルタントだけを生業にしてきた人は、分析力を買われているのだと思っている節があるようなのですが、傍目から見ていると分析力自体は普通で、そんなことよりも外部の人だからこそできる部門横断のヒアリングと取り潰しという内部でやると軋轢が生まれそうな組織と組織の壁を壊す精神的なストレス代行に高い報酬が支払われているように見えます。

BtoBソリューション提供企業が使う「マーケティング

最近「マーケティング」という言葉を反乱させているのはこちらのタイプの方々のように思います。

顧客にセールスするのが営業それをサポートするのがマーケティング

簡単に言うとそういう意図で使われているようです

  • PRで自社製品が必要とされる社会的気運を高める
  • ホワイトペーパーを作り展示会に出展しサイトやメルマガのコンテンツで見込み顧客のリストと優先順位をつけて営業に渡す

私は広告代理店の人間なので「それはプロモーションなのでは?」と思ってしまうのですが、この用法は間違っているわけではなく、伝統的な企業でも「~マーケティング」という子会社は、だいたい「販売用子会社」ということがあります。

最近はBtoBセールス分野のソリューション市場が活発なため、「SNSマーケティング」や「WEBマーケティング」といった形で、「マーケティング」という用語が氾濫しています

広告代理店が使う「マーケティング

これも恐らく使い方としてあっているわけではないのですが、巷の書店に平積みされる広告代理店の人や出身者が書いた本にも「マーケティング」ということが乱立しています。

  • プロモーションの戦略(とくにBtoC)
    • ターゲットインサイトの調査と分析
    • コミュニケーションの費用を投下すべき戦略ターゲットの選定
    • 3C分析やPEST分析を踏まえた、世間の文脈へのブランドのポジションの再定義

という意味で使われていることが多いと思います。

戦略コンサルタント目線では「Price、Place、Product、Promotion」がマーケティングの4つのPであって「Promotion」に特化する場合は、マーケティングと呼ぶと誤解を招くと思いますが、一般の人にはもやはこっちの意味での「マーケティング」の方が馴染みがあるかもしれません。

「製品企画時点でターゲットは決まっているべき」なのですが、実態としては既存製品の延長で世に出ている商品も多く、「商品は何も変わらないのだけど見せ方や売り込む相手を変えてもう一度、売れる商品に返り咲きたい」というニーズは極めて多いです。果たして製品の見直しをせずにできる範囲は限られているのではないか?とも思いますが、他部門と軋轢を生むことなく既存プロセスの延長で拡販できるという精神的負担の少なさが、このニーズを生み出しているのでしょう。

より重要な視点は「売れる仕組み作り」

それぞれの立場から「マーケティングとは何か?」を哲学者のように議論し続けることも趣味としては良いのだと思いますが、より重要なことは、「どうやって売れる仕組み作りをするか?」ということだと思います。商品企画寄りであろうと販売促進寄りであろうとプロモーション寄りであろうと、最終的な目的地は、自社製品を都合よく市場で生き残れせることがゴールのはずです。

そしてそのレベルを目標にすると、どうしてもビジネスモデル自体を改善しないと到達しないことに気がついていくはずです。

企業の社会的存在意義とビジネスモデルの本質

ところで企業が社会に存在する意義は何でしょうか?最近読んだ本にわかりやすい説明がありましたが、

社会の効率をより良くする

という解釈がもっともしっくりくるのではないでしょうか?

一人一人が農作業から道具作りまで全て自給自足していると多忙で死んでしまいますが、より得意な人に集積し役割分担することで社会全体の効率が上がっていく、お互いの便益の提供の媒体として「費用」を払う。企業が高い報酬を得ることが許されるのは、足元を見て暴利を貪るからではなく、その費用を払ってでもやってもらった方がトータルでは安いからということでしょう。

徹底的に効率化をはかる中国企業

以下は、冒頭に挙げた2冊の本からの受け売りですが、

  • Xiaomiは80%のユーザーの80%の満足を満たすことで安くて良いものを提供することに成功した
  • Alibabaのリアル店舗は中間流通業者を極力排除し自分たちの強みを活かせる店舗で勝負をした
    • ECサイトでユーザーを把握しているのでどのエリアに高所得者が居てどの商品が売れるのか知っている
    • 思いついてすぐ用意できる30分という時間に注目した30分以内配達サービスのために売れそうな商品を近くの倉庫に持ってくる

など、伸びている中国企業は徹底的にコストダウンと顧客サービス向上を両立させています。

  • 日本の企業は、ユーザー満足度を上げるために不必要に製品価格を上げてしまう
  • 米国のデータ活用企業はユーザーの購買履歴をサービス向上には使わずにメルマガやポップアップ広告に使う

といった残念な方向へ進んでいますが、生き残るのは中国企業のように効率化を如何に達成するか?に集中している企業でしょう。

安くて良いものは当然ながら口コミで広まっていきますので、プロモーションで不必要にイメージアップにコストをかける必要もありません。

技術的価値提供ができる投資家

余談として紹介しますが、Xiaomiは投資スタンスも独特です。「ただお金を出す」のではなく、「自社の技術やブランドが相手のメリットになるか?」で投資先を決めています。

  • Xiaomiのブランドを冠するとローンチ時に売れやすくなる
  • Xiaomiの要素技術を提供すると伸びる

などの視点があるため、ベンチャー企業側も、ただお金を出すだけで製品開発に協力してくれない投資家よりもメリットが多く、ただのベンチャーキャピタルよりもXiaomiと組むことを選ぶことがあるようです。

当たるか当たらないかわからないけれどリスクマネーを投じなければならない文系だけの投資会社には真似をすることは難しいと考えると、今後投資家という世界も変わってくるでしょう。

技術を理解した人だけが「売れる仕組み作り」に貢献できる

Xiaomiの例もさることながら、中国以外の企業でも、更には昔から「技術を理解した人が商品を売れるしょうにできる」という事象は同じようです

という本では、「技術開発部門の人がリーダーとなって、ユーザー調査をし市場の課題を把握し、自分の知識の足りない要素技術も他部門や外部の人と折衝し、社内の意思決定者の説得をし、商品ローンチ後のサポートと改善まで把握して商品を世に送り出していくさま」が様々な事例とともに描かれています。

終わりに(自身の実感として)

広告代理店のセールパーソンとして関わる中でも実感をしていますが、ターゲットのインサイトを深堀りし、プロモーション上でこの人はコストをかけて対応すべきターゲットだ、この人は違う、といった判断をしているのは、意外にも製品企画の立場の人であったりします。本業は製品企画であり製品の開発・改善の方が業務量は多いのですが、意欲的にコミュニケーションの戦略にも関わってきます。とても大変だと思いますが、結局のところ製品の技術改善やプロセスの改善ができる人だけが「売れる仕組み作り」の全体を俯瞰することができるのでしょう。

マーケティングとは何か?」の議論も大切ですが、このようなポジションの人が今後、企業の中心を担っていくのだと思います。

仕事の生産性を下げているのは、「人間という不完全なモノ同士のコミュニケーション不良」の積み重ね

ビジネスの進行を阻害する大きな要因が、自分の心の弱さであるころは、

twitter.com

が良くご指摘されていますが、 他にも、面倒なことを後回しにせずに「すぐやる」と説いているビジネス本も本屋でよく平積みされていると思います。

見過ごされがちなのですが人間の心の弱さは様々なケースで生産性を下げていると考えられます。

組織内のコミュニケーションコストについて

最近とても面白かった本に

という本がありました。

  • 先が見えない課題から先に始めて、工程管理の最終フェーズでは先が見えるタスクだけにするようにしよう
  • 課題の整理レベルでは「仮設」と呼ぶべきではない
  • 意見が言いやすい組織にしよう

等々、様々な納得がある本です。

その中でも組織のヒエラルキー内のコミュニケーションコストに注目しているのが非常に印象的でした。

そこから振り返ると、恐らく日本の組織の生産性を下げているのは人間という不完全なモノ同士のコミュニケーション不良の積み重ねによるのではないか?と思えてきました。

コミュニケーション不良の例

幾つかイメージしやすい例を考えてみます。

お願いした通りに納品されず出戻りが発生する

機械が仕様の通り作るものは比較的正確に不良品が少ないのが昨今ですが、人間が作る「レポート」「提案書」「デザイン案」機会などは、”思った通りのものが仕上がってこない”ということで作業遅延することはママあります。

  • 依頼する方が課題を構造的に把握できず正しくオーダーできていない。
    • 「何か違うんだよねー」
      • ※依頼者が考えるのが苦手で、サボってしまった。
  • 依頼された方が、独自性を発揮し、納品物に過不足がある
    • 「こっちの方が良くないですか??」

面倒な人が居ると避けるようになる

威圧的な人が居るとしばしば組織が崩壊することも、パワーハラスメントが叫ばれる中で注目されてきていると思われます。

  • 取引先へ連絡するだけの業務をまだしてない
    • 「済みません。バタバタしちゃって」
      • ※威圧的な相手に電話するのが心理的負担で後回しにしてしまった
  • 事故の報告が組織の上にすぐ上がらない
    • 「先ず、事実確認をしないと」
      • ※ミスを怒られるのが怖くて報告をせず、運良く時間が解決することに期待してしまった
  • 社内で連携が取れず同じ業務を別々の部署が外部にコストを払ってしまう
    • 「あそこの部署、面倒な人ばっかなんで、もうお金で動く外部の人に依頼した方が早いんですよ」
      • ※相談に丁寧に対応してくれない部署を誰も使わなくなってしまった

良い人に思われたい、悪人に思われたくない

或いは、個人の心の弱さによる

  • 無駄な会議が減らないし進め方が変わらない
    • 「何か反論する空気じゃないじゃないですか?」
      • ※面と向かって、やり方が違うと提示する勇気がない
  • 得意先と発注先や社内スタッフの認識のズレを調整できない
    • 「客が素人でわかってないんですよ」「そうですよねー」
      • ※発注先やスタッフに嫌われたくないので強く言えない。嘘を言ってしまう。
  • ネガティブ思考の人の考えがチームに広まる
    • 「予算が少ないからなぁ」「元々難しいお題でしたから」
      • ※チームリーダーが「お前は要らない」と強く言えない

働き方改革パワハラ対策という視点では見えてこない

人間の本能に起因

人間の弱さは防衛本能

これも多くの人が指摘していることで、学術的な研究があるのかどうかはわかりませんが、実感としては正しい素朴な理論だと思います。

  • 自分を否定されたら強く反論してしまう
  • 自分が悪者にはなりたくない
  • 怒られたくない
  • 自分の色を出したい

などは、全て自分を守るために自動的に反応してしまうので、意識していてもある程度は避けられません

人間はだいたい怠惰

習慣化しないとダイエットも何も継続しないとはよく言われますが、

  • 楽をしたい、面倒な作業を後回しにしたい
  • 面倒な人との衝突は避けたい

といったことも怠惰な本能ではないかと思います。

対処について

個人のメンタルの問題に終始すると解決しない

パワーハラスメントについては、多くの会社が、パワハラと告発された人を左遷する、という対処療法的なことや、「パワハラは”今の時代ダメなんですよー、若い人辞めちゃいますよー”」という研修をして、対応しているのではないでしょうか?

或いは、すぐにやれない人への対処は、先輩からのOJTによる指導、で対応しているかもしれません。

しかし、これらの行動が「人間の本能から来るもの」だとしたら、小手先の対応ではないでしょうか?

人間同士のコミュニケーション機会を減らす

人間同士のボールのパスが増えれば増えるほど、コミュニケーション不良により不良品が発生します。「エンジニアリング組織論への招待」では、それを”アジャイル”な組織体制に解決を求めていました。

重要なのが、人間から人間へのパスの回数を減らすということなのだとしたら、

  • マルチタレントの数を増やして分業の回数を減らす
  • そもそも作成を人間ではなくて機械にやらせる
    • イメージが難しいかもしれませんが、例えば得意先のニュースをまとめるならクロールしてトピックモデルにしてしまうと、人間のバイアスが減ります。自分の色を出してバイアスをかけてしまうことを排除できます

など、構造的な解決が可能です。

一人一人のメンタルを改造していくことも必要かもしれませんが、本能だとすると限界がありますので、いかに構造的に故障率・不良率を下げていくか?という工場生産ラインのカイゼンと同じような改善が必要なのではないでしょうか。


  • 改めてオススメの本として

  • また、チームのコミュニケーションを良くする方法についてはこちらの本も良いです。 グーグルのエンジニアの本ですが、チームの中に居て空気を悪くする人にちゃんと変わって欲しいと伝える、など、とても実務的な本です

OpenCVで画像をくっきりさせる

OpenCVに関する記述が出てきて、画像をくっきりさせる という技術があることに驚いたので、自分の画像で試してみました。

事前準備

必要なパッケージのインソール

OpenCVが必要です。anaconda には無いようなのでpipでインストールします

qiita.com

pip install opencv-python
pip install opencv-contrib-python

画像を取り込んで確認する

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import cv2
image_cap_bgr = cv2.imread("data/cap.jpg",cv2.IMREAD_COLOR)
plt.imshow(image_cap_bgr)
plt.axis("off")
plt.show()

f:id:yyhhyy:20190811181508p:plain
output_8_0

元の画像がわかりにくかったですが、openCVはRGBではなくBGRで読み取るため、赤と青が逆転しています。

image_cap_rgb = cv2.cvtColor(image_cap_bgr,cv2.COLOR_BGR2RGB)
plt.imshow(image_cap_rgb)
plt.axis("off")
plt.show()

f:id:yyhhyy:20190811181538p:plain
output_11_0

保存しておきます。 ただし、OpenCVは勝手にBGRだと思って保存しますので、Matplotlibで表示するためにRGBにしたものを再びBGRに戻して保存しないといけません。

cv2.imwrite("image_cap_rgb.jpg",,cv2.cvtColor(image_cap_rgb,cv2.COLOR_RGB2BGR))

画像をくっきりさせる

画像をぼやかす(blur)は、各ピクセルを近傍のピクセルの平均値に変換すること。くっきりさせるのはその逆という概念になります

一旦、とある本にあったカーネルを丸パクリしてみます

kernel = np.array([[0,-1,0],
                  [-1,5,-1],
                  [0,-1,0]])
image_cap_sharp1 = cv2.filter2D(image_cap_rgb,-1,kernel) 
plt.imshow(image_cap_sharp1)
plt.axis("off")
plt.show()

f:id:yyhhyy:20190811181807p:plain
output_17_0

cv2.imwrite("image_cap_sharp1.jpg",cv2.cvtColor(image_cap_sharp1,cv2.COLOR_RGB2BGR))

確かに表面の凸凹が実際より強調された気がしますが、なんかもっとわかりやすい例やピンボケ画像が必要ですね。1

ぼかしの逆をすればいいって目からウロコでしたが、知っている人からすれば当たり前のことなんでしょうか。個人的には凄く新鮮でした


  1. そもそも はてなブログにアップする時点で解像度も下がるので益々わからんですね。

GPUパソコンにtensorflow-gpuを入れたはなし

最近GeforceGPUが入ったPC(もはや名前はワークステーションでした)を買って、暫くずっと`tensorflow-gpuが入らない、と苦労していたのですが、

偶然検索したこちらの記事の通り、Anaconda Prompt に任せたら一瞬で適切なものをインストールしてくれました。

qiita.com

検索で最初にヒットするサイトの多くが自力でNVIDAドライバを入れさせる手法の紹介なのですが、今では上記の方法で充分です。

時代の変化は早いですね!

Pythonでアンケート調査のクラスター分析と決定木分析を行う

アンケート調査の分析をするのはマーケティング担当者で、恐らく大学時代は社会学や心理学といった文系出身だと思います。昔ならSPSS、最近ならRだと思います。

一方で、Pythonはどちらかというと情報学系の人やシステムエンジニアが使うツール(言語)でPythonでアンケート分析を真っ向からしている書籍は存外少ないものです。最近私はRからPythonへの全面的な移行を考えているのですが、備忘録も兼ねて、Pythonでアンケート調査を行ってみました。

事前準備・前処理

先ずは予め読み込んでおいた方が良いLibrary類をインポートしておきます。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

seabornのテーマをデフォルトで選ぶようにしておきます。

#sns.set()
sns.set(font="Noto Sans CJK JP")

データの準備

アンケート調査のクラスター分析のためには集計されたクロス表ではなく、その前のローデータ(0,1の回答レコードの行列)が必要なのですが、なかなか個票データを公開しているところがなく、UCIのサイトから拝借することにしました。1

http://archive.ics.uci.edu/ml/datasets.php

ある都市に対する満足度の評価です

http://archive.ics.uci.edu/ml/datasets/Somerville+Happiness+Survey

提供されているCSVファイルがWindows文字コードだったようですのでエンコードの指定をして読み込みます2

df = pd.read_csv("data/SomervilleHappinessSurvey2015_2.csv",
                 encoding="cp932",
                 header=0)
df.head(3)
D X1 X2 X3 X4 X5 X6
0 0 3 3 3 4 2 4
1 0 3 2 3 5 4 3
2 1 5 3 3 3 3 5

各設問についてはMCUのサイトに以下の補足があります。 今のままではわかりにくいので簡単な列名に変えたいと思います3

D = decision attribute (D) with values 0 (unhappy) and 1 (happy) 
X1 = the availability of information about the city services 
X2 = the cost of housing 
X3 = the overall quality of public schools 
X4 = your trust in the local police 
X5 = the maintenance of streets and sidewalks 
X6 = the availability of social community events 

Attributes X1 to X6 have values 1 to 5.
df = df.rename(columns={
    "D":"幸福かどうか",
    "X1":"行政サービス情報へのアクセスしやすさ",
    "X2":"住宅供給の高さ",
    "X3":"公立の学校の全般的な質の良さ",
    "X4":"地域警察への信頼の高さ",
    "X5":"道路・歩道のメンテナンス状況",
    "X6":"地域社会行事の利用のしやすさ"})
df.head(3)
幸福かどうか 行政サービス情報へのアクセスしやすさ 住宅供給の高さ 公立の学校の全般的な質の良さ 地域警察への信頼の高さ 道路・歩道のメンテナンス状況 地域社会行事の利用のしやすさ
0 0 3 3 3 4 2 4
1 0 3 2 3 5 4 3
2 1 5 3 3 3 3 5

何を分析するか?」をまだ決めていませんでしたが、例えば、「幸福度の高い人は市のどこを評価しているのか?」を探ることにしてみましょう。

その場合、「幸福だ」と答えているデータだけに絞る必要があります。

df_happy = df[df["幸福かどうか"]==1]
df_happy.head(3)
幸福かどうか 行政サービス情報へのアクセスしやすさ 住宅供給の高さ 公立の学校の全般的な質の良さ 地域警察への信頼の高さ 道路・歩道のメンテナンス状況 地域社会行事の利用のしやすさ
2 1 5 3 3 3 3 5
5 1 5 5 3 5 5 5
7 1 5 4 4 4 4 5

念のためにこれで何件あるか確認しましょう。あまりデータが少なすぎると分析の信憑性が下がります

len(df_happy)
77

また、幸福ではない人のデータは今回の分析では使わないので、「幸福かどうか」の列を削除します。

更に、インデックスの番号を新しいデータに対して振り直します。

df_happy = df_happy.drop("幸福かどうか", axis=1)
df_happy = df_happy.reset_index(drop=True)
df_happy.head(3)
行政サービス情報へのアクセスしやすさ 住宅供給の高さ 公立の学校の全般的な質の良さ 地域警察への信頼の高さ 道路・歩道のメンテナンス状況 地域社会行事の利用のしやすさ
0 5 3 3 3 3 5
1 5 5 3 5 5 5
2 5 4 4 4 4 5

データの標準化

5段階スケールできいている各設問について、「1」に集中しているものや、「5」に集中しているものがある筈です。4

df_happy.describe()
行政サービス情報へのアクセスしやすさ 住宅供給の高さ 公立の学校の全般的な質の良さ 地域警察への信頼の高さ 道路・歩道のメンテナンス状況 地域社会行事の利用のしやすさ
count 77.000000 77.000000 77.000000 77.000000 77.000000 77.000000
mean 4.545455 2.558442 3.415584 3.792208 3.831169 4.389610
std 0.679502 1.117958 1.004603 0.878660 1.056342 0.763576
min 3.000000 1.000000 1.000000 1.000000 1.000000 1.000000
25% 4.000000 2.000000 3.000000 3.000000 3.000000 4.000000
50% 5.000000 2.000000 3.000000 4.000000 4.000000 5.000000
75% 5.000000 3.000000 4.000000 4.000000 5.000000 5.000000
max 5.000000 5.000000 5.000000 5.000000 5.000000 5.000000

どの列も分散を等しくすることで、情報量を同じにできます。5

from sklearn import preprocessing
ss = preprocessing.StandardScaler()
df_happy_s = pd.DataFrame(ss.fit_transform(df_happy))
df_happy_s.head(3)
0 1 2 3 4 5
0 0.673326 0.397559 -0.416393 -0.907521 -0.791997 0.804625
1 0.673326 2.198267 -0.416393 1.383598 1.113745 0.804625
2 0.673326 1.297913 0.585552 0.238038 0.160874 0.804625

また列名が消えてしまいましたので振り直します。

幸い、先程の「df_happy」の列名をそのままコピペすればいいだけです

print(df_happy.columns)
Index(['行政サービス情報へのアクセスしやすさ', '住宅供給の高さ', '公立の学校の全般的な質の良さ', '地域警察への信頼の高さ',
       '道路・歩道のメンテナンス状況', '地域社会行事の利用のしやすさ'],
      dtype='object')
df_happy_s.columns = df_happy.columns
df_happy_s.head(3)
行政サービス情報へのアクセスしやすさ 住宅供給の高さ 公立の学校の全般的な質の良さ 地域警察への信頼の高さ 道路・歩道のメンテナンス状況 地域社会行事の利用のしやすさ
0 0.673326 0.397559 -0.416393 -0.907521 -0.791997 0.804625
1 0.673326 2.198267 -0.416393 1.383598 1.113745 0.804625
2 0.673326 1.297913 0.585552 0.238038 0.160874 0.804625

分析

因子分析

設問が多岐に渡るときは、設問を縮約すべきで、そういうときは因子分析を行いますが、今回は既に6問と少ないので行いません。

↓自分がやるまでもなく丁寧にまとめられた記事がありました。因子得点は fit_transformで出てきます。また、事前に自分で標準化していないと変な値になるので因子分析だけをするときも注意して下さい。

hk29.hatenablog.jp

scikit-learn.org

クラスター分析

「幸福度の高い人は市のどこを評価しているのか?」を探ると言っても、人によって重視ポイントが異なります。それを幾つかにタイプ分けをして把握するためにクラスター分析を行います

適切なクラスター数を探す

適切なクラスター数を決める手法に完全な決まりはありませんが、見た目で判断するために、階層的クラスタ分析を一度行うという手法がママあります。

from scipy.cluster.hierarchy import linkage, dendrogram

統計に細かい人にとってはどの手法を選ぶか?は重要だと思いますが、比較的一般的なユークリッド距離、ウォード法で階層的クラスター分析を行います

df_happy_s_hclust = linkage(df_happy_s,metric="euclidean",method="ward")
plt.figure(figsize=(12,8))
dendrogram(df_happy_s_hclust)
plt.savefig('figure_1.png')
plt.show()

f:id:yyhhyy:20190707224159p:plain

どの辺りで線をひくか?はかなり恣意的ですが、この場合は4グループぐらいが適切でしょうか?

f:id:yyhhyy:20190707224239p:plain

k平均法によるクラスター分析

実務においてはクラスターに分かれればそれでいいというわけではなく、解釈の容易性が求められます。それぞれのクラスターにはどういう違いがあるのか?そういったことを知るには、kmenasクラスター分析の方が便利です

from sklearn.cluster import KMeans

クラスター数を4つと決めたので引数に入れて関数を作成

km = KMeans(n_clusters=4,random_state=42)

skit-learnは直接pandasデータフレームを読み込まないのでnumpyの行列に変換する必要がある

df_happy_s_ar = df_happy_s.values
display(df_happy_s_ar)
array([[ 0.67332598,  0.39755886, -0.41639284, -0.90752108, -0.79199672,
         0.80462467],
       [ 0.67332598,  2.19826665, -0.41639284,  1.38359771,  1.11374539,
         0.80462467],
       [ 0.67332598,  1.29791276,  0.58555244,  0.23803832,  0.16087433,
         0.80462467],
       [ 0.67332598, -0.50279503,  0.58555244,  1.38359771,  1.11374539,
         0.80462467],
       [-2.28930833, -0.50279503,  0.58555244, -0.90752108,  0.16087433,
        -0.51359022],
       [ 0.67332598, -1.40314893,  0.58555244, -0.90752108,  0.16087433,
         0.80462467],
       [-0.80799118, -0.50279503, -0.41639284, -0.90752108,  0.16087433,
        -0.51359022],
       [-0.80799118, -0.50279503, -0.41639284, -0.90752108,  0.16087433,
        -0.51359022],
       [ 0.67332598, -1.40314893, -1.41833812,  1.38359771, -1.74486778,
        -0.51359022],
       [-0.80799118,  0.39755886, -0.41639284, -0.90752108, -0.79199672,
        -0.51359022],
       [-2.28930833,  0.39755886, -0.41639284,  1.38359771,  1.11374539,
         0.80462467],
       [-2.28930833,  0.39755886, -2.4202834 , -0.90752108, -0.79199672,
        -0.51359022],
       [-2.28930833,  0.39755886, -2.4202834 , -0.90752108, -0.79199672,
        -0.51359022],
       [ 0.67332598,  0.39755886, -0.41639284, -0.90752108,  1.11374539,
        -1.83180511],
       [-2.28930833, -0.50279503,  0.58555244,  0.23803832,  0.16087433,
         0.80462467],
       [-2.28930833, -0.50279503,  0.58555244,  0.23803832,  0.16087433,
         0.80462467],
       [-0.80799118, -1.40314893, -0.41639284, -3.19863987, -2.69773883,
        -0.51359022],
       [ 0.67332598,  0.39755886,  0.58555244, -0.90752108,  0.16087433,
         0.80462467],
       [ 0.67332598,  0.39755886,  0.58555244, -0.90752108,  0.16087433,
         0.80462467],
       [ 0.67332598, -0.50279503, -0.41639284, -0.90752108, -1.74486778,
         0.80462467],
       [-0.80799118,  1.29791276, -0.41639284,  0.23803832, -1.74486778,
        -0.51359022],
       [-0.80799118, -0.50279503,  0.58555244, -0.90752108, -1.74486778,
        -0.51359022],
       [-2.28930833, -1.40314893, -1.41833812,  0.23803832, -0.79199672,
         0.80462467],
       [ 0.67332598,  0.39755886,  0.58555244,  0.23803832,  0.16087433,
         0.80462467],
       [ 0.67332598,  0.39755886, -0.41639284,  0.23803832,  0.16087433,
         0.80462467],
       [ 0.67332598, -0.50279503,  1.58749772,  1.38359771,  1.11374539,
        -1.83180511],
       [ 0.67332598, -1.40314893, -0.41639284, -0.90752108,  0.16087433,
        -0.51359022],
       [ 0.67332598, -1.40314893, -0.41639284, -0.90752108,  0.16087433,
        -0.51359022],
       [ 0.67332598, -1.40314893, -0.41639284, -0.90752108,  0.16087433,
        -0.51359022],
       [ 0.67332598, -0.50279503,  0.58555244, -0.90752108,  0.16087433,
         0.80462467],
       [ 0.67332598, -0.50279503,  0.58555244, -0.90752108,  0.16087433,
         0.80462467],
       [-0.80799118,  0.39755886, -1.41833812,  0.23803832, -0.79199672,
        -0.51359022],
       [-0.80799118,  0.39755886, -1.41833812,  0.23803832, -0.79199672,
        -0.51359022],
       [ 0.67332598,  0.39755886,  1.58749772,  1.38359771,  0.16087433,
         0.80462467],
       [ 0.67332598, -0.50279503,  0.58555244, -2.05308048, -1.74486778,
        -0.51359022],
       [-0.80799118,  1.29791276, -0.41639284, -0.90752108, -1.74486778,
         0.80462467],
       [ 0.67332598, -0.50279503,  0.58555244,  0.23803832,  1.11374539,
         0.80462467],
       [ 0.67332598, -0.50279503,  0.58555244,  0.23803832,  1.11374539,
         0.80462467],
       [-0.80799118, -1.40314893, -0.41639284,  0.23803832,  0.16087433,
        -0.51359022],
       [-0.80799118, -1.40314893, -0.41639284,  0.23803832,  0.16087433,
        -0.51359022],
       [ 0.67332598, -1.40314893,  1.58749772,  1.38359771,  1.11374539,
         0.80462467],
       [ 0.67332598,  1.29791276,  1.58749772,  1.38359771,  1.11374539,
         0.80462467],
       [-0.80799118, -0.50279503, -1.41833812,  0.23803832,  0.16087433,
         0.80462467],
       [-0.80799118,  0.39755886, -0.41639284,  0.23803832, -0.79199672,
        -0.51359022],
       [-0.80799118,  0.39755886, -0.41639284,  0.23803832, -1.74486778,
        -0.51359022],
       [ 0.67332598, -0.50279503,  1.58749772,  1.38359771,  1.11374539,
         0.80462467],
       [ 0.67332598, -0.50279503, -0.41639284,  1.38359771,  1.11374539,
         0.80462467],
       [ 0.67332598,  2.19826665,  1.58749772,  1.38359771,  1.11374539,
         0.80462467],
       [-0.80799118,  0.39755886, -1.41833812,  0.23803832,  0.16087433,
        -0.51359022],
       [-0.80799118, -0.50279503,  0.58555244,  0.23803832,  0.16087433,
        -0.51359022],
       [ 0.67332598,  0.39755886, -1.41833812,  0.23803832,  0.16087433,
        -0.51359022],
       [ 0.67332598, -0.50279503, -0.41639284,  0.23803832,  0.16087433,
         0.80462467],
       [ 0.67332598,  0.39755886, -0.41639284, -0.90752108,  1.11374539,
         0.80462467],
       [ 0.67332598, -0.50279503,  0.58555244,  1.38359771,  0.16087433,
         0.80462467],
       [ 0.67332598, -1.40314893, -0.41639284,  0.23803832,  1.11374539,
         0.80462467],
       [ 0.67332598,  1.29791276,  1.58749772,  1.38359771,  1.11374539,
        -0.51359022],
       [ 0.67332598,  2.19826665, -0.41639284,  0.23803832,  0.16087433,
         0.80462467],
       [ 0.67332598,  1.29791276,  0.58555244, -0.90752108, -0.79199672,
        -0.51359022],
       [ 0.67332598,  1.29791276,  0.58555244,  0.23803832,  0.16087433,
         0.80462467],
       [ 0.67332598,  2.19826665,  1.58749772,  1.38359771,  1.11374539,
         0.80462467],
       [ 0.67332598,  1.29791276,  1.58749772,  0.23803832,  1.11374539,
        -0.51359022],
       [-0.80799118, -0.50279503, -0.41639284,  0.23803832, -0.79199672,
        -1.83180511],
       [ 0.67332598, -0.50279503, -1.41833812,  0.23803832,  1.11374539,
         0.80462467],
       [ 0.67332598,  0.39755886, -1.41833812,  0.23803832,  0.16087433,
         0.80462467],
       [ 0.67332598,  0.39755886,  0.58555244, -0.90752108,  0.16087433,
        -1.83180511],
       [-0.80799118,  0.39755886,  0.58555244,  0.23803832, -0.79199672,
        -0.51359022],
       [ 0.67332598, -1.40314893,  0.58555244, -0.90752108,  1.11374539,
         0.80462467],
       [ 0.67332598, -1.40314893,  1.58749772, -0.90752108,  1.11374539,
         0.80462467],
       [ 0.67332598,  0.39755886,  0.58555244,  0.23803832,  0.16087433,
        -0.51359022],
       [ 0.67332598, -0.50279503,  0.58555244,  0.23803832, -1.74486778,
        -1.83180511],
       [-2.28930833,  1.29791276,  0.58555244,  1.38359771, -2.69773883,
        -1.83180511],
       [ 0.67332598, -1.40314893,  1.58749772,  1.38359771,  1.11374539,
         0.80462467],
       [-0.80799118,  0.39755886, -0.41639284,  0.23803832,  0.16087433,
        -0.51359022],
       [ 0.67332598,  2.19826665, -2.4202834 , -3.19863987,  1.11374539,
        -4.4682349 ],
       [ 0.67332598, -0.50279503, -0.41639284,  0.23803832,  0.16087433,
        -1.83180511],
       [ 0.67332598, -0.50279503, -0.41639284,  0.23803832, -1.74486778,
         0.80462467],
       [ 0.67332598,  0.39755886, -0.41639284,  0.23803832,  0.16087433,
         0.80462467]])

kmeansを適用した結果のグルーピングの配列が出力として渡される

df_happy_s_ar_pred = km.fit_predict(df_happy_s_ar)
display(df_happy_s_ar_pred)
array([3, 3, 3, 0, 1, 0, 2, 2, 1, 1, 1, 1, 1, 2, 1, 1, 2, 3, 3, 2, 1, 2,
       1, 3, 3, 0, 2, 2, 2, 0, 0, 1, 1, 3, 2, 1, 0, 0, 2, 2, 0, 3, 1, 1,
       1, 0, 0, 3, 1, 1, 2, 0, 3, 0, 0, 3, 3, 3, 3, 3, 3, 2, 0, 3, 2, 1,
       0, 0, 3, 2, 1, 0, 1, 2, 2, 1, 3])

割り振られた結果を元の(標準化する前の)データにクラスターIDとして一列追加する

df_happy_clust = df_happy[:]
df_happy_clust["cluster_ID"] = df_happy_s_ar_pred
df_happy_clust.head(3)
行政サービス情報へのアクセスしやすさ 住宅供給の高さ 公立の学校の全般的な質の良さ 地域警察への信頼の高さ 道路・歩道のメンテナンス状況 地域社会行事の利用のしやすさ cluster_ID
0 5 3 3 3 3 5 3
1 5 5 3 5 5 5 3
2 5 4 4 4 4 5 3

因みにこのままではクラスターIDも数値として扱われてしまいます

print(df_happy_clust.dtypes)
行政サービス情報へのアクセスしやすさ    int64
住宅供給の高さ               int64
公立の学校の全般的な質の良さ        int64
地域警察への信頼の高さ           int64
道路・歩道のメンテナンス状況        int64
地域社会行事の利用のしやすさ        int64
cluster_ID            int32
dtype: object

カテゴリカル変数に変えておきましょう

df_happy_clust["cluster_ID"] = df_happy_clust["cluster_ID"].astype("category")
print(df_happy_clust.dtypes)
行政サービス情報へのアクセスしやすさ       int64
住宅供給の高さ                  int64
公立の学校の全般的な質の良さ           int64
地域警察への信頼の高さ              int64
道路・歩道のメンテナンス状況           int64
地域社会行事の利用のしやすさ           int64
cluster_ID            category
dtype: object

クラスタが何人くらいになったのかを把握しておきます

print(df_happy_clust["cluster_ID"].value_counts())
1    22
3    20
2    18
0    17
Name: cluster_ID, dtype: int64

まぁまぁまどのクラスターも同じ人数ですね

クラスターの分析

クラスターでグルーピングし、回答の片寄りがどこに出たのか?を確認します。

今回は、間隔尺度の設問でしたので単純に平均するのは乱暴ですので、踏まえて更にデータを再構築する必要があります。

df_happy_clust = df_happy_clust[:].astype("category")
print(df_happy_clust.dtypes)
行政サービス情報へのアクセスしやすさ    category
住宅供給の高さ               category
公立の学校の全般的な質の良さ        category
地域警察への信頼の高さ           category
道路・歩道のメンテナンス状況        category
地域社会行事の利用のしやすさ        category
cluster_ID            category
dtype: object

ダミー変数化したい列を指定するために列名を取得してリスト化、更にクラスタIDを除きます

dummy_list = list(df_happy_clust.columns)[0:-1]
print(dummy_list)
['行政サービス情報へのアクセスしやすさ', '住宅供給の高さ', '公立の学校の全般的な質の良さ', '地域警察への信頼の高さ', '道路・歩道のメンテナンス状況', '地域社会行事の利用のしやすさ']

ダミー変数化したい列名を指定して全ての設問をダミー変数化します6

df_happy_clust_dmy = pd.get_dummies(df_happy_clust,columns=dummy_list)
df_happy_clust_dmy.head(3)
cluster_ID 行政サービス情報へのアクセスしやすさ_3 行政サービス情報へのアクセスしやすさ_4 行政サービス情報へのアクセスしやすさ_5 住宅供給の高さ_1 住宅供給の高さ_2 住宅供給の高さ_3 住宅供給の高さ_4 住宅供給の高さ_5 公立の学校の全般的な質の良さ_1 ... 地域警察への信頼の高さ_5 道路・歩道のメンテナンス状況_1 道路・歩道のメンテナンス状況_2 道路・歩道のメンテナンス状況_3 道路・歩道のメンテナンス状況_4 道路・歩道のメンテナンス状況_5 地域社会行事の利用のしやすさ_1 地域社会行事の利用のしやすさ_3 地域社会行事の利用のしやすさ_4 地域社会行事の利用のしやすさ_5
0 3 0 0 1 0 0 1 0 0 0 ... 0 0 0 1 0 0 0 0 0 1
1 3 0 0 1 0 0 0 0 1 0 ... 1 0 0 0 0 1 0 0 0 1
2 3 0 0 1 0 0 0 1 0 0 ... 0 0 0 0 1 0 0 0 0 1

3 rows × 28 columns

クラスタIDでグループ化し数値を集約します

df_happy_clust_dmy_gp = df_happy_clust_dmy.groupby("cluster_ID")

グループ別に各設問の回答者数の合計を出します

df_happy_clust_dmy_gp_g = df_happy_clust_dmy_gp.sum().T
display(df_happy_clust_dmy_gp_g)
cluster_ID 0 1 2 3
行政サービス情報へのアクセスしやすさ_3 0 8 0 0
行政サービス情報へのアクセスしやすさ_4 0 12 7 0
行政サービス情報へのアクセスしやすさ_5 17 2 11 20
住宅供給の高さ_1 6 2 6 0
住宅供給の高さ_2 11 6 8 0
住宅供給の高さ_3 0 11 3 10
住宅供給の高さ_4 0 3 0 6
住宅供給の高さ_5 0 0 1 4
公立の学校の全般的な質の良さ_1 0 2 1 0
公立の学校の全般的な質の良さ_2 1 6 1 1
公立の学校の全般的な質の良さ_3 3 8 12 6
公立の学校の全般的な質の良さ_4 8 6 4 7
公立の学校の全般的な質の良さ_5 5 0 0 6
地域警察への信頼の高さ_1 0 0 2 0
地域警察への信頼の高さ_2 0 0 1 0
地域警察への信頼の高さ_3 5 5 9 5
地域警察への信頼の高さ_4 5 14 6 9
地域警察への信頼の高さ_5 7 3 0 6
道路・歩道のメンテナンス状況_1 0 1 1 0
道路・歩道のメンテナンス状況_2 0 5 4 0
道路・歩道のメンテナンス状況_3 0 8 1 2
道路・歩道のメンテナンス状況_4 5 7 10 11
道路・歩道のメンテナンス状況_5 12 1 2 7
地域社会行事の利用のしやすさ_1 0 0 1 0
地域社会行事の利用のしやすさ_3 1 1 5 0
地域社会行事の利用のしやすさ_4 0 14 11 4
地域社会行事の利用のしやすさ_5 16 7 1 16

最近読んだ本で知ったのですが、Jupyter notebookであれば、HTML上で各セルに棒グラフを入れたり、数値によって色をつけたりしてくれるので、是非活用したほうが良いです。

pandas.pydata.org

df_happy_clust_dmy_gp_g.style.bar(color="#4285F4")
cluster_ID 0 1 2 3
行政サービス情報へのアクセスしやすさ_3 0 8 0 0
行政サービス情報へのアクセスしやすさ_4 0 12 7 0
行政サービス情報へのアクセスしやすさ_5 17 2 11 20
住宅供給の高さ_1 6 2 6 0
住宅供給の高さ_2 11 6 8 0
住宅供給の高さ_3 0 11 3 10
住宅供給の高さ_4 0 3 0 6
住宅供給の高さ_5 0 0 1 4
公立の学校の全般的な質の良さ_1 0 2 1 0
公立の学校の全般的な質の良さ_2 1 6 1 1
公立の学校の全般的な質の良さ_3 3 8 12 6
公立の学校の全般的な質の良さ_4 8 6 4 7
公立の学校の全般的な質の良さ_5 5 0 0 6
地域警察への信頼の高さ_1 0 0 2 0
地域警察への信頼の高さ_2 0 0 1 0
地域警察への信頼の高さ_3 5 5 9 5
地域警察への信頼の高さ_4 5 14 6 9
地域警察への信頼の高さ_5 7 3 0 6
道路・歩道のメンテナンス状況_1 0 1 1 0
道路・歩道のメンテナンス状況_2 0 5 4 0
道路・歩道のメンテナンス状況_3 0 8 1 2
道路・歩道のメンテナンス状況_4 5 7 10 11
道路・歩道のメンテナンス状況_5 12 1 2 7
地域社会行事の利用のしやすさ_1 0 0 1 0
地域社会行事の利用のしやすさ_3 1 1 5 0
地域社会行事の利用のしやすさ_4 0 14 11 4
地域社会行事の利用のしやすさ_5 16 7 1 16

このままの表が表示できない環境のであれば、グラフ化する必要があります。

plt.figure(figsize=(12,8))
sns.clustermap(df_happy_clust_dmy_gp_g,cmap="viridis")
plt.savefig('figure_2.png')
plt.show()

f:id:yyhhyy:20190707224604p:plain

ただ、無理にグラフにするより、スプレッドシートに吐き出して条件付き書式で見た方が楽だとは思います

df_happy_clust_dmy_gp_g.to_csv("df_happy_clust_dmy_gp_g.csv")

クラスター分析で各クラスターについての説明を考えるのはいつも恣意的ですが、例えば以下のように分類できます。

  • クラスター「0」はどの項目にも高い評価で満足し、住宅も高くないと感じているようです。
  • クラスター「1」は学校の質は低いと思っていますが、市には満足をしている。
  • クラスター「2」は警察の質は低いと思っていますが、市には満足しています。
  • クラスター「3」は住宅は高いと感じていますが、市には満足している。

それぞれ幸福度は高いわけですから、評価が低い箇所についてはあまり気にしていないということと同義だと判断しました。もちろんこれ以外の着眼点で分けるのも良いでしょう。

決定木分析

最後に、仮に今後新しく取得されたアンケートデータから、各クラスターに回答者を振り分けたい。となれば、様々な機械学習手法が可能なのですが、一般的な人にも理解して貰いやすい、という意味では、決定木分析が良いのではないかと思います。

先ず先程のデータをラベルとデータとにわけ、どちらもnumpyの配列にします

y = np.array(df_happy_clust["cluster_ID"].values)
display(y)
array([3, 3, 3, 0, 1, 0, 2, 2, 1, 1, 1, 1, 1, 2, 1, 1, 2, 3, 3, 2, 1, 2,
       1, 3, 3, 0, 2, 2, 2, 0, 0, 1, 1, 3, 2, 1, 0, 0, 2, 2, 0, 3, 1, 1,
       1, 0, 0, 3, 1, 1, 2, 0, 3, 0, 0, 3, 3, 3, 3, 3, 3, 2, 0, 3, 2, 1,
       0, 0, 3, 2, 1, 0, 1, 2, 2, 1, 3], dtype=int64)
X = df_happy_clust.drop("cluster_ID",axis=1).values
display(X)
array([[5, 3, 3, 3, 3, 5],
       [5, 5, 3, 5, 5, 5],
       [5, 4, 4, 4, 4, 5],
       [5, 2, 4, 5, 5, 5],
       [3, 2, 4, 3, 4, 4],
       [5, 1, 4, 3, 4, 5],
       [4, 2, 3, 3, 4, 4],
       [4, 2, 3, 3, 4, 4],
       [5, 1, 2, 5, 2, 4],
       [4, 3, 3, 3, 3, 4],
       [3, 3, 3, 5, 5, 5],
       [3, 3, 1, 3, 3, 4],
       [3, 3, 1, 3, 3, 4],
       [5, 3, 3, 3, 5, 3],
       [3, 2, 4, 4, 4, 5],
       [3, 2, 4, 4, 4, 5],
       [4, 1, 3, 1, 1, 4],
       [5, 3, 4, 3, 4, 5],
       [5, 3, 4, 3, 4, 5],
       [5, 2, 3, 3, 2, 5],
       [4, 4, 3, 4, 2, 4],
       [4, 2, 4, 3, 2, 4],
       [3, 1, 2, 4, 3, 5],
       [5, 3, 4, 4, 4, 5],
       [5, 3, 3, 4, 4, 5],
       [5, 2, 5, 5, 5, 3],
       [5, 1, 3, 3, 4, 4],
       [5, 1, 3, 3, 4, 4],
       [5, 1, 3, 3, 4, 4],
       [5, 2, 4, 3, 4, 5],
       [5, 2, 4, 3, 4, 5],
       [4, 3, 2, 4, 3, 4],
       [4, 3, 2, 4, 3, 4],
       [5, 3, 5, 5, 4, 5],
       [5, 2, 4, 2, 2, 4],
       [4, 4, 3, 3, 2, 5],
       [5, 2, 4, 4, 5, 5],
       [5, 2, 4, 4, 5, 5],
       [4, 1, 3, 4, 4, 4],
       [4, 1, 3, 4, 4, 4],
       [5, 1, 5, 5, 5, 5],
       [5, 4, 5, 5, 5, 5],
       [4, 2, 2, 4, 4, 5],
       [4, 3, 3, 4, 3, 4],
       [4, 3, 3, 4, 2, 4],
       [5, 2, 5, 5, 5, 5],
       [5, 2, 3, 5, 5, 5],
       [5, 5, 5, 5, 5, 5],
       [4, 3, 2, 4, 4, 4],
       [4, 2, 4, 4, 4, 4],
       [5, 3, 2, 4, 4, 4],
       [5, 2, 3, 4, 4, 5],
       [5, 3, 3, 3, 5, 5],
       [5, 2, 4, 5, 4, 5],
       [5, 1, 3, 4, 5, 5],
       [5, 4, 5, 5, 5, 4],
       [5, 5, 3, 4, 4, 5],
       [5, 4, 4, 3, 3, 4],
       [5, 4, 4, 4, 4, 5],
       [5, 5, 5, 5, 5, 5],
       [5, 4, 5, 4, 5, 4],
       [4, 2, 3, 4, 3, 3],
       [5, 2, 2, 4, 5, 5],
       [5, 3, 2, 4, 4, 5],
       [5, 3, 4, 3, 4, 3],
       [4, 3, 4, 4, 3, 4],
       [5, 1, 4, 3, 5, 5],
       [5, 1, 5, 3, 5, 5],
       [5, 3, 4, 4, 4, 4],
       [5, 2, 4, 4, 2, 3],
       [3, 4, 4, 5, 1, 3],
       [5, 1, 5, 5, 5, 5],
       [4, 3, 3, 4, 4, 4],
       [5, 5, 1, 1, 5, 1],
       [5, 2, 3, 4, 4, 3],
       [5, 2, 3, 4, 2, 5],
       [5, 3, 3, 4, 4, 5]], dtype=object)
from sklearn import tree

決定木は永遠に細かくできます。仮の数字で段階を決めてしまいます。

dtree = tree.DecisionTreeClassifier(max_depth=4)
dtree = dtree.fit(X,y)

作ったモデルがどの程度の精度なのか?確認してみます

dtree_pred = dtree.predict(X)
display(dtree_pred)
array([3, 3, 3, 0, 1, 0, 2, 2, 0, 1, 1, 1, 1, 2, 1, 1, 2, 3, 3, 1, 1, 2,
       1, 3, 3, 0, 2, 2, 2, 0, 0, 1, 1, 3, 2, 1, 0, 0, 2, 2, 0, 3, 1, 1,
       1, 0, 0, 3, 1, 2, 2, 0, 3, 0, 0, 3, 3, 3, 3, 3, 3, 2, 0, 2, 2, 1,
       0, 0, 3, 2, 1, 0, 1, 2, 2, 1, 3], dtype=int64)

ラベルとどれくらいあっているか?正直、過学習してそうですが、今は面倒なのでこのまま進めます

sum(dtree_pred == y) / len(y)
0.948051948051948

jupyter notebookならgraphvizパッケージを使って可視化が可能です7

import pydotplus
from IPython.display import Image
from graphviz import Digraph

配列にした際に特徴量の名称が消えてしまっているので、列名を代入し、またクラスター名も数字のままだとエラーになるため、ラベルデータはastypeを使って文字列(string)に変更しておきます

dot_data = tree.export_graphviz(dtree,out_file=None,
                                feature_names = df_happy_clust.columns[0:-1],
                                class_names = y.astype("str"))

日本語を出力するにあたっては随分苦労しましたがこのブログが正解のようです。

mk-55.hatenablog.com

graph = pydotplus.graph_from_dot_data(dot_data)

graph.set_fontname('Noto Sans CJK JP')
for node in graph.get_nodes():
    node.set_fontname('Noto Sans CJK JP')
for e in graph.get_edges():
    e.set_fontname('Noto Sans CJK JP')

graph.write_png("dtree.png")
Image(graph.create_png())

f:id:yyhhyy:20190707224750p:plain

例えば、「行政サービス情報のアクセスしやすさ」が4.5以上つまり5で、「住宅供給の高さ」が2.5以上だとクラスター3だと判断されるわけですが、実際のデータと比べても特徴をよく表しています。

df_happy_clust_dmy[df_happy_clust_dmy["cluster_ID"]==3].sum().head(10)
cluster_ID              60.0
行政サービス情報へのアクセスしやすさ_3     0.0
行政サービス情報へのアクセスしやすさ_4     0.0
行政サービス情報へのアクセスしやすさ_5    20.0
住宅供給の高さ_1                0.0
住宅供給の高さ_2                0.0
住宅供給の高さ_3               10.0
住宅供給の高さ_4                6.0
住宅供給の高さ_5                4.0
公立の学校の全般的な質の良さ_1         0.0
dtype: float64

f:id:yyhhyy:20190707224827p:plain

分析からわかること

例えば、市であれば自分たちが改善できることできなことがあるはずで、対応できることを対応する場合、どのクラスタの市民に残って欲しいか?増えて欲しいか?を絞り込むことができます。

また、重点ターゲットとなるクラスタを決めたら、決定木のジャッジのポイントを元に訴求項目を作れば、どういう謳い文句が必要か?ということが自ずと決まってきます。

今回はアンケート項目が少なかったので行えませんでしたが、通常はクラスタ分析に使った質問以外にもっと多くの質問をしているはずなので、その質問で決定木分析を行うと

  • どんなメディアに接触しているのか?
  • どんな世代に多いのか?
  • どんなことに興味関心があるのか?

など、広告でターゲティングしやすいセグメントを元に決定木分析を行うこともできるでしょう


決定木以外にも通常のグラフで日本語が文字化けします。seabornを使う場合は、最初にフォントを指定しておけば大丈夫のようで、今のところ私もこれでうまく行っています

qiita.com


最近、Pythonの本がすごくわかりやすいものが増えてきました。今まではエンジニア出身の人の本が多かったですが、徐々にデータサイエンス側に向けて適切な分量の本が増えています

コンパクトにまとまっているけれどそれでも応用範囲が広い

こちらはドリルのような本でも知っていると便利

Pythonではないもの、データサイエンスにとって必要なものは何か?を丁寧に解説していて、この本は最初の頃に読んでおきたかったと思う。


  1. ここのサイト、機械学習に特化しているので、どういう目的で?どういうタイプのデータが欲しいか?をチェックしていくとデータの候補がセレクトされるんですよ!凄いですね。

  2. 因みにこのファイル文字コードが特殊でうまく読み込めず、一度Googleスプレッドシートで読み込んで再保存しています。

  3. 「cost_of_housing」と5段階スケールで聞いてますが、これは5の方が価格が高い、という意味ですかね?

  4. 片寄っていることの確認のため簡便にdescribeを使っていますが、そもそも間隔尺度は通常は平均などは意味がないので出しません。

  5. 学校のテストの偏差値と根本的な考えは同じです。みんなの点数が高かった数学の90点と、みんなの点数が低かった英語の90点とは平等に足すことはできません。

  6. drop_first=True」は今回は指定しない。

  7. Win10ではgraphivzのdot.exeをシステム環境PATHで通す必要があるようです。

機械学習屋と統計屋とデータサイエンティストの違いとは

統計学は最強の学問である」という本がブームになって既に何年も過ぎましたが、未だに「データサイエンティスト」界隈はポジショントークに溢れているようで、データサイエンスについて本を読もうとすると、データサイエンスブームに乗って我田引水しようとする2系統の間で彷徨わされてしまいます。

統計屋の場合

統計学出身の人が先ず進めるのは以下の本ではないでしょうか?

これはこれでとても勉強になる本なのですが、「早くデータサイエンスでビジネスに必要な判断をしてみたい!」というニーズを抱えている人にとっては随分と遠回りに感じます。

「同じことを100回試してみたら95回は”AとBに違いがある”と出るであろう。実際に違いがない可能性もあるし、違いがあるとは断言できないが可能性はとても高い」

学問的なことを言えばその通りかもしれませんが、ビジネスでは「意思決定」をしないといけないので、こんな歯切れの悪い言い方にこだわられても「じゃぁどうすりゃいいんだよ?」と怒られるだけでしょう。1

有意 - Wikipedia

機械学習屋の場合(あるいはAI人材)

一方でプログラミング系の人が勧めるのはこちらでしょう

これもこれで、機械学習(とくにディープラーニング)について知りたい人にとってとても分かり易い素晴らしい本ですが、これを読んだからといって何かの「意思決定」ができるようにはなりません。

「画像から猫か猫以外を分類できました!」

という報告を貰ったところで「で?新しい発見はないの?うちは新たに何に着目したら競合に勝てるの?」と怒られるだけでしょう。

勿論、自社サービスから離反しそうな人をディープラーニングで判断してクーポンを送りつける、といったことはできるでしょうが、ディープラーニングの最大の欠点は ブラックボックス になってしまうことで、自社サービスから離反しそうになった原因をつきとめることができないので、改善策は出てきません。

データを元に意思決定したい人は「データマイニング」から入った方が良いかもしれない

今の「データサイエンティストブーム」のターニングポイントは2つあって、1つは「ビッグデータブーム」で、もう一つは「ディープラーニングブーム」です。この2つのバズワードに乗っかるように「データサイエンス」という言葉が広まり始めました。

しかし「データを元に何かを発見し意思決定する」という行為自体は、データサイエンティストブームの前から存在していて、それは「データマイニング」と呼ばれていました。

どうしてもバズワードが生まれてしまうと乗っかる方が本も売れますし名前も売れますので、戦略的にバズワードに乗っかる人が出てしまいます。 [^1] その結果「データサイエンス」という言葉を見て本を手に取ったときに、「やたらp値にこだわる統計屋」と「何でもディープラーニングで済ませようとする機械学習屋」 の本にあたってしまい、求めていた情報と違うものに出会って期待外れを感じてしまうのです。

データマイニング」目的で「データサイエンス」を学びたい人に

データマイニング目的で学ぶべき統計学機械学習のポイントは、「起きている現象について分析し意思決定できるか?」という点です。その意味では、全ての統計学機械学習を学ぶ必要はなく、以下のようなラインナップがあれば充分です。

要因のうち何が重要かわかる

  • 線形回帰分析
  • 重回帰分析
  • ロジスティック回帰分析
  • 決定木

データの把握をしやくする

  • EDA(探索的データ分析)/或いは、記述統計
  • 因子分析
  • k平均法によるクラスター分析
  • コレスポンデンス分析

A/Bテストのパターンを決める

  • 実験計画法

どの程度の効果があるか?を探る

時系列の取り扱い

  • 状態空間モデル
  • 移動平均・自己回帰(ARIMA)

お勧めの本

上記のようなポイントを抑えて優先順位をつけて学びたい場合、以下のような本が良いでしょう。

ライトな本

先ず「線形回帰分析」ができるだけでもエクセルの棒グラフ地獄から抜け出せます。もちろん、どこかで次の一歩としてロジスティック回帰分析が必要になってきますが、最初はこれだけでも良いでしょう。

一通りしっかり知りたい人はこの本が今のところとてもバランスよくまとまっています。一つ一つの説明は少ないので、細かいことは別の本を読まないといけないかもしれませんが、自分にとって何が重要で何が重要でないか?の指南はこの本に頼れば良いと思います。とても良い本です。


  1. 定義が曖昧な「人工知能」などという言葉を学者は使うべきではないと思いますが、もはやその方が通りが良いので、機械学習の人も人工知能強化学習の人も人工知能と言ってしまいがち、というのも同じ現象です。

本当のところヒットなんて誰にもわからない

先日読んだ本で積年の疑問が解決しました。

ゴッホが有名な印象派になれた理由

この本は様々なヒットについて解説がありました。中でも最も面白かったのは、「カイユボット事件」1です。

印象派の売れなかった作品を買ってあげていたカイユボットは、死後、遺産を美術館に寄贈してくれ、という遺言をし、ルノワールが尽力して彼の作品群を収めました。

当時、芸術的価値を世間一般に認められていなかった印象派のこの事件はメディアを通じて話題になり、「物議をかます印象派の作品とはどういうものだろうか?」と多くの人が訪れ、結果的にこのとき収蔵された作家群が、後々の印象派の主要人物だった、と認識されるようになるのです。

たまたま売れない作品の多かったゴッホの作品はカイユボットの遺産に含まれていたため、後世で評価されるに至ったのです。

ジャスティンビーバーがツイートしたら売れる

ヒットさせる最も簡単な方法は「ジャスティンビーバーにツイートしてもらう」ということ。かつてそれで一躍有名になった動画がありました。

shiba710.hateblo.jp

ゴッホがメディアを通じて有名になったのと同様。「既に伝播力のある誰かに紹介して貰う」ことがヒットに欠かせないポイントです。

例えば、アートのオークション市場でも有名なコレクターが評価した新人作家が評価され価格が上がっていくメカニズムです。

ヒットとはアンコントローラブル

狙ってジャスティンビーバーに紹介して貰えたりテイラー・スウィフトに賛同して貰えたらそれほど嬉しいことはありませんが、実際には意図的にできることではありません。

お墨付きと露出の繰り返し

しかし、人は社会的な生物なので、権威のある人のお墨付きに流れるということは永遠に続く法則ではないでしょうか?

フェラーリは創業以来広告をしていない」という都市伝説がありますが、彼らはF1のスポンサードをして社名の露出には余念がありませんし、雑誌の試乗レポートのお願いは常にしているようです。同じ車メーカーでも高級車メーカーは沢山ありますが、フェラーリは通常の広告枠とは違う形で効果的に宣伝費を投下しているということです。

IT企業が野球に投資する理由

同じように「ソフトバンク」という企業は知名度がありますが、ソフトバンクの本業が何なのか?わかる人はいないのではないでしょうか?携帯通信キャリアとしてはヴォーダフォンの国内事業を買収してからなのでその前から本業があるはずなのですが、、、プロ野球球団運営を始めてから知名度があがり、何ものなのか?わからなくても親近感があるのではないでしょうか?

どの本に書いてあったか忘れましたがいくつか読んだスポーツ関連のビジネス本に記載がありました。球団経営は大きな宣伝になるもののあくまで子会社の事業ということになるので宣伝費(販売管理費)として費用計上されない、というメリットがあるそうです。なぜソフトバンクDeNA楽天も球団を買うのか謎でしたが、会計知識がある人にとっては合理的な行為に写るのでしょう。


アートのオークション市場のメカニズムについてはこちら。

何度も紹介しますが車メーカーのブランディングについてはこの本が本当に腑に落ちます。


  1. 事件そのものについてはこのサイトが参考になります。(印象派支援、support as patron|カイユボット.net