読者です 読者をやめる 読者になる 読者になる

後楽園でマーケティングを研究する学生のブログ

マーケティングをサイエンスする

バスケット分析の難しさ

コーディング

以前インターン先で、スーパーのPOSデータを使って併売実験を行ったのですが、バスケット分析で出した結果、あまりいい結果にならなかったので注意点というか、分析をする上で大事だと感じたことをメモります。

目次

ターゲット

分析に興味のあるかた、始めたばっかの方、lift値ってなんだっけ?という方

バスケット分析とは

バスケット分析とは一言でいうと、よく一緒に買われている商品の組み合わせをみつける分析手法です。
信頼度や支持度やリフト値など聞きなれない言葉が出てきますが、詳しくはこちらの記事と本を参照してください。とても細かく説明されていてとても参考にさせていただきました。
R本は特にRのコードが紹介されていて、パラメータのチューニングの際にとても参考になりました。
商品分析の手法(ABC分析、アソシエーション分析)

リフト値ってなに?

バスケット分析で用いられるリフト値というのは、簡単に言うと、なにもしないより組み合わせて売ることがどのくらい効果があるのかを知る指標となります。
1以上の値であれば効果があると判断できます。
他にも支持度と確信度という指標があり、三つの指標を総合して判断していきます。

バスケット分析の注意点

バスケット分析で併売の効果があると判断できた組み合わせでも実際には併売の効果があまりないことがあります。
それは価格の情報が入っていないことや、商品のカテゴリが異なっていたりすることが考えられます。

例えば、チョコボールゴディバのチョコがよく買われているとします。そのときゴディバのチョコをチョコボールが売っている駄菓子ゾーンの近くに置いて、はたして本当に売れるでしょうか?
どちらかというとゴディバのチョコが置いてある高級菓子ゾーンの近くにチョコボールを置いたほうが売れそうですよね。値段が異なる場合、併売の向きも考える必要があります。
また、お肉と、洗剤がよく買われている組み合わせだとします。そこでお肉の売り場の近くに洗剤を置いたとします。結果はどうでしょうか?
あまり売れなそうですよね。このようにいくらよく買われている組み合わせだとしても併売の効果が出にくいこともあるのです。

因果関係と相関関係

上記のようにバスケット分析上では併売の効果が高いとされた商品でも実際には効果が出なかったというのは、まさしく相関関係でしかないからなんですね。
しかし、相関関係の中には因果関係がしっかりできているものもあるのでものは試しでいっぱい併売をやってみるというのも一つの手かもしれません。
分析手法はあくまで分析なので実践して効果測定まできちんと追っていくことが大切です。

Googleトレンドをスプレッドシートから検索

spred sheet excel google トレンド

Googleトレンドで一度にたくさんのキーワードのトレンドを確認したいことがあるのですが、通常5つまでしか同時に検索することができないんです。
そこで今回はスプレッドシートで、検索したいキーワードのトレンドを取得できるような仕組みを作ってみました。

目次

どうやって作るか

スプレッドシートのimportxml()を使ってGoogle トレンドから元のデータを引っ張ってきます。
そのデータからleft(),right(),find()などを使いながらうまいこと処理して、トレンドの数字だけを引っ張ってくることができれば、完成です。

完成系

ちょっと複雑なので先に完成系を貼っておきます。コピーしてご利用ください。
検索数が多い場合はスクレイピングのスピードが下がるので、その場合は時間を空けて試してみてください。

f:id:iwa_k:20170301134702p:plain

作ってみましょう

1.googleトレンドから元データを取得

まずgoogleトレンドの検索結果の元データを取得します。

=importxml("https://www.google.com/trends/fetchComponent?q="&"検索したいキーワード"&"&geo=JP&hl=ja&cid=TIMESERIES_GRAPH_0&export=5")

試しにキーワード"楽天"のトレンドを取得してみました。

