Google Analyticsでエクセルなどでデータをいじる場合、 僕がよくやったのは、ディメンジョンを多めにセットしておいて(データ行は一万行を超えないようにする、indexするのが面倒な場合)、データを得た後、エクセルのピボットで軸を入れ替えながら、突出したポイントを探すことでした。エクセル2010でスライサー使えば、アドバンスセグメント+カスタムレポートという形が簡単かつ柔軟に実現できる。
それで、データに浸かっていじってれば、そのうち、イメージができることはできるけど、時間かかります。 そういう場合に、よく言われるのは、予め仮説を持って、その検証のみに時間を使う。サイトに対する今までの見識を持って仮説を出し、データでの裏付けを探す。見識がなければ、世間で一般的に使われる指標を一般的なディメンジョンで分類するという形になると思います。または、テストできる環境を整え、指標を対比する。。。などだったと思います。
で、それとは違うアプローチとして、データマイニングがあります。仮説なしで、結論みたい?なものをだしてもらう。ほとんどは凡庸な結論だけど、時々、宝ものが出てくるかもしれない。。。冒頭の本は、そのアプローチ、データマイニングのやり方を説明してくれてます。この本は、数式抜きでやり方を丁寧に説明してくれてるし、書き方からも読者に理解させようとする気が伝わってきます。とても良いです。
で、その中の分析木をやってみました。ただ、分析木は、マイニングというより、シンプルな結論の提示という気がします。なので、この本にあるように、mvpartというライブラリを使います。コードは最後に提示します。
冒頭のグラフが図で、結果そのものが下記。
split:分岐基準、n:その基準での観察数(セッション数), loss: n回は間違え分類した。(分類名が続いてる。今回なら、bounce, nobounces)
========================================================
n= 226
node), split, n, loss, yval, (yprob)
* denotes terminal node
1) root 226 79 bounce (0.65044 0.34956)
2) visitCount>=6.5 22 1 bounce (0.95455 0.04545) *
3) visitCount< 6.5 204 78 bounce (0.61765 0.38235)
6) landingPagePath=/cookie/utma,2010-03-11-16-53,igation_analysis,ng-apis/gaq-push 132 41 bounce (0.68939 0.31061)
12) visitCount< 3.5 130 39 bounce (0.70000 0.30000) *
13) visitCount>=3.5 2 0 nobounce (0.00000 1.00000) *
7) landingPagePath=/ 72 35 nobounce (0.48611 0.51389)
14) medium=(none),organic 48 22 bounce (0.54167 0.45833)
28) visitCount< 4.5 44 19 bounce (0.56818 0.43182) *
29) visitCount>=4.5 4 1 nobounce (0.25000 0.75000) *
15) medium=referral 24 9 nobounce (0.37500 0.62500)
30) visitCount>=2.5 2 0 bounce (1.00000 0.00000) *
31) visitCount< 2.5 22 7 nobounce (0.31818 0.68182) *
=========================================================
226セッションで79が直帰と間違え分類で、
訪問回数で6,5で分かれて(22(間違え1) – 204(間違え78))
ランディングページで分かれて、(トップページと、それ以外, 132 – 72)
その子供たちは、また訪問回数やら、メディアやらで分岐される。
数字自体は、まあそうだろうなあ、、というもので、それで終わりだけど、ガイドラインにはなると思う。
分類項目の設定は、こちらで決めたり、訪問回を連続変数にしたりしてるのは、予断があるわけだし、最大の問題点として、分析単位がセッションになっていて、ユーザー単位になってない点があります。同一のユーザーが複数回記録されているデータというのは留保しないといかん。
ただ、それでも、ある一定の基準に沿って、ディメンジョンの項目を整理して、差異の大きい部分だけを抽出しているので、データから結論を得るプロセスとしていいのではないかなと思います。
あと、枝分かれの基準のジニ係数の計算をちょこっとだけやってみる。
(詳しく(正しく?)は、上記の本、データマイニング入門の本を読んでください)
最初の枝分かれは、訪問回数の6.5 ト表示なので、1-6, 7以上と分かれてる。
そのジニ係数は、
7回以上で 1 – ((22/23)^2 + (1/23)^2) => 0.087
6回以下で 1- ( (78/204)^2 + (126/204)^2) => 0.472
これを加重して、(23/226) * 0.087 + (204/226) * 0.472 => 0.435
元々のルートのジニ係数は、1 – ((79/226)^2 + (147/226)^2) => 0.455
なので、分岐基準は、0.02となる。他の切れ目でやって、分岐基準が小さくなることを確認したいけど、パスします。。。
以下コード。
RGoogleAnalyticsは、cranにはないので、ダウンロードしてinstallする。
あとは、email, password, tableidを、各自でセットするれば、同じように動くと思う。画像ファイルの保存場所と。
library(RGoogleAnalytics)
library(mvpart)
##GAのデータを取得する
ga <- RGoogleAnalytics()
ga$SetCredentials(mailaddress, password)
query <- QueryBuilder()
query$Init(start.date = "2011-01-01",
end.date = "2011-12-15",
dimensions = "ga:medium,ga:region,ga:date,ga:hour,ga:visitCount,ga:landingPagePath",
metrics = "ga:entrances,ga:bounces",
sort = "ga:date",
table.id = "ga:xxxxxxxxx")
data <- ga$GetReportData(query)$data
##データの整理
if(!is.null(data$visitCount)) data$visitCount <- as.numeric(data$visitCount)
data$date <- as.Date(data$date, "%Y%m%d")
data$wday <- weekdays(data$date)
data$wnum <- format(data$date, "%w")
##Langind Pageを上位(entrance数)5つに絞る
top5.lp <- arrange(
ddply(data, .(landingPagePath), summarise, sum=sum(entrances)),
-sum
)[1:5,"landingPagePath"]
dt0 <- subset(data, landingPagePath %in% top5.lp)
##URLが長いのがあるので、後ろから15文字だけにする
dt0$landingPagePath <- sapply(dt0$landingPagePath, function(x){ l <- nchar(x); substr(x, l-15, l)})
##top5.reg <- arrange(ddply(data, .(region), summarise, sum=sum(entrances)), -sum)[1:5, "region"]
##dt1 <- subset(dt0, region %in% c("Tokyo","Osaka","Aichi","Fukuoka","Kanagawa"))
##1セッションで1レコードの形にする
dt0 <- within(dt0, nobounces <- entrances-bounces)
a0 <- adply(dt0, 1, function(x)data.frame(engage=rep("bounce",x$bounces)))
a1 <- adply(dt0, 1, function(x)data.frame(engage=rep("nobounce",x$nobounces)))
dt0 <- rbind(a0,a1)
##いらないディメンジョン、指標を外しておく。以下の書式?なら元から無い項目でもOK
dt0 <- dt0[, !names(dt0) %in%
c("visits","bounces","nobounces","date","region","hour","wday","wnum","entrances")]
##RPARTの部分
ret <- rpart(engage ~ ., data=dt0, method="class",cp=0.01)
png("~/Dropbox/cart.png",width=800,height=600)
rpart:::plot.rpart(ret, uniform=T, branch=1, margin=0.15)
rpart:::text.rpart(ret, all=T,pretty=0,fancy=T,digits=3,use.n=T)
dev.off()
plotcp(ret)
出稿する側は、キーワードとそれにヒモ付た広告を出す。(管理上、いろんなグルーピング機能があって、それらの上位グループ(広告グループ、キャンペーン)で制御が色々入れられる)。
ユーザーが検索する。 と、検索語にマッチしたキーワードに紐づいた広告が表示される。 ユーザーから見ると、検索語を打ち込む => 広告文を見る => ランディングページ訪問という流れ。
ここで大事だと思った点の一つは、ユーザーは出稿側がシステムに出す出稿キーワードは見ないし知らないという点。(完全一致はそのままなので、広告を出す側のキーワード設定とユーザーの検索語が一致するが、) 。
というのは、リスティング広告の管理をしてると、キーワード単位(マッチタイプ、テスト機能で分化管理、グループ、キャンペーンで統合管理されるが)で考えがちだと思われるのだけど、ユーザーサイドの行動である実際のクエリーを出発点に組み立てる事は大事だと。ユーザーは自分のクエリーをだして、広告文をクリックして、サイトに行く。ユーザーの頭に出稿する側のキーワードは入ってない。部分一致の場合。
ユーザーの行動プロセスを起点に考えると、上記のような流れ。で、その前提でデータを見えるようにしないとイカン。
もちろん、google, yahooの入札システムサイドでも上手く立ちまわる事は大切(CPC, 順位などの管理)だ。 そして、こちらも巨大なブラックボックスで、仮説、検証サイクルが廻る事にはなる。
で、
上記の点を念頭にしたとき、僕としては、以下の2点、A,Bを抑えたときに、作業が楽になった。
A .クエリーデータの取得(Google Analyticsで)
B. adwords側でのクエリーデータのマッチタイプ別の分別管理。(yahooは楽にならなかった、、、)
1.yahooリスティング
これは、独自に取らないと行けない。また取れてもadwordsのようにリスティング側のデータを教えてくれないので、クエリーデータを取るだけになるが、それでもやる必要がある。
方法としては、一般にはutm_source, utm_medium, utm_camapignを付けてということになっているが、あんまり有効な方法には見えない。この方法だと、クエリーデータはリファラーから取って、cookieのutmzのutmctrに入るはずだけど、utmパラメータを設定すると、リファラー内のキーワード情報は、日本語のまま入ってしまう。で、エンコードしてcookieに入らないと、最終的なデータで文字化けやデータ不取得となることがあるのだ。通常の場合(utmパラメータを使わない)と違う仕様にした理由は分からない。マルチバイトの人が声を上げてないだけかもしれないし、他に理由があるのかもしれないし、僕の勘違いかもしれない。 でも、この辺りのデータ不備で悩んでる人は多いと思う。
で、utm_xxxx を使わないとすると、、、ヤフーリスティング側の仕様を利用する方法が良い。
yahooリスティングからクリックされた時には、ヤフーがパラメータを付けてくれる。それを使う。
クエリー情報、キーワード情報、マッチタイプ情報、キャンペーンID情報、広告グループID情報、広告ID情報 をURLパラメータとして付加してくれてる。これを素直にGAに入れれば良い。
その方法としては2つ。サイト内検索を使う。 or トラッキングAPIを使う。
サイト内検索は、2つのパラメータ情報を取得できる(独立した?ディメンジョンで) ので、キーワード+クエリー、クエリー+マッチタイプとい形で取得しておく。サイト内検索の設定に、ovraw, ovkey, ovmtcとかを設定すれば良い。サイト内検索でのディメンジョン情報は、ゴール指標と結びついているので、コンバージョン測定もやり易い。問題はcookieに情報が入るわけではないので、再訪問データとのかけ合わせが出来ないけど、これは通常のデータ分析でも難しいので、要らない(僕は)。
トラッキングAPIは、このへんだけど、やったことない。その上、問題があって、URLパラメータの情報は、ヤフーが日本語にして(エンコードせずに)付加してくれてるので、上で書いたデコード問題が発生しそう。ただ、クエリー情報以外の情報(キャンペーンID,広告グループID、広告IDとか)を収納できそうなので、これと、後で、ヤフーリスティング側からのIDの情報とJOINすれば、かっちりとしたデータが取れるとは思う。 集計してそのデータが活かせるかは、分からないけど。
と書いたように、サイト内検索の機能を使うのが手軽で良い。正統的な方法のutm系のパラメータとも共存して測定できるし。 ただ、APIを使う人は、ランディングページのパラメータを取り出して、エクセルなどで処理すれば、こんな方法は要らないかもしれない。
サイト内検索はパラメータによるデータの振り分け機能と考えると、GAの応用の幅が広がる。
あと、GAで実際に集計するプロファイルからは、yahooリスティングのパラメータを除去しておかないと、ランディングページの種類が大量になり、コンテンツ単位の集計は破綻すると思うので、プロファイル設定はキチンと手を入れておく必要。生データ用は、別のプロファイルで残しておく。
ヤフーはこれくらいはさっと思いついてやった。GAに詳しかった?人の立場としては… ただ、応急処置的だし、javascriptでもっとやりたいとも思ったけど、、、全部自分で管理できないので、それは無理だし、それ以上のデータを持っても、サイトのパフォーマンス上げられる自信もなかった。リスティングの運営は手間暇かかるし、ビジネス的にはリスティングの運営のがお金に響くので、リスティング運用に時間をかけるのが本筋。また、リスティングの情報は質が高い(解釈しやすいし、相場情報になってる)
ヤフーリスティングは、情報が国内に限られるので、adwordsよりノウハウをネットで探すのが難しかしい。ただ、サポートは手厚いので、キチンとサポートを利用できる体制だと運用が楽かも。そういう意味ではある程度、組織だって運営できる所に利点があるのかな。代理店制度とかそのへんはよく分からない。
2. adwords
クエリーデータを起点にデータを取得するの続き。
アドワーズに関しては、自動設定にしておけば、クエリーデータは、アドワーズ側のレポートで見られるし、GAにも送ってくれる。なので、上記のヤフーの計測レベルは既に達成されている。
ただ、GA側のadwordsデータを使うと、もっといろいろ利点があるので書く。
まず、アドワーズのクエリーレポートはデータが来るのが遅い。一緒に提示されるデータで遅い物があるのか、クエリーデータがでるのに、2,3日掛かったような気がする、、、
一方、GAの方は、通常のクリックデータと同じなので、通常?、一時間以内にはデータが乗る。
なので、アカウントのスタート時とか、大規模の修正を掛けたときはGA側でクエリーのデータを追うべきだ。キーワードの除外設定は早ければ早いほど予算を残せるし、有効なクエリーを見つけて、キーワード登録するのが早ければ早いほどコンバージョンが実績で残る。
また、APIを使うのが前提になるけど、adwordsのクエリーディメンジョンに、他のディメンジョンも追加できるし、通常のメトリックスやゴール指標もデータとして乗っかるので、かなり細かく追っていこうと思えば、できる。追っかけて成果を出す解析スキルはないけど、、、、
あと、アドワーズレポートとの違いはまだある。クリック単位ででデータ(ディメンジョン、指標)が見られるけど(正確にはGAのディメンジョンで細分化出来る範囲で)、順位ディメンジョンの他にトップポジション、右ポジションという、広告配置位置のディメンジョンでも指標がだせるので、パフォーマンスの違いが測定できるし、adwordsで順位が一位だが、RHSになってるものもチェックできる。(必要なら広告ランクを増して、TOPに持っていかないとイカン)
* 先週あたり、アドワーズ側でもデータが出るようになったらしい。ただ、それでも、GAの方がデータの粒度を細かくできる。多くのディメンジョンのあるデータキューブ(アナリティクのデータはそう考えると良いはず。多次元データベース)みたいなのに組み込まれるわけだし。だと。 サイト内データ指標ともある程度ひもづくし。できない組み合わせもあったけど。
と、GA側のデータなら、adwordsのデータはかなり細かく、そして迅速に分かるので、有効に使うえる。
あと、GAとは関係ないけど、adwordsの管理の話を少し。
リスティングを経験する中で、最初に戸惑ったウチの一つは、部分一致と完全一致の所だったような気がする、、、他にもあっただろうけど、、、
メイン?なキーワードは、完全一致と部分一致の両方で出稿していくことになると思うけど、除外設定の知識も最初はなかったので、設定に悩んだ記憶がある。除外設定をしったあとは、完全一致と部分一致を区別するには、(部分一致+ 完全除外一致) で部分一致の出稿すれば、完全と部分の分離ができると分かって楽になった。
基本的に複数の広告グループで、完全グループと部分グループを別々に作るのが良いと思う。他の人の現場の方法は知らないけど、ここもそう言っててる。http://certifiedknowledge.org/blog/3-strategies-for-organizing-your-match-types/ (キャンペーン単位で分けるのもありかもしれない。 (このサイトは有用な情報が多い。他にも良い記事がある。)
この辺りは、adwordsの話。 ヤフーリスティングは除外設定が、フレーズ一致的な除外に固定されているの、上記のことはできず、入札単価の調整などになる。でも、単価調整方式だとCTRの履歴がどうしても歪みがちで、広告ランクが部分一致側に高く出て、マッチタイプの分別コントロールが非常に難しくなることがあると思う。 いい対策はないかと、ネットで探しても、この辺りで良い情報は見つからなかった。除外もキレイに出来ないし。この辺りの秋に改正されるらしい。
しかし、ヤフーリスティングとadwordsを同時に利用すると、adwordsすごいなあと思う。全体のシステムもすごいのだろうけど、adwordsのような数字でいっぱいなものが、ウェブのインターフェースで運用できるのにも驚いた。ウェブ上で情報をブラウズして操作するシステムで、自分が知る限り最高のモノの一つだと思う。salesforceとかを使うともっとすごいシステム(UI)ってあるのだろうか? うーん、証券システムかなあ、、こっちもすごいかもしれない。バックエンドは知らない。
まとめ
リスティングについて、ほぼゼロベースの知識で望んだ時の経験の記憶の棚卸です。GAのついての知識はあったので、それを活かす形でやった記憶の掃き出しです。 新しいGAのことについて(新しいUIとか間接効果とか)は知らないので、聞かないでください。
そういえば、アドワーズのサーチファネルも、クエリーパスを時々見ると、ある種の感覚的なヒントがつかめると思う。定量的な分析はスキルが不足してる。僕には。
あと、リスティング広告の運用については、無駄なクリックの削減が大きなテーマだと思う。で、その最初の一歩は、出稿キーワードと検索クエリの対応関係をつかむとやりやすいかなと。出稿キーワードの感覚も掴めるし。 GAを使うと、ヤフーリスティングの管理画面よりその辺りが明確に見えてきます。そして、adwordsも、adwordsのレポートデータより、GAのレポートのが便利な点が多いです。
あと、ヤフーリスティングに関しては、要らないサイトを除外できる機能は必須機能だと思う。全体のクリックの2割がヘン?なサイトにクリックされてたとしたら、25%余分な贅肉がついたCPAで、競合と勝負してることになる。それで、運用がジリ貧な循環になり、本来できた運用ができなくなって撤退となる可能性すらあるわけで、単純な知識の差で永続的な差が付きかねない。
また、全然クリックされなくても、品質インデックスに悪影響を与えない可能性も無くもないので、クリックされない外部サイトも早めに除外する方が良いかも(yahooは外部サイトのクリック履歴を組み入れないと宣言してない。adwordsは除外設定自体が無いけど、QSにはカウントしないと宣言してる)
以上です。
あと、アドワーズの全般に向こうのフォーラムに出たベストプラクティスを、メモ書きしたものがあった。リンクを張っておきます。https://sites.google.com/a/abc-analytics.com/adwords/forumno-besutopurakutisuno-matome
リスティング広告は、データがスグに出るし、競合との勝負という点があるので独善的になりにくいし、adwordsに関してはテスト機能+レポート機能も強化されて、思いつきレベルでもいろいろ試せるし(テストでなくても試せるけど、比較はしにくい)。サイトの継続的な改善にはまずはリスティングというのは良い方法だと思った。その後もニーズを探るのもリスティングが良さそうでもある。ヤフーリスティングもGAとうまい具合に統合できるといいなあと思った。
]]>時間系列の記憶は、人間の記憶の中でも頼りになる方。超整理法のアドバンテージは、ここにあったはず。 で、月間レポートを書く場合に、時間系列のヒートマップだと、人間の記憶とレポートの記録が、上手くつながる気がする。なので、ヒートマップ(時系列)が好き。
ggplot2は、簡単にヒートマップが出せる。
例として、このブログのGoogle Analyticsの4月のデータ。
時間、日付け、訪問数、平均PVの4つが入ったデータフレーム。
R> str(abc)
‘data.frame’: 720 obs. of 4 variables:
$ hour : int 0 1 2 3 4 5 6 7 8 9 …
$ date : Date, format: “2011-04-01″ “2011-04-01″ “2011-04-01″ “2011-04-01″ …
$ visits: int 1 1 0 0 1 0 1 0 2 3 …
$ apv : num 1 1 0 0 1 0 1 0 1 1 …
ggplot2を読んで、ggfluctuation。データ型は、テーブル型でもいいし、3カラムのデータフレームでもいい。今回は、まずはapv(average-page-views)を抜いて、3カラムデータフレーム。
library(ggplot2)
ggfluctuation(abc[,-4], type=”colour”)
でも、ggfluctuationのヘルプを見ると、type=colourは traditionalの形だそうだ。
今は、大きさそのものを出す方が良いという認識?
ggfluctuation(abc[,-4])
ただ、ggfluctuationは、拡張性?に乏しいような気がする。
geom_pointでcolor, sizeを指定して、4種類のデータ(時間帯、日付、訪問数、平均PV)を示す。
ggplot(abc, aes(hour, date, colour=apv, size=visits) + geom_point()
赤みが付くと、平均PVが高い。大きさは訪問数。
セッションの量と質と、時間帯+日付を示す。
今回は、セッションの質を、平均PVにしたけど、
ECサイトなら売上(セッション辺り)とか、CVRとかを使えば良い。直帰率でもいい。
2010年の秋にGoogle Analyticsの加重並び替えが導入されました。Wikiの方の紹介。新機能として紹介されたに似た感じのものを、アドバンスセグメントでやろうとするものです。
ちょっとズルですが、APIでデータを取得するのが前提です。
APIでのデータの取得は、実は簡単で、
で出来ます。他にも、色々なツールがあります。
今回は、Cの自作ツールを使ってデータを取得しておきます。
閲覧開始ページ、キーワード、開始数、直帰数を取ります。
で、タイトルの話のエクセルの貼り付けます。ここから、本題です。
コピペしたあとは、テーブルにします。
もう少し、下ごしらえが続きます。
entrancesの集計値は、下図のようにススメます。
(テーブル名が英語だと補完が効いて、マウス無しで^^です)
式は、=SUBTOTAL(9,table1[entrances]) になります。
同じようにbounces(直帰数)も計算します。=subtotal(9,table1[bounces]) ですね。
bounceRate(直帰率)は、この2つを割り算します。
ここで、もう一回、画像。
ここから、本当の本題であった、加重ソートを入れます。
前提として、100回以上セッションがあった、キーワード+ページは、そのまま。ソレ以下のものを、全体の平均値と按分する方針です。
B1に 分かれ目の数字、100を入れておいて、TrueBounceRateの列を作りましょう。
上の数式を説明します。
entrancesが100以上なら、その列のBounceRateのまま。なので [@BounceRate]
以下なら、全体の平均値(E1)と[@BounceRate]を 全体のEntrances(C1)と列のentrances([@enttances])で按分する。
([@bounces]/$B$1 * [@BounceRate] ) + (1 – ([@bounces]/$B$1)) * $E$1
Googleの加重ソートは、たぶん似たような感じだと思う。
加重ソートはいろいろ本を読んだけど、理論的背景はよく理解できなかった。
2次元の正規分布の場合に、なんやからすると、上記のような単純な式でもOKという話だったと思うけど、よく理解できなかったので、公開レクチャーしれくれる人がいたらお願いします。@phar
で、ここまでは単に計算しただけど、ここからエクセルのテーブル機能が生きて来る。
ランディングページ単位のキーワードでの加重ソート、ある単語が含まれるキーワードデータの加重ソートとかが簡単にできる。
最初の画面で、
landingを/api ディレクトリ以下のものに絞る。
その後、TrueBouncecRateを昇順に。(自動で順列にならない、、、フィルターするたびに、並べ替えの必要がある。ここは、イケテナイ。)
まあ、それでもそれっぽいソートが出来上がる。landingページを絞った上での、加重ソート。
今回は、ディメンジョンがキーワード、閲覧開始ページという組み合わせだけど、ソレは自分でデータを持ってくるときに好きに選べばいい。
また、加重平均の按分の中心になる平均値(直帰率)も、テーブルでフィルタリングすると、subtotalでそのフィルタリングされたデータの平均値で計算し直されるので、都合が良い。
あまり、データ数が少なくなるとだめだけど、そのデータ全体での平均値を適用して計算しなすのは、フィルタリング前のデータの平均値を持ち出すより適切なはずだ。
google analyticsには、加重ソート機能がありますが、似たような事をエクセルでしました。
エクセルのテーブル機能を使うことにより、簡単に特定ディメンジョンの加重並び替え(条件は複数でもOK => アドバンスセグメント)ができることを図示しました。 これは、たぶん、今のレポート画面ではできないことだと思います。 ただし、並び替えのアルゴリズムは違うのでしょう。
冒頭のリンクにも 今回のようなことをやった記録があって(加重ソートが出始めたころに書いたやつ) 、実際の GAでの順番と比較したグラフがありますが、そんなにズレはないと思います。
そんなにってどんだけ? 主観です^^。前やったとき、順位相関とか計算してみたけど、それを用いるのが正しいのかさっぱり分からなかったし、数値も直感的に理解できなかったので、、、単純な加重ソートもGAのソートも、結果としてはそんなに変わらないと思いました。
確か、加重ソートをアドバンスセグメントでという話は、結構要望であったと思うので、擬似ですが、それなりに役に立つ作業工程の紹介だと思います。試してみてください。
]]>Rを使ったアクセスデータの集計(1)
plyrとggplotを使ってます。
(*)Rを勉強し始めたら、早めにplyrとggplot2を覚えるのが吉。
見通しがよくなると思います。アクセス数値の集計というより、Rの勉強エントリ。
アクセスの基本的な数値を集計して、同じ時間軸で並べます。
以下のものを図示します。まず下準備。認証まで
コード(認証)
#RGoogleAnalyticsをファイル内にダウンロードしておいて使う。
source("/home/shirai/ga/r/RGoogleAnalytics/R/RGoogleAnalytics.R")
source("/home/shirai/ga/r/RGoogleAnalytics/R/QueryBuilder.R")
#今回の目的のggplot2、同時に plyrとreshapeもloadされる。
library(ggplot2)
#オブジェクト的な使い方? dataframeの要素に関数がある
ga <- RGoogleAnalytics()
#mail, pwを自分の設定ファイルから取得する
ac <- read.csv("/home/shirai/.gacc.csv",header=T,stringsAsFactors = F)[1,]
#認証を通す
ga$SetCredential(ac$mail,ac$pw)
コード(クエリー、集計、グラフ化)
クエリーを組み立てて、データ集計、グラフ化まで
#queryオブジェクトを作ってリクエストを作る。ビルドパターンって奴?
query <- QueryBuilder()
id <- "ga:21600568"
start.date = "2010-01-01"; end.date = "2010-12-30"
query$Init(start.date=start.date, end.date = end.date, table.id = id,
dimensions = c("ga:date"),
metrics = c("ga:visits,ga:pageviews,ga:timeOnSite"))
#データ取得 $dataにデータが、それ以外にはレコード数とかもある
output <- ga$GetReportData(query)
data.b <- output$data
#使い易いように、カラム名を加工、日付データは日付型に
names(data.b) <- sub("ga:","",names(data.b))
data.b$date <- as.Date(data.b$date, "%Y%m%d")
#滞在時間は平均滞在時間に、pageviewは平均PVへ、いるものだけ残す
data.b <- transform(data.b, avStay = (timeOnSite/visits))
data.b <- transform(data.b, avPV = (pageviews/visits))
data.b <- data.b[, c("date","visits","avStay","avPV")]
#ずるして、avPVは3000(5分)以上はNAに。 異常値なので
data.b$avStay <- ifelse(data.b$avStay > 1800, NA, data.b$avStay)
#パッケージのreshape機能。いわゆる?行持ちのデータ(日付 x データ種類 x 数値)に
data.b.molten <- melt(data.b, id="date")
head(data.b.molten) #ちょっと出力
#| date | variable | value |
#| 2010-01-01 | visits | 9 |
#| 2010-01-02 | visits | 7 |
#| 2010-01-03 | visits | 9 |
#| 2010-01-04 | visits | 20 |
#| 2010-01-05 | visits | 32 |
#| 2010-01-06 | visits | 18 |
#
#ggplotで出力(x軸にdate,y軸にvalue:数値,
p <- qplot(date, value, data=data.b.molten, geom="line", main = "基本数値")
#ここで、グループ別に図示する機能 facet_gridを使う, 縦軸スケールは個別で
p <- p + facet_grid(variable ~ ., scale="free_y")
#見た目を調整して、ファイルに出力
p <- p + opts(axis.text.x = theme_text(size=5))
p <- p + opts(strip.text.x = theme_text(size=5))
p <- p + scale_x_date(major="1 month", format="%m月")
ggsave("basic.png", height=6, width=6, dpi=96)
ぎざぎざ。データの把握がしにくいですね。

