注目の投稿

【kepler.gl】コロナ対策による人流の変化も地図上に可視化(各種メディアで報道)

kepler.glのサイト画面 kepler.glを使ってコロナ対策の効果を分析したところ、テレビ、新聞、ネットのメディアから問い合わせや報道依頼が殺到。今も、土日返上で都内や全国の人流変化を分析しています。この記事では人流変化の可視化に便利なkepler.glにつ...

2016年10月2日日曜日

【統計】ABテストの結果を電卓で計算できるぐらい簡単に統計的に判断する方法(二項分布、正規分布近似)


  • まず、ABテストとは?
例えば、WEBページに設置するバナーのクリック数を上げたい時、左側に設置するのが良いのか右側に設置するのが良いのか悩むことがあります。この悩みを解決するために、どっちのバナーが良いのかを検証することをABテストと呼びます。詳しくは、「A/Bテスト(英: A/B testing)とは、主にインターネットマーケティングで行われる、施策判断のための試験の総称である。」をご参照。


  • 優劣を正しく判断するために統計を使う必要がある
左側に設置するAバナー、右側に設置するBバナーがあるとき、それぞれのクリック数の平均値で比較することもできます。ただ、単純な平均値の比較だと、どちらも本当の効果は同じにも関わらず、偶然的に生じた平均値の違いで優劣を判断してしまう場合があります。そこで、統計をうまく使い、優劣を正しく判断することが求められます。


  • 二項分布を仮定すると期待値、分散、標準偏差を簡単に計算できる
統計的に優劣を判断するには、一般的に期待値や分散、標準偏差を求める必要がありますが通常手計算では求めることが難しいです。ただ、二項分布を仮定すると、簡単に分散等を計算することが可能です。ちなみに、二項分布は、結果が成功か失敗のいずれかである n 回の独立な試行を行ったときの成功数で表される離散確率分布です。そして、二項分布の期待値と分散は定義により容易に求めることができます。例えば、nを試行回数、pを成功確率とするとき、期待値はnp、分散はnp(1-p)、標準偏差は√np(1-p)となります。


  • 実際に二項分布を仮定して期待値、分散、標準偏差を計算
実際に計算してみます。AバナーとBバナーがあるとします。何れもN=100,000です。クリック率は、Aバナー:Pa=0.001、Bバナー:Pb=0.0011です。ここで、A{N, Pa}とB{N, Pb}は何れも二項分布に従うと仮定します。期待値は、Aバナー:Ea=NPa=100、Bバナー:Eb=NPb=110です。分散は、Aバナー:Va=NPa(1-Pa))=99.9、Bバナー:Vb=NPb(1-Pb))≈109.9,です。標準偏差は、Aバナー:Sa=√NPa(1-Pa)≈10.0、Bバナー:Sb=√NPb(1-Pb)≈10.5です。


  • 仮説検定をおこなう
上記で計算したところ、Ea>Ebとなり、Aバナーの期待値が大きいことが分かりました。しかし、これは偶然の結果であり、AバナーとBバナーの間には本質的な違いはないかもしれません。そこで、仮説検定をおこないます。今回は、AバナーとBバナーは同等であるという帰無仮説を設定します。ここで、Bバナーがクリックされた回数、すなわち期待値Ebが二項分布(と仮定する)A{N, Pa}に従うとするとき、ラプラスの定理により(Eb-Ea)/(Sa)は近似的に標準正規分布に従うことを導けます。これを用いて計算すると、(Eb-Ea)/(Sa))≈1.0になります。標準正規分布の95%信頼区間は、-1.96~1.96なので1.0はその範囲内に入ります。そのため、帰無仮説を棄却できない、すなわち、本質的に違いがないことを否定できません。つまり、期待値で見ればEa>Ebですが、統計的な差はないと言えます。


  • 統計的に差がないことが分かったら