Google Trendsvar _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-4401283-1']); _gaq.push(['_setDomainName', 'google.com']); _gaq.push(['_setAllowLinker', true]);_gaq.push(['_setCookiePath', '/trends']);_gaq.push(['_trackPageview', window.location.pathname + window.location.search + window.location.hash]); (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); google.load('visualization', '1',{'packages':['corechart']});google.setOnLoadCallback(drawChart); function drawChart() { var chartData = {""columns"":[{""id"":""d"",""label"":""Date"",""type"":""datetime""},{""role"":""annotation"",""type"":""string""},{""p"":{""html"":true},""role"":""annotationText"",""type"":""string""},{""id"":""q0"",""label"":""楽天"",""type"":""number""},{""role"":""annotation"",""type"":""string""},{""p"":{""html"":true},""role"":""annotationText"",""type"":""string""},{""role"":""certainty"",""type"":""boolean""}],""headlineDataPoints"":[],""width"":485,""axisAnnotations"":[{""row"":84,""url"":""//support.google.com/trends/bin/answer.py?hl\u003dja\u0026answer\u003d1383240""}],""rows"":[
[{""v"":new Date(2004, 0, 16, 12, 0),""f"":""2004年1月""},null,null,19,null,null,true],
[{""v"":new Date(2004, 1, 15, 12, 0),""f"":""2004年2月""},null,null,20,null,null,true],
[{""v"":new Date(2004, 2, 16, 12, 0),""f"":""2004年3月""},null,null,19,null,null,true],
[{""v"":new Date(2004, 3, 16, 0, 0),""f"":""2004年4月""},null,null,20,null,null,true],
[{""v"":new Date(2004, 4, 16, 12, 0),""f"":""2004年5月""},null,null,21,null,null,true],
[{""v"":new Date(2004, 5, 16, 0, 0),""f"":""2004年6月""},null,null,20,null,null,true],
[{""v"":new Date(2004, 6, 16, 12, 0),""f"":""2004年7月""},null,null,20,null,null,true],
[{""v"":new Date(2004, 7, 16, 12, 0),""f"":""2004年8月""},null,null,20,null,null,true],
...
[{""v"":new Date(2016, 8, 16, 0, 0),""f"":""2016年9月""},null,null,49,null,null,true],
[{""v"":new Date(2016, 9, 16, 12, 0),""f"":""2016年10月""},null,null,46,null,null,true],[{""v"":new Date(2016, 10, 16, 0, 0),""f"":""2016年11月""},null,null,47,null,null,true],
[{""v"":new Date(2016, 11, 16, 12, 0),""f"":""2016年12月""},null,null,53,null,null,true],
[{""v"":new Date(2017, 0, 16, 12, 0),""f"":""2017年1月""},null,null,48,null,null,true],
[{""v"":new Date(2017, 1, 15, 0, 0),""f"":""2017年2月(集計途中のデータ)""},null,null,46,null,null,true],[{""v"":new 
Date(2017, 2, 16, 12, 
0),""f"":""2017年3月""},null,null,null,null,null,true]],""showHeadlines"":false,""percentData"":false,""colors"":[""#3f85f2""],""height"":230}; 
var htmlChart = new trends.HtmlChart( 'time-chart', chartData.columns, chartData.rows, chartData.headlineDataPoints, null , chartData.showHeadlines,true, chartData.percentData, chartData.colors, chartData.width, chartData.height, chartData.axisAnnotations,true);htmlChart.render(); }  body {margin: 8px}; 

上がトレンドの検索結果となります。
データの概要を簡単に説明すると、2004年からのデータで、100を検索ボリュームの最大値としてトレンド結果を返してくれています。
[{""v"":new Date(2017, 0, 16, 12, 0),""f"":""2017年1月""},null,null,48,null,null,true]
2017年1月のトレンドは48となります。(nullに挟まれている数字)

2.元データから必要なデータを抽出する

取得した元データから必要なデータは、「2017年1月のトレンドは48」というデータのみです。
それらを集めて、下の表のようなものを作っていきます1

キーワード 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
楽天 20 40 60 80 20 40 60 80 20 40 60 80
amazon 50 60 80 70 50 60 80 70 50 60 80 70
メルカリ 10 20 40 90 10 20 40 90 10 20 40 90

元データからfind()してleft()やright()で該当の数字だけを取り除く処理はまた別の機会に書きたいと思います2:。
mid()を使うともっとシンプルにかけるよ。などございましたらコメントください。宜しくお願いします。


  1. 表内の数値は僕が独断と偏見で入力した値ですので、きちんと作成したシートで検索し直してください。

  2. 実は疲れてしまったとういうのはここだけの話です。

データベースを構築しよう〜PostgreSQL編〜

データベース PostgreSQL

データコンペ用のデータベースをPostogreSQLで構築しました。 データ型さえきちんと頭に入れれば簡単に作れました。

作成手順

手順 1. データベース作成 1. テーブル作成 1. データの挿入

今回のゴール

今回はレシートのデータを表にしてみます。 二行目の日本語は補足説明です。実際にはありません。 データベース名はposdata、表の名前はordersとします。