対策としては、週別のデータにすればいいけど、それだと一日の変動の様子が消えてしまう。下で移動平均を考えることによって、曜日変動の除去を考えるけど、ページ別のセッション数も見ておく。
query$Init(start.date=start.date, end.date = end.date, table.id = id,
dimensions = c("ga:date,ga:pagePath"),
metrics = c("ga:uniquePageviews"),
max.results = 10000,
start.index = 1
)
ret <- ga$GetReportData(query,max.rows=50000)
pv.data <- ret$data
names(pv.data) <- sub("ga:","",names(pv.data))
pv.data$date <- as.Date(pv.data$date, "%Y%m%d")
ret <- ddply(pv.data, .(pagePath), summarise, pagesum = sum(uniquePageviews))
top5.urls <- ret[rev(order(ret[,"pagesum"]))[1:5],1]
pv.data.top5 <- subset(pv.data, pagePath %in% top5.urls)
上位5ページを表示
p <- qplot(date, uniquePageviews, data=pv.data.top5, geom="line", log="y")
p <- p + facet_grid(pagePath~., labeller = function(l,x)substr(x,0,30),scales='free_y')
p + opts(strip.text.y = theme_text(angle=0)) + scale_x_date(major="1 month", format="%m")
ggsave("visits.png", height=5, width=6,dpi=96)