残念。と落胆する必要はありません。今回の結果、バナーを左側に設置するか右側に設置するかでは、何れも効果が同じであるという事実が分かりました。そのため、この事実を活かした施策が提案可能になります。また、設置場所によって効果の差がでないことが分かったので、今後、左右のバナーでクリック率等の違いが出た際は、他の要因、例えば、バナーのクリエイティブで差が出たと推定することが可能です。

  • 組み合わせの問題
今回は、組み合わせによる問題は考慮していません。つまり、本来は左側の方がクリック率が高いにも関わらず、右側でクリック率が高まるクリエイティブを選んでしまい実験した結果、それが丁度良い具合に補正されて、偶然にも左右同等の結果になってしまった可能性があるということです。このような問題を回避するには、単一のクリエイティブの影響を小さくするために複数のクリエイティブで実験する等の対策をおこなう必要があると考えられます。


参考サイト




2016年9月30日金曜日

【トレジャーデータ】SQL:複数のテーブル間で重複するユーザ数の日時の推移を集計する

前回、【トレジャーデータ】WITH句を使って複雑なSQLの可読性・効率性を改善
前々回、【トレジャーデータ】JOINを使って複数のテーブルからデータ抽出する複雑なSQLを書く
では、記事別に自社サイトUU、自社サイト×他社サイト重複UU、自社サイト×購買者重複UUを集計するSQLを書いた。

今回は、下記のような結果をイメージしてSQLを書いてみる。

日にち自社サイトUU自社サイトと他社サイトの重複UU自社サイトと購買者の重複UU
2016-06-011000012020
2016-06-021200013030
2016-06-031500020015

テーブルとしては、
  • 自社サイトログ:mysite_log(time, article_name, user_id)
  • 他社サイトログ:othersite_log(time, article_name, user_id)
  • 販売ログ:order_log(time, item_id, order_user_id)
  • 商品ログ:item_info(time, item_id, brand_name)
があるとする。
ここで、販売ログは、order_user_idという独自のuser_idを持つ。そのため、
  • id照合テーブル:userid_matching(user_id, order_user_id)
をうまく用いることが必要になる。
また、今回、自社サイトと購買者の重複UUは特定のブランドのみで抽出することも条件に入れて集計している。

SQLのコードは下記の通り、


WITH mysite_with AS(
  -- 自社サイトUU
  SELECT
    td_time_format(time,
                 'yyyy-MM-dd',
                 'jst') AS days,
    user_id
  FROM
    log_db.mysite_log
  WHERE
    AND TD_TIME_RANGE(time,
                    '2016-06-01',
                    '2016-09-01',
                    'JST')
  GROUP BY
    td_time_format(time,
                 'yyyy-MM-dd',
                 'jst'),
    user_id
),
othersite_with AS(
  -- 自社サイトと他サイトの重複UU
  SELECT
    td_time_format(time,
                 'yyyy-MM-dd',
                 'jst') AS days,
    user_id
  FROM
    log_db.othersite_log
  WHERE
    TD_TIME_RANGE(time,
                '2016-06-01',
                '2016-09-01',
                'jst')
  GROUP BY
    td_time_format(time,
                 'yyyy-MM-dd',
                 'jst'),
    user_id
),
order_with AS(
  -- 自社サイトと販売データの重複uu
  SELECT
    order_log.days,
    match.user_id
  FROM (
    SELECT
      user_id,
      order_user_id
    FROM
      log_db.userid_matching--サイト側user_idと販売側order_user_idを称号するテーブル
  ) match
  JOIN (
    SELECT
      td_time_format(time,
                   'yyyy-MM-dd',
                   'jst') AS days,
      order_user_id
    FROM
      log_db.order_log
    WHERE
      TD_TIME_RANGE(time,
                  '2016-06-01',
                  '2016-09-01',
                  'jst')
      AND item_id IN(
        SELECT
          item_id
        FROM
          info_db.item_info -- ブランド名などのアイテム情報が格納されたテーブル
        WHERE
          brand_name = 'xxx' -- ブランド名を指定する場合
    )
    GROUP BY
      td_time_format(time,
                   'yyyy-MM-dd',
                   'jst'),
      order_user_id
  ) order_log
  ON (
    match.order_user_id = order_log.order_user_id
  )
) SELECT
  mysite.days,
  MAX(mysite.uu) AS mysite_uu,
  MAX(wp.wp_uu) AS wp_uu,
  MAX(ord.ord_uu) AS order_uu