order_id product_id order_price order_amount order_date
レシート番号 商品番号 販売価格 購入量 購入時間
00001 101010 3000 1 2016/3/17 21:08
00002 202020 1000 2 2016/3/17 22:00
00002 303030 2000 1 2016/3/17 22:00
00003 123456 4000 2 2016/3/17 22:30
00004 223456 5000 1 2016/3/17 22:40

基本DB操作

まず表を入れる箱を用意します。 データベース作成

CREATE DATEBASE データベース名;

CREATE DATEBASE posdata;

これで表を入れる箱ができました。 次にテーブルのアウトラインを先に作ります。

テーブル作成

CREATE TABLE テーブル名 (カラム名 データ型,カラム名 データ型,カラム名 データ型,・・・);

CREATE TABLE テーブル名 (order_id text,product_id text,order_price integer,order_amount integer,order_date timestamp with time zone);

こんな感じの表のアウトラインができます。

order_id product_id order_price order_amount order_date
レシート番号 商品番号 販売価格 購入量 購入時間
text text integer integer timestamp with time zone

下の二行は説明のためにあります。実際には一行目しかありません。

最後に中身(データ)を入れていきます。 今回は、CSVファイルからデータを挿入するのですが、PostgreSQL特有のCOPY文を使ってます。

copy テーブル名 from 読み込むCSVの絶対パス;

copy orders from ’ORDER.csv’ WITH(encoding ’SJIS’,format csv,header true);
# whith以降は csvのエンコードがshift-jisかつcsvにヘッダーがあったためつけてるオプションになります。

完成ー

order_id product_id order_price order_amount order_date
00001 101010 3000 1 2016/3/17 21:08
00002 202020 1000 2 2016/3/17 22:00
00002 303030 2000 1 2016/3/17 22:00
00003 123456 4000 2 2016/3/17 22:30
00004 223456 5000 1 2016/3/17 22:40

補足

データ型の変更

データ型の設定をミスって値が挿入できない場合は下のコマンドでデータ型を変えることできます。

ALTER TABLE テーブル名 ALTER COLUMN カラム名 TYPE 変更したいデータ型

データ型もっと知りたい!

データ型について知りたい方はこちらのリンクを参照してください。

データ型

ABテストで確実に成果を出す

コーディング

僕は、インターン先でABテスト案を探すところから、実施するところまで一気通貫で担当してきました。 本などでよくPDCAが大事だということを目にしていましたが、実際に運用してみて本当にPDCAが大事だということを学び、一番は諦めない気持ちだと感じました。 3Qが終わり、一区切りついたので整理していこうと思い、メモります。

この記事のターゲット

ABテストはやったことはあるが目立った成果が出せず、今後の改善に悩んでいる方

この記事を読むメリット

  • ABテストの大まかな流れがわかる
  • ABテストを運用する上でぶつかる壁が見えてくる

ABテストは準備が9割

ABテストの評価はOptimizelyやVWOなどのABテストツールを使っていれば、基本的に自動で判定してくれます。
しかし実際はどうでしょうか?
これは効果がある!」と思っていた改善案でも実際には効果が良いのか悪いのかも分からずに終わってしまうこともあります。
ただ事前の準備をきちんとしていれば、そのようなことも少なくなりますし、何よりも次のテストに活かすことができるのです。
僕はOptimizelyを使って運用していたので、たまに例にあげて説明します。

ABテストを実施するまでの流れ

具体的にどんな準備が必要か紹介する前に、下記にABテストを実施する流れを大まかに紹介します。

  1. 改善するページの探索
  2. 改善案の考案
  3. ABテストの設計&実行
  4. 結果の評価&分析

なぜこの順番なのか。 それは、ABテストは基本的に成果が上がるまで同じページで行ったほうが、成果がでやすいからです。 なので、最初に改善するページを決めます。 それから改善案を考えます。 成果が上がるまで粘り強く同じページで改善を続けて、コツコツ進めていきましょう。

僕はこのような流れでABテストを運用していました。 この中で特に大事なのは、2,3の手順です。

では、流れを一つ一つ細かく見ていきます。

1.改善するページを決める

条件は少ないですが、特にABテストで改善したいゴールは何かを明確にしておくようにしてください。

  1. 流入数はテストできるだけあるか
  2. ゴールに近いページかどうか(たいていはフォーム画面になると思います。)
  3. モバイルとPCは原則テストを分ける

A/B Test Sample Size Calculatorでテストで効果があるかないか判定だきるだけの流入数があるか参考にすると良いでしょう。
2回目からは、以前のテストの結果を参考にして、流入が足りているか判断していきましょう。
optimizelyの場合、ページの種類によっても異なるので最低でも5000UUはほしいと経験的に感じました。
AAテストをやった時に4000UUでもかなり差がついてしまったことがあります。
AAテストというのは同じデザインのページを二つ用意して、それぞれの成果を見比べるテストです。主にテストツールの精度を測るために行います。