累計で表示してみる
pv.data.top5.cumsum <- ddply(pv.data.top5, .(pagePath), transform, cumsum = cumsum(uniquePageviews))
p2 <- ggplot(data=pv.data.top5.cumsum, aes(date,cumsum,color=pagePath)) + geomline()
p2 + opts(legend.position="bottom") + scalexdate(major="1 month", format="%m")
p2 + opts(legend.position="bottom", legend.box="vertical")
ggsave("cumsumvisits.png", height=5, width=6, dpi=96)

積み上げのが比較しやすいのかも
10月過ぎから勢いがついたページがある。
query$start.index(1)
query$dimensions("ga:data,ga:pagePath,ga:source")
ret <- ga$GetReportData(query,max.rows=50000)
pv.data <- ret$data
names(pv.data) <- sub("ga:","",names(pv.data))
pv.data$date <- as.Date(pv.data$date, "%Y%m%d")
pv.data.top5 <- subset(pv.data, pagePath %in% top5.urls)
pv.data.top5.yg <- subset(pv.data.top5, source %in% c("yahoo","google"))
pv.data.top5.yg.cumsum <- ddply(pv.data.top5.yg, .(pagePath), transform, cumsum = cumsum(uniquePageviews))
p2 <- ggplot(data=pv.data.top5.cumsum, aes(date,cumsum,color=source)) + geom_line()
p2 + facet_grid(pagePath~.,scale="free_y",labeller=function(l,x)substr(x,1,20)) + opts(strip.text.y = theme_text(angle=0))
ggsave("upv_yg.png", width=5,height=5,dpi=96)