FROM (
  SELECT
    days,
    COUNT(DISTINCT imid) AS uu
  FROM
    mysite_with
  GROUP BY
    days
) mysite LEFT
JOIN (
  SELECT
    mysite_with_othersite.days,
    COUNT(DISTINCT mysite_with_othersite.user_id) AS othersite_uu
  FROM (
    SELECT
      days,
      user_id
    FROM
      mysite_with
  ) mysite_with_othersite
  JOIN (
    SELECT
      days,
      user_id
    FROM
      othersite_with
  ) othersite_with_1
  ON (
    mysite_with_othersite.days = othersite_with_1.days
    AND mysite_with_othersite.user_id = othersite_with_1.user_id
  )
  GROUP BY
    mysite_with_othersite.days
) othersite
ON (
  mysite.days = othersite.days
) LEFT
JOIN (
  SELECT
    mysite_with_order.days,
    COUNT(DISTINCT mysite_with_order.user_id) AS ord_uu
  FROM (
    SELECT
      days,
      user_id
    FROM
      mysite_with
  ) mysite_with_order
  JOIN (
    SELECT
      days,
      user_id
    FROM
      order_with
  ) order_with_order
  ON (
    mysite_with_order.days = order_with_order.days
    AND mysite_with_order.imid = order_with_order.imid
  )
  GROUP BY
    mysite_with_order.days
) order
ON (
  mysite.days = order.days
)
GROUP BY
  mysite.days
ORDER BY
  mysite.days


以前、WITH句を使わないで複数のテーブルをJOINさせた無駄に複雑なSQLを書いていたのだが、もし、今回もWITH句を使わずに書いていたらものすごく可読性の悪いSQLになっていただろう。


2016年9月29日木曜日

【トレジャーデータ】任意の期間のデータを消す

トレジャーデータは、例えば、「user_idカラムにxxxが含むレコードを削除」ということが仕様上できない。では、データを削除したいときはどうするのか?できないのか?

もちろん、可能である。データ作成日(time)を指定して、任意の期間のレコード削除することができる。ただし、削除の最小単位は1時間。

具体的な方法は以下の通り

1.まずはTreasure Dataにログイン

$ td -e https://api.treasuredata.com account -f

でログインする。
Emailとパスワードを入力すればOK。

2.partial_deleteを使って削除

$ td table:partial_delete example_db table1 --from '2016-01-01 JST' --to '2016-01-02 JST'

以上

  • 参照サイト
TreasureData上の過去データを削除する


2016年9月28日水曜日

【マーケティング】TVに取り上げられてもバズらなかった事例

今日はソーシャルメディアマーケティングの勉強会に参加。その中で、興味深かったのが「バズらせる」ためのプロセス。一例としては、SNS等で公開された記事等がTVで取り上げられることによって、さらに世の中に広まるというもの。そして、それをただほって置くのではなく、それを活かすアクション・フォローをすることでより効果的になるということだ。これについて、実体験から「たしかに」と思った。

以前、「【格闘】カラスとツバメ  [FIGHTING]Crow and Swallows」という動画をyoutubeに投稿したことがあるのだが、それが偶然TV局の目に留まり、2012年11月29日のスーパーニュース(フジテレビ)で取り上げられ全国に放送された。しかし、視聴数は伸びることはなかった。一つの原因としては、全くその後のフォローをしなかったこと。もし、取り上げられたことを活かした次のアクションをしていれば、あるいは「バズる」こともあったかもしれない。残念。。