また基本的にモバイルページとPCページは別サイトとして、テストを分けることをおすすめします。
流入数もデバイスによって異なりますし、レイアウトやデザインも異なるからです。

2.改善案をきめる

改善案を考える上で、大事なのは、見た目の違いがあるデザインかどうかです。
例えば、アイキャッチ画像を変更したり、2カラムレイアウトを反対にしたり、色を大きく変更したりです。
文字サイズをちょっと大きくしたり、小さくしたりするのは効果がでにくいです。

ユーザーがその改善案の違いで行動を変えるくらいの変化でなければ、改善につながりません。
なので本当に細かい修正はテストなしでやってください。工数の無駄なので。
また、いくらテストできるからと言っても、工数がかかるので、優先順位を考えなくてはなりません。

ではどのように優先順位を考えると良いのでしょうか(ここでは改善案のアイデアだしはすでに済んでいるものとします)。

  1. 改善率が大きくなりそうかどうか(直感)
  2. 素早くPDCAが回せるかどうか

改善率が大きくなりそうかどうかは直感で良いと思います。
試してみないとわかりませんが、結局は人間が利用するものなので、直感的に改善しそうと思ったものから試したほうが良いと思います。

テスト設計に時間がかかりすぎてしまうと、高速なPDCAを回せなくなってしまいますので、他のテストを実行中に準備するなど時間の使い方を工夫しましょう。
キャッチコピーの変更やアイキャッチ画像の変更などであれば、すぐに次のテストを回すことができるので優先順位は高くなります。
ポップアップなどは実装が少し大変な上に、次のテストに結びつけるのが、少し難しくなるので、優先順位は下がります。ただし、改善率が高そうであれば、試します。

3.ABテストを設計し実行する

どの改善案を試すか決まったら、ABテストの設計をしていきます。
テスト設計で必要なもの

  1. ターゲット設定
  2. ゴール設定
  3. テスト前確認

ターゲット設計は主にデバイスを分けるように設定します。
ゴール設定はOptimizelyでは指定ページへの到達だけでなく、要素のクリック率やクリック数も測ることができます。
なるべく少ないゴールを設定し、見る数字を絞りましょう。またゴールの優先順位も決めておきましょう。

テスト前確認は必ず行いましょう。
フォーム画面では最悪の場合、コンバージョンボタンを押しても予約されなかったり、購入できなかったりすることもあります。
テスト前確認は意外と時間がかかりますので、その時間的コストを頭にいれつつ設計したいものです。

4.結果の評価とその分析

結果の評価自体はとても簡単です。
Optimizelyの場合は統計的有意性に基づいた結果を自動的に計算してくれます。
ゴールと流入数さえ、きちんと準備すれば、そのテストの効果があったのかなかったのかを判断してくれます。
テストの結果には次の4つのタイプがあります。

  • 統計的有意性があり、結果が良くなっていた
  • 統計的有意性があり、結果が悪くなった
  • 統計的有意性は無いが、結果が良くなっていた
  • 統計的有意性が無いが、結果が悪くなっていた

この中で統計的にきちんと改善できたと言って良いのはどれでしょうか?
正解は一番上のもののみです。
詳しく書くと、
2つ目は統計上、悪くなった。
3つ目と4つ目は、AパターンとBパターンに差があるとは言い切れなかった。(決断を保留にする。)
と判断します。
Optimizelyの場合は、検定方法にベイズ検定を中心とした独自の検定を行っています。

結果が分かったところで、高速にPDCAを回すためには次のPlanにどう結びつけるかが大事です。
なぜその結果になったのか、思い通りのテストになったのか、原因をよく考え、次のテストに反映させていきましょう。
結果が悪かったことで悲観する必要は全くありません。悪かったらその逆のデザインやレイアウトにしてみれば、改善できるはずです。
途中で投げ出さず粘り強くやっていきましょう。

これは半分自分に言い聞かせるために書いています。笑
でもそうやって成果が出たので自信があります。

参考文献

僕がABテストをする上で参考になったサイトを紹介させていただきます。
この下の二つさえあれば、なんとかなるかなと思いました。
他にもたくさんあるので是非見てみてください。
海外の有名IT企業のABテストブログまとめ
ABテストで圧倒的成果を残すためのslideshare資料5選+5記事

ABテスト運用者の方の参考に少しでもなればと思います。

textareaのカーソル位置に外部ボタンから値を挿入