あんまり関係なさそう。あとyahooから来るのは一ヶ月遅い(かった)。
曜日効果を考慮することによって、日別の変動を捉えつつ、ギザギザ問題の解消を目指します。Rのdocompose関数を使って、曜日効果とトレンドを分離します。
基本のアイデアは、観測値を (季節分+トレンド+誤差) と考えて、7日間の移動平均をとれば、曜日効果はキャンセルアウトされる。
んで、誤差もキャンセルアウトとまずは考える。なので、移動平均はトレンドの値と考えられる。曜日効果分は、曜日ごとの平均を出して、全体の平均から引いて出す。
で、実測値から、トレンドと曜日分を引いたのが誤差分。
こんな考えらしい。細かくは曜日分や誤差分にトレンドを入れたりするみたいだけど、decompose関数は普通にそのまんまみたい。で、decompose関数でいきます。
#前のデータをそのままで visitsのdecomposeする。tsオブジェクトにする
#曜日効果なので、7日間を指定,日付に関しては無視
visits.c <- ts(data.b$visits, freq=7)
#decompose関数はそのまんま、入れるだけ
visits.d <- decompose(visits.c)
#ggplotで出力するので、data.frameに戻す
visits.d1 <- as.data.frame(visits.d[c(2,1,3)])
#NAが初めと終わりに3日づつでるので、除去
visits.d2 <- visits.d1[c(-1,-2,-3,-362,-363,-364),]
#日付を再代入
visits.d2$date <- seq(as.Date("2010-01-04"),as.Date("2010-12-27"),by=1)
#元データと合体して、列順を入れ替え
visits.d2$observe <- data.b$visits[c(-1,-2,-3,-362,-363,-364)]
visits.d2 <- visits.d2[, c(5,1,2,3,4)]
#meltさせて行持ちにして、グラフ
p <- qplot(date,value, data = melt.data.frame(visits.d2, id.vars="date"), geom="line")
p + facet_grid(variable~., scales='free_y')
ggsave("decompose.png", width=6, height=6, dpi=96)
セッション数の曜日別差異(合計は 0 )| 月 | 火 | 水 | 木 | 金 | 土 | 日 | 計 |
| 9.7 | 14.3 | 16.7 | 15.4 | 11.9 | -32.8 | -35.2 | 0. |
とりあえず、トレンドは見える感じです。
週刊平均値をプロットするよりは、ダイナミック。日別よりは見やすい。
エンゲージメントの測定として、滞在時間を対象にします
library(stringr)
query$Init(start.date=start.date, end.date = end.date, table.id = id,
dimensions = c("ga:date"),
metrics = c("ga:visits,ga:pageviews,ga:timeOnSite"))
d1 <- ga$GetReportData(query)
d2 <- d1$data
names(d2) <- str_replace(names(d2), "ga:", "")
d2$date <- as.Date(d2$date,"%Y%m%d")
d2 <- transform(d2, avTime = timeOnSite/visits)
#4月以降のデータにする(1−3月は計測方法が違うので)
d3 <- subset(d2, date >= as.Date("2010-04-01"))
dc.avtime <- decompose(ts(d3$avTime, f=7))
dc.visits <- decompose(ts(d3$visits, f=7))
#曜日別の滞在時間(結果画面で)
print(dc.visits$figure)
print(dc.avtime$figure)
#trendDataだけ持ってくる
d.bind <- data.frame(
visits = dc.visits$trend,
avTime = dc.avtime$trend,
date = seq(as.Date("2010-04-01"),as.Date("2010-12-30"),by=1))
#平均化によるデータのない部分を除去
d.bind2 <- d.bind[c(-1,-2,-3, -272,-273,-274),]
#グラフ化
p <- qplot(date,value, data=melt(d.bind2, id.var="date"),geom="line")
p <- p + facet_grid(variable~.,scale="free_y")
p <- p + scale_x_date(major="1 month", format="%m")
p + opts(ylab="上:セッション数 下:平均滞在時間(秒)
ggsave("trend_visits_timeonsite.png",height=6,width=6,dpi=96)
| 月 | 火 | 水 | 木 | 金 | 土 | 日 |
| 9.7 | 14.3 | 16.7 | 15.4 | 11.9 | -32.8 | -35.2 |
滞在時間(単位は秒数)
| 月曜 | 火曜 | 水曜 | 木曜 | 金曜 | 土曜 | 日曜 |
| 45.8 | 4.9 | 53.9 | 20.3 | 20.3 | (-)46.0 | (-)99.2 |
トレンドデータ(曜日効果除去後のもの)
なんかこれだけでは、よくわからんかも。 曜日別というより、他のセグメントを当たる必要がある。
日別の集計値としての、平均滞在時間ではなんとも言いがたい。
実は、GAはセッション滞在時間もセグメント情報として持ってる。
なので、他のセグメント情報と掛け合わせで、滞在時間によるセッションの分布がだせる。
指標側では平均しか見えないけど、こちらは分布まで見える。
ここでは、月別とメディア別とランディングページ別を見てみる
ただ、上でも書きましたが、滞在時間データそのものの信頼性には疑問はあります。
僕自身のRの演習が主目的になっちゃってます。
月別のセグメントも入れて、データを取得。滞在時間は ga:visitLength
#クエリーを組み立てる。その前は、前のコードから続いてるものがある
query$dimensions("ga:visitLength,ga:month")
query$metrics("ga:visits")
#前と同じくmax.rowsは10000に増やす
output <- ga$GetReportData(query,max.rows=10000)
output$total.result #=>5708
d1 <- output$data
names(d1) <- sub("ga:","",names(d1))
#バープロットは、通常はcountデータをとるけど、weight指定で合計もいける
p <- qplot(visitLength, data=d1, geom="bar", weight=visits, log="y")
p + facet_grid(month~.)
なんかみずらい、、、右側に月の表示。左側にセッション数、x軸は滞在時間だけど、、謎グラフになった。
あと滞在時間が、飛び飛びになってるが怪しいし、0秒がどこだか不明だし、、
全然だめ、、

横軸を詰める。facet_wrapで表示。
p <- qplot(visitLength, data=d1, geom="bar", weight=visits, log="xy") p + facet_wrap(~month)
こちらのがみやすい。

#http://tolstoy.newcastle.edu.au/R/e2/help/06/10/2836.html d2 <- data.frame(lapply(d1, rep, d1$visits)[1:2]) d2$visitLength <- as.numeric(d2$visitLength) qplot(x=d2$visitLength, data=d2, geom="density",binwidth=10) + facet_grid(month ~ .) ggsave(dpi=96,width=6,height=6,file="hist_month_visits.png")

なんか形は違ってきましたね、、、くらいか。
#中央値と平均値を求める
stt <- ddply(d2, .(month), function(x) data.frame(median=median(x$visitLength),
mean=mean(x$visitLength)))
#以下、グラフ出力今回は、barplotとdensityを合わせる
p <- ggplot(d2, aes(visitLength)) + geom_histogram(aes(y=..density..)) + geom_density()
p <- p + facet_grid(month ~ ., labeller=function(l,x)paste(x,"月",sep=""))
p <- p + opts(strip.text.y = theme_text(hjust=1, angle=0))
p <- p + geom_vline(data=stt, aes(xintercept=stt$median), color=I("red"))
p <- p + geom_vline(data=stt, aes(xintercept=stt$mean), color=I("green"))
ggsave("hist_month_visits2.png",width=6,height=10,dpi=96)
レジェンドが引けなかった、、、緑が平均値。赤が中央値。
複数のgeomがある場合のlegendの対象指定はどうなのだろう?

時間変化とは別にメディア別もやってみる。
query$dimensions("ga:visitLength,ga:medium")
query$metrics("ga:visits")
output.m <- ga$GetReportData(query,max.rows=10000)
d.m <- output.m$data
colnames(d.m) <- sub("ga:","", colnames(d.m))
#organic, referral, (none) に絞る
d.m1 <- subset(d.m, medium %in% c("organic","referral","(none)"))
#visits単位のレコードに
d.m2 <- data.frame(lapply(d.m1[1:2], rep, d.m1$visits))
d.m2$visitLength <- as.numeric(d.m2$visitLength)
p <- ggplot(d.m2, aes(x=medium, y=visitLength)) + stat_boxplot()
p <- p + coord_flip() + ylab("滞在時間(秒)")
ggsave("boxplot_medium_visits.png",width=6,height=6,dpi=96)

分類が大まかすぎる。下でキーワード別をやる
ddply(d.m2, .(medium), function(x) round(quantile(x$visitLength/60)))
メディア別滞在時間分布(分)
| medium | 0% | 25% | 50% | 75% | 100% |
|---|---|---|---|---|---|
| (none) | 0 | 2 | 12 | 22 | 33 |
| organic | 0 | 6 | 16 | 24 | 33 |
| referral | 0 | 4 | 14 | 23 | 33 |
上で月別、メディア別をやったけど、両者を合わせる。
#組み合わせ
query$dimensions("ga:visitLength,ga:medium,ga:month")
query$metrics("ga:visits")
output.mm <- ga$GetReportData(query,max.rows=100000)
d.mm <- output.mm$data
colnames(d.mm) <- sub("ga:","", colnames(d.mm))
#organic, referral, (none) に絞る
d.mm1 <- subset(d.mm, medium %in% c("organic","referral","(none)"))
#visits単位のレコードに
d.mm2 <- data.frame(lapply(d.mm1[1:3], rep, d.mm1$visits))
d.mm2$visitLength <- as.numeric(d.mm2$visitLength)
p <- ggplot(d.mm2, aes(x=medium, y=visitLength, color=medium)) + stat_boxplot()
p <- p + facet_grid(.~month) + ylab("滞在時間(秒)")
p <- p + opts(axis.text.x = theme_text(angle=90,hjust=0, vjust=0))
ggsave("boxplot_medium_month_visits.png",width=8,height=5,dpi=96)
結果

数字も出しておく
母数(セッション数)
cast(d.mm2, medium~month, length)
| medium | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 11 |
|---|---|---|---|---|---|---|---|---|---|
| (none) | 139 | 117 | 337 | 199 | 217 | 309 | 256 | 229 | 161 |
| organic | 842 | 1007 | 1194 | 1541 | 1477 | 1453 | 1586 | 1713 | 1392 |
| referral | 124 | 79 | 506 | 182 | 354 | 392 | 231 | 218 | 172 |
滞在時間平均値
cast(d.mm2, medium~month, function(x)round(mean))
| medium | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
|---|---|---|---|---|---|---|---|---|---|
| (none) | 671 | 723 | 722 | 903 | 741 | 702 | 822 | 813 | 962 |
| organic | 682 | 794 | 912 | 942 | 909 | 914 | 956 | 952 | 967 |
| referral | 590 | 780 | 800 | 892 | 860 | 832 | 867 | 973 | 1009 |
滞在時間中央値
cast(d.mm2, medium~month, function(x)round(median))
| medium | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
|---|---|---|---|---|---|---|---|---|---|
| (none) | 552 | 686 | 704 | 960 | 703 | 661 | 772 | 778 | 1022 |
| organic | 609 | 745 | 1002 | 1022 | 948 | 975 | 1016 | 1002 | 1002 |
| referral | 183 | 882 | 826 | 917 | 874 | 833 | 778 | 1002 | 1056 |
ここからは、キーワードを見ていく。指標はそのまま滞在時間。
ランディングページもセグメントに加えてみる。
##dimensionとmetricsを設定して取得。他の項目はそのまま
query$dimensions("ga:landingPagePath,ga:visitLength,ga:keyword")
guery$metrics("ga:entrances")
#前回のindexがclearされないみたい。1に戻しておく。
query$start.index(1)
out.k <- ga$GetReportData(query,max.rows=100000)
str(out.k)
得られるデータはこんな感じ
閲覧開始は15379回あった。レコード?数は13061
List of 3 $ data :'data.frame': 13061 obs. of 4 variables: ..$ ga:landingPagePath: chr [1:13061] "/" "/" "/" "/" ... ..$ ga:visitLength : chr [1:13061] "0" "0" "0" "0" ... ..$ ga:keyword : chr [1:13061] "(not set)" "abc-analytics.com" "analytcs.com" "analytics tracking code input" ... ..$ ga:entrances : num [1:13061] 96 1 1 1 1 3 1 1 2 1 ... $ aggr.totals :'data.frame': 1 obs. of 1 variable: ..$ aggregate.totals: num 15379 $ total.results: num 13061
d.k <- out.k$data
names(d.k) <- sub("ga:","",names(d.k))
d.k1 <- data.frame(lapply(d.k[1:3], rep, d.k$entrances))
str(d.k1)
こんな形のデータになる。閲覧開始数は消えて1セッション=1レコードの形に。(本当は開始数=セッションではないが)
data.frame’: 15379 obs. of 3 variables:
$ landingPagePath: Factor w/ 525 levels “/”,”%EE3%81%97%E3%81%9F”,..: 1 1 1 1 1 1 1 1 1 1 … $ visitLength : Factor w 1893 levels “0″,”1″,”10″,”100″,..: 1 1 1 1 1 1 1 1 1 1 …
$ keyword : Factor w/ 6904 levels “\”/nan\” アナリティクス”,..: 57 57 57 57 57 57 57 57 57 57 …
とりあえず、閲覧開始上位5ページに対象を絞る
top5.pages <- names(rev(sort(d.k1$landingPagePath))[1:5]) d.k2 <- subset(d.k1, landingPagePath %in% top5.pages) #使わないファクターをdrop d.k2 <- droplevels(d.k2)
上位30キーワードにデータをさらに絞る
top30.kw <- names(rev(sort(table(d.k2$keyword)))[1:30]) d.k3 <- subset(d.k2, keyword %in% top30.kw) d.k3$visitLength <- as.numeric(d.k3$visitLength) d.k3 <- droplevels(d.k3) str(d.k3)
こんな感じのデータソースになる 3347セッション,5ページの30キーワードについて,811通りの滞在時間
‘data.frame’: 3347 obs. of 3 variables:
$ landingPagePath: Factor w/ 5 levels “/”,”/measurehowmuchviewedingoogleanalytics“,..: 1 1 1 1 1 1 1 1 1 1 …
$ visitLength : num 1 1 1 1 1 1 1 1 1 1 …
$ keyword : Factor w/ 30 levels “(not set)”,”analytics tracking code input”,..: 1 1 1 1 1 1 1 1 1 1 …
グラフ化。透明度を半分にして、重なり表示
p <- qplot(data=d.k1, x=visitLength,y=KW1, alpha=I(1/2))
p <- p + facet_grid(LP1 ~ ., scales="free_y", space="free",labeller=function(l,x){substr(x,1,20)})
p <- p + coord_trans(x="log10")
p <- p + opts(strip.text.y = theme_text(angle=0))
p
ggsave(filename="landing_keyword_length.png", width=10, height=10, dpi=96)

似たようなキーワードを統合して扱わないとキーワード別にしても意味なさそう
なので、キーワードグルーピングをやる
さて、キーワードはバラバラになりすぎてて、全体の把握が難しい。
キーワードでは検索フレーズ単位で管理されているので、同じ意味でもたくさんの項目になってるせい。統合しないといけないのだが、やり方不明、、、
#(not set)を除く d.k2 <- subset(d.k1, keyword != "(not set)") #キーワードを" "区切りで文字ベクトルにする k1 <- sapply(as.character(d.k2$keyword), function(x) str_split(x," ")) length(k1) [1] 11406 #検索キーワードア辺り、平均2.7くらいの語数か R> sum(sapply(k1, length)) [1] 28921 #2,3語くらいが多い table(sapply(k1, length))
検索フレーズでの単語数
| 1語 | 2語 | 3語 | 4語 | 5語 | 6語 | 7語 | 8語 | 9語 | 10語 |
| 2138 | 3646 | 3603 | 1568 | 333 | 98 | 10 | 5 | 3 | 2 |
単語単位で見る
#これをベクトルにする loop以外に思いつかない、、、
ret <- ""
for(i in 1:length(k1)) ret <- c(ret,k1[[i]])
#よくわかってないけど、この形が便利そう
k.table <- adply(rev(sort(table(ret))), 1, function(x)x)
names(k.table) <- c("kw","cumsum")
#上位10単語を出力
k.table[1:10,]
単語単位で見るキーワード登場回数(単語別での累計セッション数)のベスト10
| 1 | 3876 | |
| 2 | analytics | 3748 |
| 3 | アナリティクス | 482 |
| 4 | アクセス解析 | 482 |
| 5 | googleanalytics | 463 |
| 6 | ユニークユーザー | 413 |
| 7 | ページ別セッション数 | 347 |
| 8 | cookie | 297 |
| 9 | セッション | 277 |
| 10 | api | 270 |
とりあえず、整理しやすい単位に分解はできた。
library(string r)
#整理
d.k1 <- d.k1[, c(-4,-5)]
#googleといっしょに検索された言葉
d.k.google <- d.k1[grep("google", d.k1$keyword),"keyword"]
d.k.google2 <- Reduce(c, str_split(d.k.google, " "), ac=F)
google という単語と一緒に検索された単語
| 325 | |
| analytics | 262 |
| ユニークユーザー | 99 |
| 閲覧開始ページ | 60 |
| アクセス解析 | 38 |
| ユニークユーザー数 | 32 |
| an | 25 |
| ページ別セッション数 | 21 |
| ユニークユーザ | 21 |
| アナリティクス | 17 |
| googleanalytics | 16 |
| sql | 16 |
| 使う | 16 |
| 見方 | 15 |
| 閲覧開始 | 14 |
他の単語にも適用する 上位8単語にする
ret <- sapply(top10, function(word, df){
kws <- df[grep(word, df$keyword),"keyword"]
Reduce(c, str_split(kws," "), ac=F)
}
, d.k1)
ret.top8 <- ldply(ret, function(x){
d <- sort(table(x),d=T)[1:8]
mapply(paste,sep=":", d, names(d))
})
#各単語ごとに、同時出現単語を上位8個抽出
out <- ldply(ret, function(x) sort(table(x),d=F)
#タテヨコ逆転
t(out)
検索フレーズに含まれたもの(冒頭の数字が出現回数)*最初は同じ単語なので、その単語のキーワード回数
| “google” | “analytics” | “アナリティクス” | “アクセス解析” | “googleanalytics” |
|---|---|---|---|---|
| “3876:google” | “3748:analytics” | “482:アナリティクス” | “482:アクセス解析” | “463:googleanalytics” |
| “2549:analytics” | “2577:google” | “235:google” | “148:google” | “26:閲覧開始ページ” |
| “463:googleanalytics” | “463:googleanalytics” | “74:googleアナリティクス” | “53:analytics” | “21:ユニークユーザー” |
| “236:アナリティクス” | “191:ユニークユーザー” | “51:ユニークユーザー” | “33:セッション” | “15:ページ別” |
| “218:ユニークユーザー” | “168:api” | “43:グーグルアナリティクス” | “17:api” | “15:目標到達プロセス” |
| “202:api” | “121:閲覧開始ページ” | “24:閲覧開始ページ” | “16:ユニークユーザー” | “14:複数ドメイン” |
| “153:アクセス解析” | “107:セッション数” | “21:ページ別セッション” | “12:グーグル” | “13:カスタム変数” |
| “117:apps” | “95:セッション” | “18:ページ別” | “12:外部リンク” | “12:セッション” |
内訳はこんな感じ。眺めるだけ、、middle wordでやればと想うけど、、、
#ごちゃごちゃしてきたけど、、、グループ名カラムを追加して、dataframeを作り直し
d.g <- do.call("rbind",lapply(top10, function(group) cbind(d.k1[grep(group,d.k1$keyword),],group)))
ddply(d.g, .(group), function(df)each(median,mean,length)(df$visitLength))
キーワードグループごとの、中央値、平均値、lengthはセッション数
| group | median | mean | length |
| 905.0 | 871.9 | 4668 | |
| analytics | 904.0 | 867.8 | 4487 |
| アナリティクス | 904.5 | 874.3 | 614 |
| アクセス解析 | 904.0 | 869.9 | 545 |
| googleanalytics | 953.0 | 903.5 | 474 |
| ユニークユーザー | 928.0 | 903.5 | 632 |
| ページ別セッション数 | 928.0 | 923.1 | 405 |
| cookie | 977.0 | 919.7 | 326 |
| セッション | 964.0 | 911.6 | 1369 |
| api | 973.0 | 915.8 | 285 |
滞在が10秒で、Yes or No
ddply(d.g, .(group), function(df)table(df$visitLength<10))
FALSEが10秒以上滞在セッション数
| group | FALSE | TRUE |
| 4182 | 486 | |
| analytics | 4018 | 469 |
| アナリティクス | 541 | 73 |
| アクセス解析 | 483 | 62 |
| googleanalytics | 425 | 49 |
| ユニークユーザー | 591 | 41 |
| ページ別セッション数 | 390 | 15 |
| cookie | 296 | 30 |
| セッション | 1271 | 98 |
| api | 252 | 33 |
グラフ描画(分布点と四分点表示プロットを合わせる)
p <- qplot(data=d.g, y=visitLength,x=group,alpha=I(1/10)) + geom_jitter()
p + geom_boxplot(alpha=I(1/3), color=I("red"))+ ylab("滞在時間(秒)") + xlab("キーワードグループ")
p + coord_flip()
ggsave(filename="landing_grouped_keyword_length.png", width=6, height=6, dpi=96)
結果
統計用言語なのですが、グラフ出力も充実しており、latticeというパッケージを使うと分類した上でのグラフ表示が一行でだせます(マニュアル見る時間がかかるけど)。なので、グラフを図示していきます。
下のような感じでデータを取得します。
#認証tokenを取得 auth <- getAuth(email, password)
#アカウント+プロファイル情報を取得 acs <- getAccounts(auth) >str(acs) str(ac) 'data.frame': 45 obs. of 4 variables: $ ac.ids : chr "xxxx";,"xxxx" ... $ ac.names: chr "名前1" ,"名前2" ... $ pr.ids : chr "1111111","222222" ... $ pr.names: chr "xxxx","xxxx", #プロファイル名で検索 > ac[grep("abc.*wiki",ac$pr.names),] ac.ids ac.names pr.ids pr.names 10 188512 abc-analyticsさん 25.... abc-only-wiki-except-me 12 188512 abc-analyticsさん 255xxx... #キーワードを取得(デファルトで過去一ヶ月) data <- getData(auth,id=2551xxxxx,metrics=c("visits"),dimensions=c("keyword")) [1] "https://www.google.com/analytics/feeds/data?ids=ga:25513728&dimensions=ga:keyword&metrics=ga:visits&start-date=2010-09-03&end-date=2010-10-03" [1] "visits" [1] "(1-874)/874" [1] 2 keyword visits 14 (not set) 341 573 googleanalytics タイトル別のコンテンツ index 29 651 utma 17 851 目標到達プロセス 画面 16 36 _gaq.push 15 221 funnel visualization 14 662 utmz 11 853 目標到達プロセスの放棄数 8 316 google analytics wiki 8 30 _gaq 8 > str(data) 'data.frame': 874 obs. of 2 variables: $ keyword: chr "(not set)" "googleanalytics タイトル別のコンテンD [1] "visits" num 341 29 17 16
それで、時間帯別の表示をlevelplotという機能を使って表示していきたいと思います。
セッション数を時間帯と地域別で取得
data <- getData(auth,id=25513728, + metrics=c("visits","goal3Completions"), + dimensions=c("date","hour","region"), + start.date="2010-08-30",end.date="2010-10-03", + max.results=10000) [1] "https://www.google.com/analytics/feeds/data?ids=ga:25513728&dimensions=ga:date,ga:hour,ga:region&metrics=ga:visits,ga:goal3Completions&start-date=2010-08-30&end-date=2010-10-03&max-results=10000" [1] "visits" "goal3Completions" [1] "(1-10000)/1060" [1] 5 date hour region visits goal3Completions 702 2010-09-21 15 Tokyo 11 3 92 2010-09-01 09 Tokyo 11 2 607 2010-09-16 18 Tokyo 9 1 920 2010-09-29 15 Tokyo 8 0 608 2010-09-16 19 Tokyo 8 2 178 2010-09-03 12 Tokyo 8 0 105 2010-09-01 14 Tokyo 8 0 923 2010-09-29 16 Tokyo 7 0 520 2010-09-14 19 Tokyo 7 0 474 2010-09-13 17 Tokyo 7 2
グラフにする。
levelplot(tapply(data$visits,list(data$hour,data$date),sum), col.regions=colorRampPalette(c("white", "red"))(256),layout=esuln
週末と夜間は空白に近い。
曜日別に傾向があるかもしれない。見てみる。
wdays <- c("a日","b月","c火","d水","e木","g土”) #表示順を揃えるため levelplot(tapply(data$visits,list(data$hour,wdays[as.numeric(strftime(data$date, "%w"))+1]),sum), col.regions=colorRampPalette(c("white", "red"))(256))
まあ、なにもない。深夜に起きる人は、木曜くらいから出始めるのか?というか、祝日補正してないせいだろう。
また、サイトによっては、キャンペーンやコンテンツ投入のタイミングが見えるかもしれない。
んじゃ、とりあえず、メディア別にして、期間を長くして平均を見てみる。
d.m <- getData(auth,21600568,metrics=c("visits"),dimensions=c("hour","date","medium"),start.date="2010-01-01",max.results=10000) d.m.a <- d.m[d.m$medium %in% c("organic","referral","(none)"),] > levelplot(tapply(d.m.a$visits,list(d.m.a$hour,wdays[as.numeric(strftime(d.m.a$date, "%w"))+1],d.m.a$medium),mean), col.regions=colorRampPalette(c("white", "red"))(256),sub="曜日別|メディア別”)
検索活動は集中するくらいしかわからない。今年の平均だけど、、
これでも、具体的なユーザー像にはならない。
そこで、ある特定のキャンペーンを行った場合の波及効果みたいなのをみたいが、出せるデータがないので、このブログの公開記事の閲覧数の減衰具合みたいなのを見てみる。
d.m <- getData(auth,21600568,metrics=c("entrances"),dimensions=c("hour","date","landingPagePath",”medium”),start.date="2010-08-01",max.results=10000) rev(sort(tapply(d.m$entrances,d.m$landingPagePath,sum)))[1] /multi-cookie-tracking 338
と一番多い、multi-cookie-tracingを見てみる。メディアタイプを、オーガニックと参照リンクに絞って表示
d.m <- getData(auth,21600568,metrics=c("entrances",”newVisits”),dimensions=c("hour","date","medium"),start.date="2010-01-01",max.results=10000) d.m.a <- d.m[d.m$medium %in% c("organic","referral"),] > levelplot(tapply(d.m.a$entrances,list(d.m.a$hour,d.m.a$date, d.m.a$medium),mean), col.regions=colorRampPalette(c("white", "red"))(256),sub="特定ページメディア別”)
9月1日の深夜に出して、リンク中心にアクセスが上がって、その後は検索にかかるようになる。しかし、検索に乗るのも早いものだ。
次のものは、新規に絞ってみたもの。本当に新規でないのもあるだろうけど。検索による継続的な流入はある。
d.m <- getData(auth,21600568,metrics=c("entrances",”newVisits”),dimensions=c("hour","date","medium"),start.date="2010-01-01",max.results=10000) d.m.a <- d.m[d.m$medium %in% c("organic","referral"),] > levelplot(tapply(d.m.a$newVisits,list(d.m.a$hour,d.m.a$date, d.m.a$medium),mean), col.regions=colorRampPalette(c("white", "red"))(256),sub="特定ページメディア別”)
あとは、、、、
会員情報などとマッチングしてるサイト(外部ソースとの照合は規約違反かも)なら、ユーザー属性別のアクセス状況などは、比較表示しやすいと思います。
検索ワードで属性を分けたり、地域と時間情報と天候情報(外部から引っ張る)で属性を仮定したりと思ったのですが、余裕ができたらやってみたいと思います。
とりたてて何か発見ができたわけではないですが、Rを使うと、こんな感じで手短にグラフ出力ができます。ただ当然ながら、学習曲線はキツイ。ここまで来るのに相当な学習時間がかかりました。データ分析ではなく、データ表示だけなのに、、、
以下コードです。RCURLとXMLが必要です。
]]>
*2010/09/14 _linkでアドレスバーからcookieにデータを取り込む際、日本語データがある(検索ワードなど)と、うまくデータの引継ぎができないと思います。(フォーラムに質問した)。使う方は気をつけて(?)ください。chrome, firefoxの場合。IEはOK。 エンコードしてればよかった。
僕自身はあまりマルチトラッキングについて馴染みがないのですが、時々、マルチトラッキング絡みの質問を、フォームから受けるようになってきたので、いろいろと頭の中を整理しました。読んでください。
また、マルチトラッキングがニーズとなるのは、ドメインやディレクトリで分けてデータを収集したいということなので、具体例も画像付きで書きました。下の方。
大まかな前提を。 google analyticsはビーコン型のアクセス解析ツールです。gifリクエストをサーバーに飛ばして、データを収集してレポートしてくれます。モバイル計測の場合を除き、javascriptで操作します。トラッカーオブジェクトを作り、データ置き場としてのcookieを操作しつつ、gifリクエストを発行します。
ですので、同一情報を複数のアカウントに送りたい場合は、トラッカーオブジェクトを複数発行して、このオブジェクトにアカウント情報をそれぞれいれてやって、送信すればいいだけです。
公式サイトには英語のものしかないですが、用例があります。http://code.google.com/intl/ja/apis/analytics/docs/tracking/asyncUsageGuide.html#MultipleCommands
ただ、ドメイン別・ディレクトリ別に情報を取って、専用のプロファイルに送りたいですよね。
3つのパターン。サブドメイン、クロスドメイン、サブディレクトリの場合を見ていきます。
ただ、もう少し、講釈を続けます。
*これは、僕自身の考えに基づいて書いています。計測がうまく行かないかもしれないので、うまく行かない場合は、ダメじゃないか!と怒るか、もしくは、ぜひ連絡ください。
ケース: aaa.xxx.comというサイトと、bbb.xxx.comというサイトを管理していて、この二つの横断的な情報を収集しつつ、aaa, bbb個別の情報を得たい場合です。
通常の方法としては、
_gaq.push([“_setAccount”,”UA-xxxxxx-yy”]); _gaq.push([“_setDomainName”,”.xxx.com”]); _gaq.push([“_trackPageview”])
という形を、aaa, bbbの両方に同じようにおけば、これで計測できるわけで何も問題ないですし、フィルターで切り分ければ情報を分けることも可能です。aaa, bbb別にアクセス情報を見られます。ただ、cookieをaaa,bbbで共有するために、サブドメイン独自でのセッション情報の管理(ユニークユーザ、新規・既存・リファラーなど)ができません。
そこで、cookieを3つ、aaa専用 bbb専用 aaa-bbb共用という形で用意して、専用の3つのプロファイルという形を用意します。
| aaa.xxx.com | bbb.xxx.coom |
| _gaq.push([“ab._setAccount”,”UA-for-aaabbb”]); _gaq.push([“ab._setDomainName”,”.xxx.com”]); _gaq.push([“ab.trackPageview”]); _gaq.push([“a._setAccount”,”UA-for-aaa”]); _gaq.push([“a._trackPageview”]); |
_gaq.push([“ab._setAccount”,”UA-for-aaabbb”]); _gaq.push([“ab._setDomainName”,”.xxx.com”]); _gaq.push([“ab.trackPageview”]); _gaq.push([“b._setAccount”,”UA-for-aaa”]); _gaq.push([“b._trackPageview”]); |
これで、3つのcookie(utm(a,b,c,v,z))セットができます。
このコードの説明をこれからします。
このsetDomainNameは、内部的に二つの働きをします。
cookieの所属ドメインの決定と、cookieの値にドメイン情報を持たせることです。
cookieの基本的な仕様は、ドメイン、パス別に指定して作成することができますが、javascriptからはドメインとパスはみえません、読む場合は見えないのです。ですので、cookieの値にドメイン情報を持たせることは意味を持ちます。(ドメインハッシュ値は他にもセキュリティ用途もあるみたいですが、よくわかりません)
s1-test-analytics.com
s2.test-analytics.com
画像の文字が細かいですが、.test-analytics.com(両方に存在)と、s1.test-analytics.com, s2.etst-analytics.comの3つがあります。
左側の赤四角が、ドメイン情報を現してます。.test-analytics.comなら 113074331。s1.test-analytics.com:154559162, s2.test-analtics.com: 155607801。
と、setDomainNameは、cookieのドメインと、cookieの値の最初の10桁数字(ドメインハッシュ値)を決めています。cookieの最初の値が、ドメイン固有(ハッシュ衝突がないとして)の値ですので、この値を見て、複数cookie時に、該当cookieを判断しています。ネームスペース的な機能とでも言えばいいでしょうか?
では、cookieが3つできてることは確認できたので、今度はドメイン別に機能しているかをみてみましょう。
次のようなケースを想定します。
セッション1: s1に訪問後、s2を訪問。
セッション2: s1のみ訪問
セッション3: 再度、s1のみ訪問
セッション4: s2のみ訪問(外部リンク経由(phar.awe.jp)
セッション5: s1を訪問
この場合、全体をカバーする情報は、5セッション。s1では4セッション。s2では2セッションと記録される形になって欲しいです。参照情報は、全体では(phar.awe.jp) s1はDirect, s2は(phar.awe.jp)となって欲しいです。
実際に図を見ていきます。
セッション1 s1を訪問。
同一セッション内で、s2を訪問。 s2の参照情報は、s1.test-analytics.comに。
セッション2に。(セッションの更新は、utmcのcookieを削除で行う) 両方のutmaのカウンタ(Valueの最後)は2に。
セッション3 同じようにカウンタがインクリメントされ、3に。
セッション4 phar.awe.jpから s2に遷移。全体のもの、s2のcookieが更新される(カウンタ(utma)、参照情報(utmz))
セッション5 s1を訪問(セッション4で、s2を訪問した情報は、test-analytcs.comの方のみ反映されている。
と、想定通りになりました。 .test-analytics.comで全体のセッション管理を。s1,s2は個別でセッション管理されてます。s1からs2の移動も外部参照元となりますし、訪問回数は、s1,s2は個別に管理されてます。
サブドメインは、これで問題ないですね。では、クロスドメインの例を見てみましょう。少し複雑で、問題含みです。
どこまでニーズがあるのかわかりませんが、これもみていきましょう。livedoorでは、この形で運用してるみたいですね。 あと、公式サイトのクロスドメイン計測の形です。シングルトラッキングで、前提が多少入りますが、ここらの情報は検索すればあちこちにあるのでいいと思います。
それで先程、クロスドメインがサブドメインより、少し複雑と書いたのは、cookieは他のドメインの値は読めないので、特殊なことをしてるし、サブドメイン計測とは同等の機能とはならないからです。
とりあえず、設定を考えましょう。二つのドメイン計測で、全体計測と、個別計測を実現する。
| test.analytics.com | phar.awe.jp |
| _gaq.push(“a._setAccount”,”UA-for-any”]); _gaq.push(“a._setDomainName”,”none”]); _gaq.push(“a._setAllowLinker”,true]); _gaq.push(“a._trackPageview”]); _gaq.push(“b._setAccount”,”UA-test-analytics”]); _gaq.push(“b._trackPageview”]); |
_gaq.push(“a._setAccount”,”UA-for-any”]); _gaq.push(“a._setDomainName”,”none”]); _gaq.push(“a._setAllowLinker”,true]); _gaq.push(“a._trackPageview”]); _gaq.push(“b._setAccount”,”UA-phar-awe”]); _gaq.push(“b._trackPageview”]); |
これで、この両者のドメイン間に onlick=’_gaq.push([“a._link”,this.href]);return false;’という形。
*2010/09/10 どうも、setDomainが”none”のものと、通常のものの実行順番が、上記のものと反対になると”none”で設定した方が上手く動かないように見える。バグなのかそういう仕様なのかは謎だけど、”none”を先にして、setDomainName無しをあとにした方が良いよう。もともと、クロスドメインで値を受け渡しするのは、ポリシーに反する部分もあるだろうから、異なるドメインの計測はやらない方が良いんだろう。
サブドメインでの計測では、cookieがメインドメインの元で共有され、変更が反映されてました。でも、クロスドメインはそれができません。urlのパラメータを読み込む形での受け渡しです。ですので、この受け渡しがないと、値の変更を反映できません。
とにかく、実際の図をみていきましょう。セッションを全体で3回行います。test-analyticsに2回。phar.aweに2回。またぐセッションがそのうち一回です。
最初のセッション。 参照元(utmz)は、s2.tes-analyticsからリンクを張りました。
_gaq.push([“a._link”,this.href])でクロスドメイン遷移。細かいですが、参照元は全体では s2。個別では test-analytics.comからと別れてます。
セッション2。test-analytics.comを訪問。カウンタ(utma)が増えただけ。
セッション3 phar.awe.jpを訪問。サブドメイン計測ではcookieが共有されていたので、カウンタが上がっていたのだが、クロスドメインはそれがないので、全体計測の方のカウンタ(umta)が3にならず。2のまま。
と、全体で3セッションあったのだが、全体を計測する utma(ドメインハッシュ Valueが1の方) では、
2回目の訪問が二回あって、3回目の訪問がない結果となる。また参照情報の変更も、url上のパラメータ渡しがないと、cookie値を共有する機会がないので、不整合な結果を作りやすい。
ショッピングカートのように、必ず特定のドメインからの遷移であれば問題はないでしょうが、結論としては、
クロスドメインでの全体・個別の分別管理は問題が多そうと、僕は思います。
さて、最後にもう一つトピックを取り上げます。ディレクトリでの全体・個別をマルチトラッキングで管理するです。ディレクトリの分別管理はニーズが多そうですが、固有の問題があります。
実は、僕が手伝ってるサイトでも、先日これをやろうとしたのですが、うまく行かなくて悩んだのです。
cookieの値がヘンだなあと、、
実は、ドメインの違いに関しては、google analyticsのcookieがドメイン情報を持っていたので、区別できたのですが、パスの情報は入れてくれてないのです。なので、複数cookieはできません。作る事はできても、カウンタのインクリメントは、どっちをするか不定です。参照情報のutmzは一個しかできません、最初に設定したパスで出るのみです。何も指定してなけば、”/” 。これは、最初にsetCookiePathで”/DDD/”としたのでこうなりました。
utmaは、二つ作ってはいますが、判別手段がないため、次回の更新はどちらかの値を取得して、二つとも、同じ値に更新です。実質的に、サイト全体での管理しか、しないことと同じになります。utmzを”/”で指定すればです全体ですし、utmzが”/DDD/”のみになれば、全体のセッション管理が狂います。
で、手段がないのかというと、ドメインを分けてやれば、個別管理できます。
http://www.lunametrics.com/blog/2009/03/06/cookies-tracking-multiple-accounts-ga/
@t32k さんの昔のエントリーも発見。
なので、ここで細かく書いてもしょうがないので、コードだけ書いておわりにします、、、、
と思ったのですが、lunameticsのコードだと、ドメイン名が同じなのでcookieが独立して動かない(ga.js側が)と思います。ドメインを分けるには、サブドメイン表記を使うか、クロスドメインの”none”を使うかだと思います。ここでは、noneを使う形の載せます、、、、
と思ったのですが、setDomainName(“none”)を使うと、セッションの更新がうまく行かない気が、、上のクロスドメイン時では問題なかったので、setCookiePathといっしょに使うと、動きがヘンになる????
なので、setDomainName(“.test-analytics.com”)とsetCookiePath(“/DDD/”)を一緒に使う形にします。
| 通常の場所 | 特定ディレクトリ(今回は/DDD/) |
| _gaq.push([“a._setAccount”, “UA-all”]); _gaq.push([“a._trackPageview”]); |
_gaq.push([“a._setAccount”, “UA-all”]; _gaq.push([“a._trackPageview”]); _gaq.push([“b.setAccount”, “UA-DDD”]); _gaq.push([“b.setDomainName”,”.test-analytics.com”]); _gaq.push([“b.setCookiePath”, “/DDD/”]); _gaq.push([“b.trackPageview”]); |
setDomainName(“none”)で作ったcookieは、FQDN所属(leading period ‘.’なし)のcookieになるので、ほかの場面でクロスドメイン用に、”none”を使う場合は使えません。サブドメンの設定を使うしかないと思う。
実際の例
セッション3回。/DDD/index.htmlを二回。/index.htmlを一回。
セッション1: /DDD/index.htmlを訪問。
注意: 所属Domainは双方同じ .test-analytics.comになってますが、ドメインハッシュ値は113… と115…と違います。これはデフォルトsetDomainNameなしを使うと、内部としては “test-analytcs.com”と “.”なしでドメインハッシュを計算するため。 .を付けとけば違うハッシュ値をとれる。で、今回は所属ドメインンが同じだが、パスが違うためcookieは存在できる。
セッション2: index.htmlを訪問
セッション3 /DDD/index.htmlを訪問
全体で3回訪問した。全体を把握する方の,115001218は、カウンタ(utma)が3になってる。個別ディレクトリ管理の113074331は、2回と想定通り。めでたし、めでたし。
ここまで、読んでくれる人がいるか疑問です(僕なら飛ばし読みだ)が、まとめです。感想の箇条書き。
Google Analyticsの、他のサービスと違う有名な仕様として、外部リファラーが無い場合のリピーターのリファラー情報は、前回のリファラー情報とするというのがあります。
直接訪問は前回のリファラー情報でレポートとして上がるという事です。最初からノーリファラーなら、ノーリファラーですが。
僕は、他のアクセス解析サービスはよく知らないので、どちらが有用なデータ表示方法かは分からないのですが、GAの仕様を決定した人は、こっちが良いと思ってそうしたんだと思います。
本当は、ユーザー単位で、トラフィック情報の変遷を見ることが出来れば、万事解決なのですが、GAは大きな会社のサービスなので、いろんな事に配慮しなければならず、ユーザー単位のトラフィック情報を見ることはできません。ユーザーグループを作って、カスタム変数に記録すれば、グループ単位では見られるようになりますが、それは置いておきます。
それで良い点はというと、直接訪問を必ず、どこかの参照元の情報に帰属させてる点です。アクセス解析の目的は、コンバージョンの向上にあるわけで、その要因となる流入元情報の寄与を割り振ってるわけです。2,3回目は、bookmarkから来たんだろうけど、一回目は、、、というデータ処理を省くことができるわけです。
もちろん、リピーターは、複数の外部サイトからやってることも多いわけで、その場合は上書きされていくので、きちんとした?データにはならないわけですが。
訪問回数と参照元情報をディメンジョンに、セッション数を指標にします。
データは、仮想の数字です。サイトの数字を参考にして、乱数を降った擬似的なデータです。 縦軸(Y軸)は、10の対数です。4なら1万。2なら100。 2,3回目までくるのは、1%とかの世界ですね。1以下で10以下です。
あと、データ集計期間の長さによってデータが偏るので、そこも注意した方が良いです。その集計期間の訪問回数というのは、Google Analyticsでは出せないです。出る数字は、全ての測定期間で、何回目の訪問だったかです。
参照元別で残存率が違う場合、主要な参照元の5回目残存率なんかは、気にしても良いかもしれません。この場合だと、Eなんかは2.8(630くらい)から1.8(63くらい)くらいと、10%くらいは残ってくれてます。ほかは、Dなんかは、5回目で、3(1000以上)から1以下(10)になります。
最終的には、コンバージョン率と絡めて、数字を吟味する必要はあるのですが、参照元によって残存率が違う、訪問回数が進むにつれ、1/10, 1/100になっていくけど、参照元に依っては生き残る率が高いのがあるのを気にするのもいいかもしれません。
Google Analyticsのノーリファラーを前回のリファーラー情報に割り振る仕様も、こういう見方をする場合は、データ処理が楽だと思います。
もちろん、あるユーザーの二回目セッションは参照元がAで、三回目はBという事もあるので、訪問回数の残存= ユーザーの残存率 とはならないですが、ある程度は類似するはずです。また、このデータ集計期間に、一回目が入らずに二回目から登場のパターンもありえます。GAは、ある期間内で、何回目の訪問だったかは教えてくれません。
ということで、参照元別に訪問回数別のセッション数を見て、この参照元はコアのユーザーに成ってくれやすい。 裏を言えばリピートしない、というのを把握する話でした。
実はどこかのサイトのデータ整理をしてて、対数グラフにしたら、それなりに見えたので、blogを書きました。 底を求める方法が分からず、何回か検索した。中学生の僕が見たら、僕を殴りに来たかも、、、630 => 10^2.8 は、頭に出なかった、、、
]]>前回、スクロール計測のレポートを自動配信する話を書いたのですが、今回は、Google Analtyicsのページ遷移データをグラフ化してメール配信する話です。
同じく、google apps script + google chart api でのメールによるレポート配信です。
あるページにおける、遷移上の前後のページは、WEBのレポート画面で見ることができます。ナビゲーションサマリーですね。ナビゲーションサマリーの数字の見方は、以前Wikiの方に書きました。
WEB上のレポート画面では、重複ページの回数を抜いて、パーセンテージにして表示してます。(移動先のデータ(nextPage)のデータがよく分かってないのですが、、)
今回は、Data Export APIでデータを持ってくるので、重複ページを抜かないとと思っていたら、uniquePageviewの数字がそのままの数字なので、端折って、uniquePageviewの数字を使いました。uniquePageviewを使う妥当性について、Forumなどで質問があったみたいですが、数値を見る分には大丈夫そうなので、uniquePageviewを使います。単に重複を抜くためだけですが。(もう少し考えてみた。けっこう注意が必要かも)
とにかく、ディメンジョンに, “priviousPagepath”と”nextPagePath”。指標に uniquePageviewを使います。
前回のデータを表示する時に、chart apiの文書を見てたら、おもしろいチャートの種類を見つけました。 Connectivity graphsとかいてあります。日本語だと 連結グラフ?
これも、パラメータを指定するだけで、グラフをimageにして返してくれます。またしても、パラメータの設定が難しかったのですが、PDFの説明書みたいなのを見ながら設定してみました。微妙に chart apiでの記述と、h本家の記述と違う部分もあると思います。subGraphも設定できるみたいで、それでクラスター表示みたいなのをしようと思ったけど、Google Chart Api側で、動くように設定する方法がわかりませんでした。(* google chart apiでの方法を知っていたら教えてください。)
前回も書きましたが、http://code.google.com/apis/chart/docs/chart_playground.htmlでできるチャート図を確認しながら、設定をいじれるので、便利です。
それで、実際にこのサイトのデータで生成してみました。メールレポートの場合は、google apps scriptでHTMLメールを使って、scriptの自動実行時間を指定すれば良いです。参考に、ちょっと違うけど、フォーム受付けを自動返信する話です。今回は、これを時間指定にするだけです。
できたimage画像です。 (画像クリックでできたURLそのものに遷移します)
閲覧開始数のベスト8ページのデータと、ページ遷移の組み合わせで数が多かったベスト10のデータを抜き出してます。頭に数字があるのは、そのページの期間内の閲覧開始数です。矢印線上にある数字がページ遷移の数です。 数字だけの奴(_108_)は、indexページです。閲覧開始数が108でした。
(自分自身に返ってる奴は、データの初期段階でパラメータが違うものをカットしたせいです。処理が粗いです。説明もゴニョゴニョですが、、)
サイトオーバーレイもいいですが、サイト全体のユーザーの動きはコチラのほうがイメージし易いと思います。
気を付ける点としては、上のリンク先の記事でも説明してるのですが、セッションベースのデータでないので、遷移の数は、ランディングからすぐに遷移した数ではないです。 secondPagePathもデータとしてはあるので、そちらでもグラフはかけそうではあるのですが、、、
あと、chart apiで生成できるピクセル数の大きさは、縦×横で、300000pxまでなのですが、このgraphVizでできるデータはそれを超えてます、、、、また、これはexperimentのマークがついてるので、仕様変更の可能性もありそうではあります。
それでも、自動化できたので、データを見せていただける方には、メールで日次配信します(コメントで遷移図希望と書いて下さい)。今回のは、Google Analtyisの初期設定で取れるデータなので、閲覧権限をもらえれば、それでOKです。 Auth認証でのデータ表示に対応するのは、詳しい人なら簡単にできそうなので、そういう人に任せます。
]]>前回、スクロール率をgoogle analyticsで計測した話を書きました。 レポート部分は、エクセルでの表示だったんですが、今回、自動化のひとつの方法として、タイトルの方法をやったので、報告します。
データを外部から引っ張ってきて、加工して、メール配信というのが、割合簡単にできます。
今回は、google analyticsのデータを引っ張って、加工して、HTMLメールで配信という形の紹介です。
GASの記述は、javascriptです。 google analyticsの集計の設定も javascriptですので、 javascriptは習得しがいのある技術かもしれません。
手順としては、URLFetchで google analytics Data Export Api からデータを取得して、それを google chart apiを使って、 HTMLメールで送る作業です。
URLを指定すれば、チャート画像を返してくれます。
ですので、変化する数字を自動的にグラフ化してレポーティングしたい場合に価値が高いです。
パラメータがたくさんあって、マニュアル(解説)が英語なのでとっつきにくいですが、データ、チャートタイプ、ラベル、軸、などの概念とapiでのパラメータが一致すれば、それなりに使えるのではと思ってます。
別別サイトにメモみたいなのを書きましたが、、、あまり参考にならないかも。
いろいろいじって、なんとか自動化にこぎつけたのですが、GmailのHTML表示の時に、imageタグの部分が上手く表示されなくて、いろいろ探したのですが、結局あきらめました。 たぶん、文字数の問題のような気がしてるのですが、、、どうなんでしょう。 Yahoo Japanの Web mailなら問題なかったです。
出力画像と スクリプトを貼りつけます。
スクロール計測とそのレポート配信を試してみたい方は連絡下さい(コメントでスクロール計測希望と書いてくだい)。 設定して、週次 or 日次で、特定のディメンジョンで切ったスクロール率のよる閲覧割合をメールレポーティングします。無料です。 以下の画像のような形でのHTMLメールが送られます。
画像レポートの後に、参考に、GASのスクリプトを貼りつけておきます。 貼り付けた以外にも追加の定義(関数、代入式)などがあるので、全部ではないです。
以下、レポート配信に使ったscriptの一部。
]]>1: function CRReport(id,name,title,address){2: //id = "ga:21600568", name="shirai", title="wordpress", address="kimiyuki[at?]gmail.com";3: var startdate; var enddate; var dimensions = []; var metrics = []; var filters = ""; var segments = "";4: var entries; var sort;var data;5: var tmp;6: var siteurl;7: startdate = getMyDate(-15); enddate = getMyDate(-3);8: dimensions = ["ga:pagePath,ga:hostname"]; metrics=["ga:visits"]; sort=["-ga:visits"];9: entries = getDataFromApi(id, startdate, enddate,dimensions, metrics, filters, segments, sort);10: pagePaths = [];11: entries.slice(0,3).forEach(function(entry){12: var el = entry.getElements("http://schemas.google.com/analytics/2009", "dimension");13: el.forEach(function(e){14: if(e.getAttribute("name").getValue() == "ga:pagePath") pagePaths.push(e.getAttribute("value").getValue());15: if(!siteurl && e.getAttribute("name").getValue() == "ga:hostname") siteurl = e.getAttribute("value").getValue();16: });17: });18:19: data = {};20: var w1 = [null,null, null]; var w2 = [];21: pagePaths.forEach(function(path){22: Utilities.sleep(500);23: w1 = [null,null,null], w2=[];24: var dim_sg = "ga:medium";25: dimensions = ["ga:eventCategory", "ga:eventAction", "ga:pagePath",dim_sg]; metrics = ["ga:uniqueEvents"]; sort = null;26: filters="ga:eventCategory==CompRead0523B;ga:pagePath==" + path;27: segments=null;28: entries = getDataFromApi(id, startdate, enddate, dimensions, metrics, filters, segments, sort);29: entries.forEach(function(entry){30: var ds = entry.getElements("http://schemas.google.com/analytics/2009", "dimension");31: var es = entry.getElements("http://schemas.google.com/analytics/2009", "metric");32: var w = new Array();33: ds.forEach(function(e){34: if(e.getAttribute("name").getValue()=="ga:eventAction") w1[1] = parseInt(e.getAttribute("value").getValue().replace("z",""));35: if(e.getAttribute("name").getValue()== dim_sg){w1[0] = e.getAttribute("value").getValue();}36: });37: es.forEach(function(e){38: if(e.getAttribute("name").getValue() == "ga:uniqueEvents") w1[2] = parseInt(e.getAttribute("value").getValue());39: });40: w2.push([w1[0], w1[1],w1[2]]); //値にして設定するため41: w1 = [null,null, null];42: });43: w2 = w2.sort(function(a,b){return a[1] > b[1]});44: if(data[path] == null) data[path] = [];45: data[path] = w2.map(function(x){return x});46: //data[path] = w2.map(function(e){return [e[0], Math.floor((e[1]/total)*100)]});47: });48: // Logger.log(data);49: html = outputChart(data, startdate, enddate, siteurl);50: Logger.log(html);51: MailApp.sendEmail(address, "CompRead","html mail", {cc: "kimiyuki[aat]gmail.com", htmlBody: html});52: }53:54:55: function outputChart(data, startdate, enddate ,siteurl){56: var html = "<div>" + startdate + "から" + enddate + "のセッション数上位3ページの精読率調査です。数字はセッション数です。</div>";57: for(e in data){58: //Logger.log(e);59: //Logger.log(data[e] instanceof Array);60: //Logger.log(data[e].length);61: //Logger.log(data[e].toString());62:63: //making dimentionable data64: var dimmed_data = data[e].reduce(function(r, x){65: if(r[x[0]] == null){66: r[x[0]] = x[2];67: }else{68: r[x[0]] += x[2];69: }70: return r;71: }, {});72: //Logger.log(dimmed_data.toSource());73:74: var tmp = [];75: Logger.log("object="); Logger.log(dimmed_data.toSource() + "\n");76: for(c in dimmed_data){tmp.push([c, dimmed_data[c]])};77: dimmed_data = tmp.sort(function(a,b){ return a[1] < b[1]});78: Logger.log("dimmed_data=");79: Logger.log(dimmed_data.toString()+"\n");80: var label = data[e].map(function(x){return x[1]}).unique();81: var data1 = data[e].filter(function(x){return x[0] == dimmed_data[0][0]}).map(function(x){ return x[2]});82: var data2 = data[e].filter(function(x){return x[0] == dimmed_data[1][0]}).map(function(x){ return x[2]});83: var data3 = dimmed_data.length < 3 ? "" : data[e].filter(function(x){return x[0] == dimmed_data[2][0]}).map(function(x){return x[2]});84: //Logger.log(label);85: //Logger.log(data1);86: //Logger.log(data2);87: //html += "<tr><td>" + e + "</br>" + data[e].map(function(x){return x[0] + '=>' + x[1]}).join(',') + "</td></tr>";88: html += "<div><div>";89: html += makeChart(label, data1, data2,data3, dimmed_data[0][0], dimmed_data[1][0], (dimmed_data.length > 2 ? dimmed_data[2][0] : ""));90: html += "</br>";91: html += "<a href='http://" + siteurl + e + "'>" + e + "</a></div>";92: html += "<div> </div>" //全角空白を入れた。間隔を作るため93: }94: html += "</div>";95: return html;96: }97:98: function makeChart(label, data1, data2, data3, dataLabel1, dataLabel2, dataLabel3){99: var max = data1.concat(data2).reduce(function(r, e){return r > e ? r : e;}, 10);100: var chds = "chds=" + "0," + max;101: var chxr = "chxr=1,0," + max;102: var chco = "chco=4d89f9,c6d9fd" + (data3 != "" ? ",63C6DE" : "");103: var chdl = "chdl=" + dataLabel1 + "|" + dataLabel2 + (dataLabel3 != "" ? "|"+dataLabel3 : "");104: var chm = "chm=N,000000,0,,12,0,e|N,00FFFF,1,,12,0,e" + (data3 != "" ? "|N,0000FF,2,,12,0,e" : "");105: var url =106: "<img style='display:block;' src='http://chart.apis.google.com/chart?cht=bhg&chs=250x" + (dataLabel3 != "" ? "990" : "660") + "&chd=t:" +107: data1.join(",") + "|" + data2.join(",") + (data3 != "" ? "|"+data3.join(",") : "") +108: "&" + chds + "&" + chxr + "&" + chm + "&" + chdl + "&" + chco +109: "&chxt=y,x&chxl=0:|" + label.reverse().join("|") + "&chxs=0,ff0000,12,0,lt|1,0000ff,10,1,lt'/>";110: //Logger.log(url);111: return url //.replace("&", "&");112:113: }