【格闘】カラスとツバメ  [FIGHTING]Crow and Swallows



  •  2012年11月29日のスーパーニュース(フジテレビ)で放送

【Cytoscape】ネットワークを可視化する最強ツール

Cytoscape を使ってネットワークを可視化してみた


Cytoscapeで可視化したネットワーク

なかなかキャッチーな綺麗な図が描けてる
ただ、この全体図だけでは個々の繋がりがよく把握できない
そこで、拡大してみたのが下記の図

拡大図

Cytoscapeの便利機能の一つは、このようにどんどん図を拡大していき個々のノード間の繋がりを簡単に把握できる点にある。いちいち、作図時に拡大設定などをしなくても良いので作業効率が非常に良い。

今後、積極的に使いたいツールの代表格!

2016年9月27日火曜日

【新居】遠くの夜景はきれいだけど…(分析と関係ないです)

東京に来て早2、3年くらい
一度はタワマンに住んでみたいという思いがやっと実現できた
写真にはよく写ってないが、スカリツリー、東京タワー、六本木ヒルズを一望できる
ただ、周りは車や人通りが少なく寂しい感じ


部屋からの眺望

思えば、研究員も含めれば約6年間で4回ほど転職し、
周りの環境もその都度大きく変わっていった

まさに「光陰矢の如し」で時が過ぎたように思える

博士課程を退学し坂の上の雲を目指してきたが、

はたして雲との距離は縮まっているのだろうか


2016年9月26日月曜日

【トレジャーデータ】WITH句を使って複雑なSQLの可読性・効率性を改善

前回書いた複雑なSQL
をWITH句を用いて効率性を改善!ただ、どのくらい改善したか計測してない。。。


-- WITH句を使うことで効率性改善
WITH my_with AS(
  SELECT
    article_name,
    user_id
  FROM
    データベース名.mysite_log
  WHERE
-- NOT LIKE '%Safari%'を使っていたが遅くなると聞き下記に変更
    td_browser != 'Safari'
    AND TD_TIME_RANGE(time,
      '2016-06-01',
      '2016-07-01',
      'JST')
  GROUP BY
    article_name,
    user_id
) SELECT
  mysite.article_name,
  MAX(mysite.my_uu) AS my_uu,
  MAX(othersite.other_uu) AS other_uu,
  MAX(order.order_uu) AS order_uu
FROM (
    SELECT
      article_name,
      COUNT(DISTINCT user_id) AS my_uu
    FROM
      my_with
    GROUP BY
      article_name
  ) mysite LEFT
JOIN (
    SELECT
      article_name,
      COUNT(DISTINCT imid) AS other_uu
    FROM
      my_with
    WHERE
      user_id IN(
        SELECT
          user_id
        FROM
          データベース名.log_web_wp
        WHERE
          TD_TIME_RANGE(time,
            '2016-06-01',
            '2016-07-01',
            'jst')
      )
    GROUP BY
      article_name
  ) othersite
  ON (
    mysite.td_title = othersite.td_title
  ) LEFT
JOIN (
    SELECT
      article_name,
      COUNT(DISTINCT user_id) AS order_uu
    FROM
      my_with
    WHERE
      user_id IN(
        SELECT
          user_id
        FROM (
            SELECT
              user_id,
              order_id
            FROM
              データベース名.userid_orderid_matching
          ) matching
        JOIN (
            SELECT
              order_id
            FROM
              データベース名.order_log
            WHERE
              TD_TIME_RANGE(time,
                '2016-06-01',
                '2016-07-01',
                'jst')
              )
          ) order_log
          ON (
            matching.order_id = order_log.order_id
          )
      )
    GROUP BY
      article_name
  ) order
  ON (
    mysite.article_name = order.article_name
  )
GROUP BY
  mysite.article_name
ORDER BY
  mysite_uu DESC