jQuery button jquery textarea tool コーディング

本日は決まった言葉をボタンからワンタッチでtextareaに文字を挿入することができる機能を作成したので勉強メモを残しておきます。 スマホではtextareaにfocusがある時にキーボードが出現するのでキーボードを維持したままボタン機能を実装するのが今回の目標でした。 自分なりに使いやすくまとめられたと思ったので参考までにどうぞ。

実装手順

  1. ボタンを用意(display:none)
  2. textareaにfocusされたらボタン出現
  3. textareaからfocusが外れたらボタンを消す
  4. ボタンをおした時はボタンをの表示を維持し、focusを元のtextareaに戻す
  5. プラグインを使いカーソル位置にボタンの値を挿入する

詳しくはコードを参照ください。

jQuery.selectionというプラグインを使いました。 岩崎さんすごいです。この場を借りてお礼を申し上げたいと思います。ありがとうございました!

Copyright © 2010-2014 IWASAKI Koji (@madapaja). http://blog.madapaja.net/ Under The MIT License

index.html

<!DOCTYPE html>
<head>
   <meta charset="utf-8">
   <title>便利ボタン</title>
   <meta name="viewport" content="width=device-width,initial-scale=1.0">
   <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
   <style>
        .sharp{
            width: 300px;
            height: 300px;
        }
        #sharp_btns{
            display: none;
            position: fixed;
            bottom: 0;
        }
    </style>
</head>
<body>
<!--便利ボタン このままコピー -->
    <div id="sharp_btns">
        <input class="sharp_btn" type="button" id="sharp1" value="こんにちは">
        <input class="sharp_btn" type="button" id="sharp2" value="こんばんは">
        <input class="sharp_btn" type="button" id="sharp3" value="さようなら">
    </div>
<!--        便利ボタン終わり-->
    
    
    
    <main>      
<!--        便利ボタン機能をつけたいtextareaに.shrapを追加-->
<textarea class="sharp">
あ
い
う
え
お
</textarea>
    <textarea name="" class="sharp" id="" cols="30" rows="10">
か
き
く
け
こ
    </textarea>
    <textarea name="" id="" cols="30" rows="10">
さ
し
す
せ
そ
    </textarea>
    </main>
<!-- javascript -->
    <script src="insert.js"></script>
</body> 

insert.js

/*!================プラグイン================
 * jQuery.selection - jQuery Plugin
 *
 * Copyright (c) 2010-2014 IWASAKI Koji (@madapaja).
 * http://blog.madapaja.net/
 * Under The MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
    (function($, win, doc) {
    /**
     * get caret status of the selection of the element
     *
     * @param   {Element}   element         target DOM element
     * @return  {Object}    return
     * @return  {String}    return.text     selected text
     * @return  {Number}    return.start    start position of the selection
     * @return  {Number}    return.end      end position of the selection
     */
    var _getCaretInfo = function(element){
        var res = {
            text: '',
            start: 0,
            end: 0
        };

        if (!element.value) {
            /* no value or empty string */
            return res;
        }

        try {
            if (win.getSelection) {
                /* except IE */
                res.start = element.selectionStart;
                res.end = element.selectionEnd;
                res.text = element.value.slice(res.start, res.end);
            } else if (doc.selection) {
                /* for IE */
                element.focus();

                var range = doc.selection.createRange(),
                        range2 = doc.body.createTextRange();

                res.text = range.text;

                try {
                    range2.moveToElementText(element);
                    range2.setEndPoint('StartToStart', range);
                } catch (e) {
                    range2 = element.createTextRange();
                    range2.setEndPoint('StartToStart', range);
                }

                res.start = element.value.length - range2.text.length;
                res.end = res.start + range.text.length;
            }
        } catch (e) {
            /* give up */
        }

        return res;
    };

    /**
     * caret operation for the element
     * @type {Object}
     */
    var _CaretOperation = {
        /**
         * get caret position
         *
         * @param   {Element}   element         target element
         * @return  {Object}    return
         * @return  {Number}    return.start    start position for the selection
         * @return  {Number}    return.end      end position for the selection
         */
        getPos: function(element) {
            var tmp = _getCaretInfo(element);
            return {start: tmp.start, end: tmp.end};
        },

        /**
         * set caret position
         *
         * @param   {Element}   element         target element
         * @param   {Object}    toRange         caret position
         * @param   {Number}    toRange.start   start position for the selection
         * @param   {Number}    toRange.end     end position for the selection
         * @param   {String}    caret           caret mode: any of the following: "keep" | "start" | "end"
         */
        setPos: function(element, toRange, caret) {
            caret = this._caretMode(caret);

            if (caret === 'start') {
                toRange.end = toRange.start;
            } else if (caret === 'end') {
                toRange.start = toRange.end;
            }

            var userAgent = window.navigator.userAgent.toLowerCase();
            if (userAgent.indexOf("firefox") > -1) {
                element.focus();
            }
            try {
                if (element.createTextRange) {
                    var range = element.createTextRange();

                    if (win.navigator.userAgent.toLowerCase().indexOf("msie") >= 0) {
                        toRange.start = element.value.substr(0, toRange.start).replace(/\r/g, '').length;
                        toRange.end = element.value.substr(0, toRange.end).replace(/\r/g, '').length;
                    }

                    range.collapse(true);
                    range.moveStart('character', toRange.start);
                    range.moveEnd('character', toRange.end - toRange.start);

                    range.select();
                } else if (element.setSelectionRange) {
                    element.setSelectionRange(toRange.start, toRange.end);
                }
            } catch (e) {
                /* give up */
            }
        },

        /**
         * get selected text
         *
         * @param   {Element}   element         target element
         * @return  {String}    return          selected text
         */
        getText: function(element) {
            return _getCaretInfo(element).text;
        },

        /**
         * get caret mode
         *
         * @param   {String}    caret           caret mode
         * @return  {String}    return          any of the following: "keep" | "start" | "end"
         */
        _caretMode: function(caret) {
            caret = caret || "keep";
            if (caret === false) {
                caret = 'end';
            }

            switch (caret) {
                case 'keep':
                case 'start':
                case 'end':
                    break;

                default:
                    caret = 'keep';
            }

            return caret;
        },

        /**
         * replace selected text
         *
         * @param   {Element}   element         target element
         * @param   {String}    text            replacement text
         * @param   {String}    caret           caret mode: any of the following: "keep" | "start" | "end"
         */
        replace: function(element, text, caret) {
            var tmp = _getCaretInfo(element),
                    orig = element.value,
                    pos = $(element).scrollTop(),
                    range = {start: tmp.start, end: tmp.start + text.length};

            element.value = orig.substr(0, tmp.start) + text + orig.substr(tmp.end);

            $(element).scrollTop(pos);
            this.setPos(element, range, caret);
        },

        /**
         * insert before the selected text
         *
         * @param   {Element}   element         target element
         * @param   {String}    text            insertion text
         * @param   {String}    caret           caret mode: any of the following: "keep" | "start" | "end"
         */
        insertBefore: function(element, text, caret) {
            var tmp = _getCaretInfo(element),
                    orig = element.value,
                    pos = $(element).scrollTop(),
                    range = {start: tmp.start + text.length, end: tmp.end + text.length};

            element.value = orig.substr(0, tmp.start) + text + orig.substr(tmp.start);

            $(element).scrollTop(pos);
            this.setPos(element, range, caret);
        },

        /**
         * insert after the selected text
         *
         * @param   {Element}   element         target element
         * @param   {String}    text            insertion text
         * @param   {String}    caret           caret mode: any of the following: "keep" | "start" | "end"
         */
        insertAfter: function(element, text, caret) {
            var tmp = _getCaretInfo(element),
                    orig = element.value,
                    pos = $(element).scrollTop(),
                    range = {start: tmp.start, end: tmp.end};

            element.value = orig.substr(0, tmp.end) + text + orig.substr(tmp.end);

            $(element).scrollTop(pos);
            this.setPos(element, range, caret);
        }
    };

    /* add jQuery.selection */
    $.extend({
        /**
         * get selected text on the window
         *
         * @param   {String}    mode            selection mode: any of the following: "text" | "html"
         * @return  {String}    return
         */
        selection: function(mode) {
            var getText = ((mode || 'text').toLowerCase() === 'text');

            try {
                if (win.getSelection) {
                    if (getText) {
                        // get text
                        return win.getSelection().toString();
                    } else {
                        // get html
                        var sel = win.getSelection(), range;

                        if (sel.getRangeAt) {
                            range = sel.getRangeAt(0);
                        } else {
                            range = doc.createRange();
                            range.setStart(sel.anchorNode, sel.anchorOffset);
                            range.setEnd(sel.focusNode, sel.focusOffset);
                        }

                        return $('<div></div>').append(range.cloneContents()).html();
                    }
                } else if (doc.selection) {
                    if (getText) {
                        // get text
                        return doc.selection.createRange().text;
                    } else {
                        // get html
                        return doc.selection.createRange().htmlText;
                    }
                }
            } catch (e) {
                /* give up */
            }

            return '';
        }
    });

    /* add selection */
    $.fn.extend({
        selection: function(mode, opts) {
            opts = opts || {};

            switch (mode) {
                    /**
                 * selection('getPos')
                 * get caret position
                 *
                 * @return  {Object}    return
                 * @return  {Number}    return.start    start position for the selection
                 * @return  {Number}    return.end      end position for the selection
                 */
                case 'getPos':
                    return _CaretOperation.getPos(this[0]);

                    /**
                 * selection('setPos', opts)
                 * set caret position
                 *
                 * @param   {Number}    opts.start      start position for the selection
                 * @param   {Number}    opts.end        end position for the selection
                 */
                case 'setPos':
                    return this.each(function() {
                        _CaretOperation.setPos(this, opts);
                    });

                    /**
                 * selection('replace', opts)
                 * replace the selected text
                 *
                 * @param   {String}    opts.text            replacement text
                 * @param   {String}    opts.caret           caret mode: any of the following: "keep" | "start" | "end"
                 */
                case 'replace':
                    return this.each(function() {
                        _CaretOperation.replace(this, opts.text, opts.caret);
                    });

                    /**
                 * selection('insert', opts)
                 * insert before/after the selected text
                 *
                 * @param   {String}    opts.text            insertion text
                 * @param   {String}    opts.caret           caret mode: any of the following: "keep" | "start" | "end"
                 * @param   {String}    opts.mode            insertion mode: any of the following: "before" | "after"
                 */
                case 'insert':
                    return this.each(function() {
                        if (opts.mode === 'before') {
                            _CaretOperation.insertBefore(this, opts.text, opts.caret);
                        } else {
                            _CaretOperation.insertAfter(this, opts.text, opts.caret);
                        }
                    });

                    /**
                 * selection('get')
                 * get selected text
                 *
                 * @return  {String}    return
                 */
                case 'get':
                    /* falls through */
                default:
                    return _CaretOperation.getText(this[0]);
            }

            return this;
        }
    });
})(jQuery, window, window.document);
/* =========ここまでプラグイン=========== */




/* =========プラグインの設定&適用=========== */
$(function(){


    $("#sharp1")
        .click(function(){        /*text: "挿入する文字", mode: "before:カーソルの前に挿入""after:カーソルの後に挿入""replace:選択範囲を置き換え", caret: "keep:カーソル保持" */
        $(".sharp:focus").selection("insert",{ text: "こんにちは", mode: "before", caret: "keep" })
    });

    $("#sharp2")
        .click(function(){
        $(".sharp:focus").selection("insert",{ text: "こんばんは", mode: "before", caret: "keep" })
    });

    $("#sharp3")
        .click(function(){
        $(".sharp:focus").selection("insert",{ text: "さようなら", mode: "before", caret: "keep" })
    });


/* =========便利ボタンの表示の切り替え=========== */
    $('.sharp').focus(function(){
        $('#sharp_btns').css("display","block");
    });
    $('.sharp').blur(function(){
        if($("#sharp_btns").is("#sharp_btns:active")){
            this.focus();
        }else{
            $('#sharp_btns').css("display","none");
        }
    });


});

ユニークユーザーを集計する時の注意点

Google Analytics データ分析

みなさんはWEBの状況を知るのにどんな数字を気にしていますか?

Google AnalyticsではCV数,CVR,PV,UU,Session数などいろいろな数字をみることができますがその中の一つにUUつまりユニークユーザー数という指標があると思います。 この数字が高ければ基本的に自分のサイトに訪れている人が多いという判断ができます。 もちろん参考にしているよ!という方にお聞きしたいのです。あなたは本当に意図しているユニークユーザー数をみることができていますか?と。

今回はGoogle Analyticsでの計測の仕方について紹介しますが、他の分析ツールを使っている方も同じ問題を実は持っているかもせいれません。 WEB担当の方やデータ分析を担当している方にぜひ一度読んでいただきたいです。

ユニークユーザーとはなにか?

ユニークユーザーとはどういう意味か。これは知っている人も多いと思います。 これはある期間中にそのサイトあるいはページを訪れた人数のこと。 つまり、ある期間中に同じ人が何回か同じサイトやページに訪れてもその数はカウントされないことになります。

ユニークユーザーの落とし穴

ユニークユーザーを見ればどのくらいの人が自分のサイトやページに訪れているのかがわかるので、ECサイトなど運営している側からするとかなり気にしてみたい数字の一つと言えるでしょう。 しかし、ユニークユーザーにも落とし穴があります。それは集計期間です。集計期間が異なるとユニークユウーザーの数にズレが生じてしまう問題です。 具体的に見ていきましょう。例えばAさんは4月と5月中に1回ずつECサイトで買い物するために訪れたとします。 集計するときに04/01~05/31の期間で集計したとします。この場合は上で説明したようにユニークユーザー数には1カウントしかされませんね。 しかしどうでしょう?04/01~04/30と5/01~05/31の期間の2回に分けて集計を行い、それを4,5月の分析データとしてみると、このときは2カウントされてしまうのです。

集計期間をわざわざ2回に分ける必要がないじゃん!と思ったかたは大丈夫なのですが、実は現場では、定期レポート作成などのために1ヶ月ごとにデータを集計する場合がありますよね。このときは上のようなズレの問題が生じてしまうのです。この方法だと同じ人が04/30と05/01に1度ずつ訪れたとしても別ユーザーとしてカウントされてしまうのです。これでは正しいデータを集計できているとは言えないですよね。

どのように集計すればよいか

上のような問題はユニークユーザーだけでなく他のデータでも起こりえることだと思います。 そこでどのようにデータを集計すればよいのか考えてみましょう。 皆さんのサイトでは一度訪問したユーザーがどのくらいの期間を空けて再訪したときにユニークユーザーとして認めますか? Amazonなどの小売りECサイトでは短期間で違うものを購入するために訪れることもあるので、比較的短く設定してもよいと思われます。 逆に、引越しサイトなどは一回頼むとあまりそのサイトは使わなくなることが多いと思うので、比較的長めに設定してもよいかと思われます。 つまりサイトによってユニークユーザーの定義をしっかり決めて決めた期間できちんと集計することが大事なのではないでしょうか?

一つ上のWEBデザインにするための小技集

デザイン

最近もそうですが、これからもWEBデザインはいかにユーザー目線で考えられているか、訪れた人がきちんと情報を理解できるようなデザインが、大切とされていくでしょう。

そんな中で今回紹介したいのは、サイトの細かい部分を装飾するための参考サイトです。

あれ、いまさっきこれからのデザインはいかにユーザー目線で考えられているかが大切と言ったじゃん!と思った方はぜひ読んでいただきたいです。

なぜならユーザー目線を考えるあまり、薄っぺらいデザインになりがちだからです。

薄っぺらいデザインのサイトというのは構成はしっかり考えられていてユーザーの導線などはしっかりしていても、見せ方や色や影までこだわらないために、見た目ですぐに飽きられてしまうサイトのことです。

今回は、ユーザー目線も大事だが、細部までしっかりこだわろうと思ってくれた方に、ぜひ参考にしたもらいたい小技サイトを紹介していきます。

CodePen

CodePen
http://codepen.io/

CodePenはフロントエンドで実装できるデザインをスニペットとして保存、公開できる場所です。
自分もブログを書くときに使ったりしているのですが、かなり便利です。
すごい動きのあるデザインなども、コードをすぐに見ることができるので参考になること間違いなしです。

Behance

Behance
https://www.behance.net/

Behanceはデザイナーの方であれば必須の参考サイトの一つと言えるでしょう。Adobeが運営していて誰でも自分の作品を投稿することができます。 いつも参考にしているのはUI/UX部門の作品達です。ページ全体のデザインからパーツごとのデザインまで幅広く掲載されています。

Dribbble

Dribbble
https://dribbble.com/

Dribbbleは招待された人だけが投稿できるSNSです。そのため投稿のレベルが全体的に高いのが魅力です。DribbbleはWEBデザインだけでなく、イラストやロゴなども多数掲載されていていい刺激になること間違いなしです。すでに利用している人も多いと思います。

UI PARADE

Ui_Parade
http://www.uiparade.com/

UI PARADEはまだあまり知られていないかもしれません。でもここにはUIに関するデザインがたくさん投稿されています。
細かなデザイン面と使いやすさの面で一度に参考になるので、ぜひたくさんいいアイデアを吸収して役に立ててもらいたいです。

UI Movement

UI_Movement
http://uimovement.com/

UI PARADEはUIデザインに特化されていて中でも動きのある部分に集中して集められているサイトです。検索もありますがスクロールして見るように最適化されているので、アイデアが思い浮かばなかったり、仕事の休憩時間などにさらっと見ていい刺激をもらいましょう。個人的にはかなり好きなサイトです。

定番なサイトから少しマニアックなサイトまで紹介出来たかなと思っています。他にもたくさんの参考サイトがあるので皆さんも自分のお気に入りのサイトを見つけてユーザーの満足度を高められるようなデザインを考えてくださいね!