Google Analytics アクセス解析 実践メモ、解説、疑問点の表明、データの見方 2011-12-18T04:45:20Z http://abc-analytics.com/feed/atom WordPress shirai <![CDATA[CARTで結論を絞る]]> http://abc-analytics.com/cart%e3%81%a7%e7%b5%90%e8%ab%96%e3%82%92%e7%b5%9e%e3%82%8b 2011-12-18T04:45:20Z 2011-12-18T04:44:15Z Continue reading ]]> cart

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)
]]>
0
shirai <![CDATA[リスティング広告運用についての雑感]]> http://abc-analytics.com/%e3%83%aa%e3%82%b9%e3%83%86%e3%82%a3%e3%83%b3%e3%82%b0%e5%ba%83%e5%91%8a%e9%81%8b%e7%94%a8%e3%81%ab%e3%81%a4%e3%81%84%e3%81%a6%e3%81%ae%e9%9b%91%e6%84%9f 2011-07-27T08:39:17Z 2011-07-27T08:39:17Z Continue reading ]]> チョット前に、ちょっとだけ、リスティング管理を経験しました。それで、知識の棚卸しをする意味で、私が思っていることを書きます。 google analyticsのデータの利用についても、織りまぜて書きます。記憶の掃き出しをしておきます。あいまいな部分や誤認識もあると思いますので、そういう前提で読んでください。(GAも、ここ数ヶ月は触ってないので、知識が古ぼけてきてます。サイトも更新してないですね。。。)

リスティング広告(サーチ)の流れ

出稿する側は、キーワードとそれにヒモ付た広告を出す。(管理上、いろんなグルーピング機能があって、それらの上位グループ(広告グループ、キャンペーン)で制御が色々入れられる)。

ユーザーが検索する。 と、検索語にマッチしたキーワードに紐づいた広告が表示される。 ユーザーから見ると、検索語を打ち込む => 広告文を見る => ランディングページ訪問という流れ。

ここで大事だと思った点の一つは、ユーザーは出稿側がシステムに出す出稿キーワードは見ないし知らないという点。(完全一致はそのままなので、広告を出す側のキーワード設定とユーザーの検索語が一致するが、) 。

というのは、リスティング広告の管理をしてると、キーワード単位(マッチタイプ、テスト機能で分化管理、グループ、キャンペーンで統合管理されるが)で考えがちだと思われるのだけど、ユーザーサイドの行動である実際のクエリーを出発点に組み立てる事は大事だと。ユーザーは自分のクエリーをだして、広告文をクリックして、サイトに行く。ユーザーの頭に出稿する側のキーワードは入ってない。部分一致の場合。

ユーザーの行動プロセスを起点に考えると、上記のような流れ。で、その前提でデータを見えるようにしないとイカン。 

もちろん、google, yahooの入札システムサイドでも上手く立ちまわる事は大切(CPC, 順位などの管理)だ。 そして、こちらも巨大なブラックボックスで、仮説、検証サイクルが廻る事にはなる。

で、

クエリーデータを得る、マッチタイプで分ける

上記の点を念頭にしたとき、僕としては、以下の2点、A,Bを抑えたときに、作業が楽になった。

A .クエリーデータの取得(Google Analyticsで)
B. adwords側でのクエリーデータのマッチタイプ別の分別管理。(yahooは楽にならなかった、、、)

A. クエリーデータの取得(Google Analyticsで)

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の管理の話を少し。

B 部分一致と完全一致は、分別して管理するのが良いはず

リスティングを経験する中で、最初に戸惑ったウチの一つは、部分一致と完全一致の所だったような気がする、、、他にもあっただろうけど、、、

メイン?なキーワードは、完全一致と部分一致の両方で出稿していくことになると思うけど、除外設定の知識も最初はなかったので、設定に悩んだ記憶がある。除外設定をしったあとは、完全一致と部分一致を区別するには、(部分一致+ 完全除外一致) で部分一致の出稿すれば、完全と部分の分離ができると分かって楽になった。

基本的に複数の広告グループで、完全グループと部分グループを別々に作るのが良いと思う。他の人の現場の方法は知らないけど、ここもそう言っててる。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とうまい具合に統合できるといいなあと思った。

]]>
0
shirai <![CDATA[ggplot2でお手軽ヒートマップ]]> http://abc-analytics.com/ggplot2%e3%81%a7%e3%81%8a%e6%89%8b%e8%bb%bd%e3%83%92%e3%83%bc%e3%83%88%e3%83%9e%e3%83%83%e3%83%97 2011-05-01T05:09:34Z 2011-05-01T04:18:34Z Continue reading ]]> 式とグラフの備忘録です。

時間系列の記憶は、人間の記憶の中でも頼りになる方。超整理法のアドバンテージは、ここにあったはず。 で、月間レポートを書く場合に、時間系列のヒートマップだと、人間の記憶とレポートの記録が、上手くつながる気がする。なので、ヒートマップ(時系列)が好き。

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”)

color-heat-map-google-analytics-data

でも、ggfluctuationのヘルプを見ると、type=colourは traditionalの形だそうだ。

今は、大きさそのものを出す方が良いという認識?

ggfluctuation(abc[,-4])

size-heatmap-google-analytics-data

 

ただ、ggfluctuationは、拡張性?に乏しいような気がする。
geom_pointでcolor, sizeを指定して、4種類のデータ(時間帯、日付、訪問数、平均PV)を示す。

ggplot(abc, aes(hour, date, colour=apv, size=visits) + geom_point()

赤みが付くと、平均PVが高い。大きさは訪問数。
セッションの量と質と、時間帯+日付を示す。

今回は、セッションの質を、平均PVにしたけど、
ECサイトなら売上(セッション辺り)とか、CVRとかを使えば良い。直帰率でもいい。

size-color-heatmap-google-analytics-data

]]>
0
shirai <![CDATA[アドバンスセグメントで擬似加重ソート(Excel のTable機能)]]> http://abc-analytics.com/weighted_sort_on_advanced_segment_by_excel_table_function 2011-04-09T14:00:17Z 2011-03-28T16:25:00Z Continue reading ]]> Google Analyticsだけに限らないTIPSですが、簡単でそれなりに実用的に使える方法だとおもうので、紹介します。

2010年の秋にGoogle Analyticsの加重並び替えが導入されました。Wikiの方の紹介。新機能として紹介されたに似た感じのものを、アドバンスセグメントでやろうとするものです。

ちょっとズルですが、APIでデータを取得するのが前提です。

APIでのデータの取得は、実は簡単で、

    1. Date Feed Explorerを使う(日本語だと止まるので、IDを直接入れる必要がある。)
    2. http://excellentanalytics.com/ を使う。
    3. http://abc-analytics.com/data-feeds-query-explorer-in-windows-applicationを使う。

で出来ます。他にも、色々なツールがあります。

今回は、Cの自作ツールを使ってデータを取得しておきます。

閲覧開始ページ、キーワード、開始数、直帰数を取ります。

WS000033

で、タイトルの話のエクセルの貼り付けます。ここから、本題です。

コピペしたあとは、テーブルにします。

  • テーブル名を英語にします。大事です。
  • 一行目に数字を入れるので、空けておきます。

WS000034

もう少し、下ごしらえが続きます。

  • 直帰数になっているので、直帰率をいれます。
  • 一行目に集計値を出すようにします。
    • ここで、テーブル機能が行きます。

entrancesの集計値は、下図のようにススメます。

(テーブル名が英語だと補完が効いて、マウス無しで^^です)

WS000035 WS000036 WS000037

式は、=SUBTOTAL(9,table1[entrances]) になります。

同じようにbounces(直帰数)も計算します。=subtotal(9,table1[bounces]) ですね。

bounceRate(直帰率)は、この2つを割り算します。

ここで、もう一回、画像。

WS000038

ここから、本当の本題であった、加重ソートを入れます。

前提として、100回以上セッションがあった、キーワード+ページは、そのまま。ソレ以下のものを、全体の平均値と按分する方針です。

B1に 分かれ目の数字、100を入れておいて、TrueBounceRateの列を作りましょう。

WS000039

上の数式を説明します。

entrancesが100以上なら、その列のBounceRateのまま。なので [@BounceRate]

以下なら、全体の平均値(E1)と[@BounceRate]を 全体のEntrances(C1)と列のentrances([@enttances])で按分する。

([@bounces]/$B$1 * [@BounceRate] )   +   (1 – ([@bounces]/$B$1)) * $E$1

Googleの加重ソートは、たぶん似たような感じだと思う。

加重ソートはいろいろ本を読んだけど、理論的背景はよく理解できなかった。

2次元の正規分布の場合に、なんやからすると、上記のような単純な式でもOKという話だったと思うけど、よく理解できなかったので、公開レクチャーしれくれる人がいたらお願いします。@phar

で、ここまでは単に計算しただけど、ここからエクセルのテーブル機能が生きて来る。

ランディングページ単位のキーワードでの加重ソート、ある単語が含まれるキーワードデータの加重ソートとかが簡単にできる。

最初の画面で、

WS000040

landingを/api ディレクトリ以下のものに絞る。

WS000041

その後、TrueBouncecRateを昇順に。(自動で順列にならない、、、フィルターするたびに、並べ替えの必要がある。ここは、イケテナイ。)

WS000042

まあ、それでもそれっぽいソートが出来上がる。landingページを絞った上での、加重ソート。

今回は、ディメンジョンがキーワード、閲覧開始ページという組み合わせだけど、ソレは自分でデータを持ってくるときに好きに選べばいい。

また、加重平均の按分の中心になる平均値(直帰率)も、テーブルでフィルタリングすると、subtotalでそのフィルタリングされたデータの平均値で計算し直されるので、都合が良い。

あまり、データ数が少なくなるとだめだけど、そのデータ全体での平均値を適用して計算しなすのは、フィルタリング前のデータの平均値を持ち出すより適切なはずだ。

まとめ

google analyticsには、加重ソート機能がありますが、似たような事をエクセルでしました。

エクセルのテーブル機能を使うことにより、簡単に特定ディメンジョンの加重並び替え(条件は複数でもOK => アドバンスセグメント)ができることを図示しました。 これは、たぶん、今のレポート画面ではできないことだと思います。 ただし、並び替えのアルゴリズムは違うのでしょう。

冒頭のリンクにも 今回のようなことをやった記録があって(加重ソートが出始めたころに書いたやつ) 、実際の GAでの順番と比較したグラフがありますが、そんなにズレはないと思います。

そんなにってどんだけ? 主観です^^。前やったとき、順位相関とか計算してみたけど、それを用いるのが正しいのかさっぱり分からなかったし、数値も直感的に理解できなかったので、、、単純な加重ソートもGAのソートも、結果としてはそんなに変わらないと思いました。

確か、加重ソートをアドバンスセグメントでという話は、結構要望であったと思うので、擬似ですが、それなりに役に立つ作業工程の紹介だと思います。試してみてください。

]]>
0
shirai <![CDATA[plyrで集計 ggplot2でグラフ化]]> http://abc-analytics.com/?p=1505 2011-03-30T16:24:12Z 2011-01-10T17:47:20Z Continue reading ]]>

Table of Contents

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)

結果

ぎざぎざ。データの把握がしにくいですね。

http://abc-analytics.com/wp-content/uploads/2011/01/wpid-basic.png

対策としては、週別のデータにすればいいけど、それだと一日の変動の様子が消えてしまう。下で移動平均を考えることによって、曜日変動の除去を考えるけど、ページ別のセッション数も見ておく。

ページ別セッション数の累計表示

  • 全ソースでのページ別
    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)

    http://abc-analytics.com/wp-content/uploads/2011/01/wpid-visits.png

    累計で表示してみる

    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)
    

    http://abc-analytics.com/wp-content/uploads/2011/01/wpid-cumsum_visits.png

    積み上げのが比較しやすいのかも
    10月過ぎから勢いがついたページがある。

  • ソース別(yahoo,google)で見てみる。yahooとgoogleのセッション数を見てみる
    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)

    http://abc-analytics.com/wp-content/uploads/2011/01/wpid-upv_yg.png

    あんまり関係なさそう。あと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)
  • 結果上から 実測値、トレンド、曜日効果分、誤差分http://abc-analytics.com/wp-content/uploads/2011/01/wpid-decompose.pngセッション数の曜日別差異(合計は 0 )
    9.7 14.3 16.7 15.4 11.9 -32.8 -35.2 0.

    とりあえず、トレンドは見える感じです。
    週刊平均値をプロットするよりは、ダイナミック。日別よりは見やすい。

平均滞在時間も曜日効果を見る

エンゲージメントの測定として、滞在時間を対象にします

  • 注意GAはeventTrackの値も滞在時間のログとしてみてます。
    なので、eventTrackを細かく発行してると、通常よりは細かく滞在時間がでます。
    ただ、それでもこのサイトのeventTrackeの発行タイミングも等時間隔で出てるわけではないし、
    非常に怪しいデータではあります。
  • コード
    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

    トレンドデータ(曜日効果除去後のもの)

    http://abc-analytics.com/wp-content/uploads/2011/01/wpid-trend_visits_timeonsite.pngなんかこれだけでは、よくわからんかも。 曜日別というより、他のセグメントを当たる必要がある。

分布を見る。滞在時間分布(セグメントデータとして)を見る

考え方

日別の集計値としての、平均滞在時間ではなんとも言いがたい。
実は、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秒がどこだか不明だし、、
    全然だめ、、

    http://abc-analytics.com/wp-content/uploads/2011/01/wpid-bar_month_visits.png

    横軸を詰める。facet_wrapで表示。

    p <- qplot(visitLength, data=d1, geom="bar", weight=visits, log="xy")
    p + facet_wrap(~month)

    こちらのがみやすい。

    http://abc-analytics.com/wp-content/uploads/2011/01/wpid-month_stay_dst.png

  • density表示にするR(ggplot2)は、近似曲線も計算して引いてくれる。
    月別の分布を密度で近似線表示
    #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")

    http://abc-analytics.com/wp-content/uploads/2011/01/wpid-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の対象指定はどうなのだろう?

    http://abc-analytics.com/wp-content/uploads/2011/01/wpid-hist_month_visits2.png

メディア別

時間変化とは別にメディア別もやってみる。

  • バープロット
    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)

    http://abc-analytics.com/wp-content/uploads/2011/01/wpid-boxplot_medium_visits.png

    分類が大まかすぎる。下でキーワード別をやる

  • 四分表示
    数値を出しておく
    今度は分表示にしておく。
    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)

結果

http://abc-analytics.com/wp-content/uploads/2011/01/wpid-boxplot_medium_month_visits.png

数字も出しておく

母数(セッション数)

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)

http://abc-analytics.com/wp-content/uploads/2011/01/wpid-landing_keyword_length.png

似たようなキーワードを統合して扱わないとキーワード別にしても意味なさそう
なので、キーワードグルーピングをやる

キーワードグループ別滞在時間

さて、キーワードはバラバラになりすぎてて、全体の把握が難しい。
キーワードでは検索フレーズ単位で管理されているので、同じ意味でもたくさんの項目になってるせい。統合しないといけないのだが、やり方不明、、、

  • キーワードに分解して整理する空白のものを単語に分解して整理してみる。
    上で、1セッション1レコードの形でキーワードが含まれる形を作ったので、それを使う。
    #(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 google 3876
    2 analytics 3748
    3 アナリティクス 482
    4 アクセス解析 482
    5 googleanalytics 463
    6 ユニークユーザー 413
    7 ページ別セッション数 347
    8 cookie 297
    9 セッション 277
    10 api 270

    とりあえず、整理しやすい単位に分解はできた。

  • 同時出現表を作る分解したあとは、どういう組み合わせになっていたかを表示する。
    同時に使われていた単語を集計する
    先頭の10単語について、同時に使われた単語の出現回数を数える
    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 という単語と一緒に検索された単語

    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でやればと想うけど、、、

  • グループ化してグラフにこの10単語でグルーピングする
    重複所属は、どちらにもカウントするようにする
    #ごちゃごちゃしてきたけど、、、グループ名カラムを追加して、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
    google 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
    google 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)

    結果
    http://abc-analytics.com/wp-content/uploads/2011/01/wpid-landing_grouped_keyword_length.png

]]>
0
shirai <![CDATA[RでGoogle Analtyics: 時間帯別グラフ]]> http://abc-analytics.com/r%e3%81%a7google-analtyics-%e6%99%82%e9%96%93%e5%b8%af%e5%88%a5%e3%82%b0%e3%83%a9%e3%83%95 2010-12-11T13:47:18Z 2010-10-04T17:51:41Z Continue reading ]]> Rという統計用?言語でData Export Apiを使うscriptを書きました(コードはこちら)

(2010/12/10 追記 )どうやら googleの中の人も R用のライブラリーを出したみたいです。テストもついてるし、サンプル誤差もついてくるので、僕自身もあっちを使うつもり。account系のデータ、ゴールとかも取れるといいなと思う

統計用言語なのですが、グラフ出力も充実しており、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

2

週末と夜間は空白に近い。

曜日別に傾向があるかもしれない。見てみる。

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))

3

まあ、なにもない。深夜に起きる人は、木曜くらいから出始めるのか?というか、祝日補正してないせいだろう。

また、サイトによっては、キャンペーンやコンテンツ投入のタイミングが見えるかもしれない。

んじゃ、とりあえず、メディア別にして、期間を長くして平均を見てみる。

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="曜日別|メディア別”)

4

検索活動は集中するくらいしかわからない。今年の平均だけど、、

これでも、具体的なユーザー像にはならない。

そこで、ある特定のキャンペーンを行った場合の波及効果みたいなのをみたいが、出せるデータがないので、このブログの公開記事の閲覧数の減衰具合みたいなのを見てみる。

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="特定ページメディア別”)

5

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="特定ページメディア別”)

6

あとは、、、、

会員情報などとマッチングしてるサイト(外部ソースとの照合は規約違反かも)なら、ユーザー属性別のアクセス状況などは、比較表示しやすいと思います。

検索ワードで属性を分けたり、地域と時間情報と天候情報(外部から引っ張る)で属性を仮定したりと思ったのですが、余裕ができたらやってみたいと思います。

とりたてて何か発見ができたわけではないですが、Rを使うと、こんな感じで手短にグラフ出力ができます。ただ当然ながら、学習曲線はキツイ。ここまで来るのに相当な学習時間がかかりました。データ分析ではなく、データ表示だけなのに、、、

以下コードです。RCURLとXMLが必要です。

こちらに移動しました。

]]>
0
shirai <![CDATA[マルチトラッキング(複数cookie)について考える]]> http://abc-analytics.com/%e3%83%9e%e3%83%ab%e3%83%81%e3%83%88%e3%83%a9%e3%83%83%e3%82%ad%e3%83%b3%e3%82%b0%e8%a4%87%e6%95%b0cookie%e3%81%ab%e3%81%a4%e3%81%84%e3%81%a6%e8%80%83%e3%81%88%e3%82%8b 2010-09-14T09:44:32Z 2010-08-31T14:53:38Z Continue reading ]]> *2010/09/10 追記を入れました。

*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つのパターン。サブドメイン、クロスドメイン、サブディレクトリの場合を見ていきます。

ただ、もう少し、講釈を続けます。

複数cookieを実現する設定

*これは、僕自身の考えに基づいて書いています。計測がうまく行かないかもしれないので、うまく行かない場合は、ダメじゃないか!と怒るか、もしくは、ぜひ連絡ください

ケース: 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の二つの機能

このsetDomainNameは、内部的に二つの働きをします。

cookieの所属ドメインの決定と、cookieの値にドメイン情報を持たせることです。

cookieの基本的な仕様は、ドメイン、パス別に指定して作成することができますが、javascriptからはドメインとパスはみえません、読む場合は見えないのです。ですので、cookieの値にドメイン情報を持たせることは意味を持ちます。(ドメインハッシュ値は他にもセキュリティ用途もあるみたいですが、よくわかりません)

s1-test-analytics.com

google-analytics-cookie

s2.test-analytics.com

WS000005

画像の文字が細かいですが、.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を訪問。

WS000010

同一セッション内で、s2を訪問。 s2の参照情報は、s1.test-analytics.comに。

WS000011

セッション2に。(セッションの更新は、utmcのcookieを削除で行う) 両方のutmaのカウンタ(Valueの最後)は2に。

WS000012

セッション3 同じようにカウンタがインクリメントされ、3に。

WS000013

セッション4 phar.awe.jpから s2に遷移。全体のもの、s2のcookieが更新される(カウンタ(utma)、参照情報(utmz))

WS000014 

セッション5 s1を訪問(セッション4で、s2を訪問した情報は、test-analytcs.comの方のみ反映されている。

WS000015

と、想定通りになりました。 .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からリンクを張りました。

WS000016

_gaq.push([“a._link”,this.href])でクロスドメイン遷移。細かいですが、参照元は全体では s2。個別では test-analytics.comからと別れてます。

WS000017

セッション2。test-analytics.comを訪問。カウンタ(utma)が増えただけ。

WS000018

セッション3 phar.awe.jpを訪問。サブドメイン計測ではcookieが共有されていたので、カウンタが上がっていたのだが、クロスドメインはそれがないので、全体計測の方のカウンタ(umta)が3にならず。2のまま。

WS000019

と、全体で3セッションあったのだが、全体を計測する utma(ドメインハッシュ Valueが1の方) では、

2回目の訪問が二回あって、3回目の訪問がない結果となる。また参照情報の変更も、url上のパラメータ渡しがないと、cookie値を共有する機会がないので、不整合な結果を作りやすい。

ショッピングカートのように、必ず特定のドメインからの遷移であれば問題はないでしょうが、結論としては、

クロスドメインでの全体・個別の分別管理は問題が多そうと、僕は思います。

さて、最後にもう一つトピックを取り上げます。ディレクトリでの全体・個別をマルチトラッキングで管理するです。ディレクトリの分別管理はニーズが多そうですが、固有の問題があります。

サイト全体・特定ディレクトリでの全体計測・個別計測のマルチトラッキング

実は、僕が手伝ってるサイトでも、先日これをやろうとしたのですが、うまく行かなくて悩んだのです。

cookieの値がヘンだなあと、、

実は、ドメインの違いに関しては、google analyticsのcookieがドメイン情報を持っていたので、区別できたのですが、パスの情報は入れてくれてないのです。なので、複数cookieはできません。作る事はできても、カウンタのインクリメントは、どっちをするか不定です。参照情報のutmzは一個しかできません、最初に設定したパスで出るのみです。何も指定してなけば、”/” 。これは、最初にsetCookiePathで”/DDD/”としたのでこうなりました。

WS000020

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を訪問。

WS000024

注意: 所属Domainは双方同じ .test-analytics.comになってますが、ドメインハッシュ値は113… と115…と違います。これはデフォルトsetDomainNameなしを使うと、内部としては “test-analytcs.com”と “.”なしでドメインハッシュを計算するため。 .を付けとけば違うハッシュ値をとれる。で、今回は所属ドメインンが同じだが、パスが違うためcookieは存在できる。

セッション2: index.htmlを訪問

WS000025

セッション3 /DDD/index.htmlを訪問

WS000026

全体で3回訪問した。全体を把握する方の,115001218は、カウンタ(utma)が3になってる。個別ディレクトリ管理の113074331は、2回と想定通り。めでたし、めでたし。

まとめ

ここまで、読んでくれる人がいるか疑問です(僕なら飛ばし読みだ)が、まとめです。感想の箇条書き。

  • 複数cookieを使えば、セッション管理を独立して行えるので、ドメイン別、ディレクトリ別に具体例をcookieの値を見ながら検証しました。レポート側では検証していません。なので、落とし穴がまだあるかもしれません。
  • サブドメインの全体・個別の独立管理は、たぶん実用に応すると思います。
  • クロスドメインでの全体・個別の独立管理は、セッション単位で分析するのには問題含みな気がします。
  • ディレクトリでの全体・個別の独立管理は、トリッキーながらも、そのトリッキが使える状況の下では、きちんと数字が取れそうだと思います。
  • google analyicsの先頭のドメインハッシュ値は、ネームスペース的役割を果たします。キー(名前)が一緒でも、こちらで同一名cookieの判別を可能にします。
  • ディレクトリ情報は、ドメインハッシュ値に含まれてないので、別ドメインのcookieを用意して、cookieの分別管理を可能にできます。
  • setDomainNameの役割、cookieの所属ドメイン、ドメインハッシュ値の作成の二つの機能を頭にいれておく。参考に、前にwikiに書いたものへのリンク
]]>
0
shirai <![CDATA[訪問回数別セッション数(vs 参照元)の残存率を見る]]> http://abc-analytics.com/%e8%a8%aa%e5%95%8f%e5%9b%9e%e6%95%b0%e5%88%a5%e3%82%bb%e3%83%83%e3%82%b7%e3%83%a7%e3%83%b3%e6%95%b0vs-%e5%8f%82%e7%85%a7%e5%85%83%e3%81%ae%e6%ae%8b%e5%ad%98%e7%8e%87%e3%82%92%e8%a6%8b%e3%82%8b 2010-07-08T13:34:23Z 2010-07-07T16:31:01Z リファラー情報の扱いは、他のアクセス解析ツールと違う仕様

Google Analyticsの、他のサービスと違う有名な仕様として、外部リファラーが無い場合のリピーターのリファラー情報は、前回のリファラー情報とするというのがあります。

直接訪問は前回のリファラー情報でレポートとして上がるという事です。最初からノーリファラーなら、ノーリファラーですが。

僕は、他のアクセス解析サービスはよく知らないので、どちらが有用なデータ表示方法かは分からないのですが、GAの仕様を決定した人は、こっちが良いと思ってそうしたんだと思います。

この方法で良いなと思える点

本当は、ユーザー単位で、トラフィック情報の変遷を見ることが出来れば、万事解決なのですが、GAは大きな会社のサービスなので、いろんな事に配慮しなければならず、ユーザー単位のトラフィック情報を見ることはできません。ユーザーグループを作って、カスタム変数に記録すれば、グループ単位では見られるようになりますが、それは置いておきます。

それで良い点はというと、直接訪問を必ず、どこかの参照元の情報に帰属させてる点です。アクセス解析の目的は、コンバージョンの向上にあるわけで、その要因となる流入元情報の寄与を割り振ってるわけです。2,3回目は、bookmarkから来たんだろうけど、一回目は、、、というデータ処理を省くことができるわけです。

もちろん、リピーターは、複数の外部サイトからやってることも多いわけで、その場合は上書きされていくので、きちんとした?データにはならないわけですが。

参照元別の残存率 = コア化するユーザー率を見る

訪問回数と参照元情報をディメンジョンに、セッション数を指標にします。

  visits_against_visit_count

データは、仮想の数字です。サイトの数字を参考にして、乱数を降った擬似的なデータです。 縦軸(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 は、頭に出なかった、、、

]]>
0
shirai <![CDATA[ページ遷移レポートの自動配信を実現する]]> http://abc-analytics.com/%e3%83%9a%e3%83%bc%e3%82%b8%e9%81%b7%e7%a7%bb%e3%83%ac%e3%83%9d%e3%83%bc%e3%83%88%e3%81%ae%e8%87%aa%e5%8b%95%e9%85%8d%e4%bf%a1%e3%82%92%e5%ae%9f%e7%8f%be%e3%81%99%e3%82%8b 2010-09-11T17:12:41Z 2010-06-19T14:50:14Z Continue reading ]]>

前回、スクロール計測のレポートを自動配信する話を書いたのですが、今回は、Google Analtyicsのページ遷移データをグラフ化してメール配信する話です。

同じく、google apps script + google chart api でのメールによるレポート配信です。

AuthSubでの作成も作りました。試してみてください。

Google Analyticsにおけるページ遷移のデータ

あるページにおける、遷移上の前後のページは、WEBのレポート画面で見ることができます。ナビゲーションサマリーですね。ナビゲーションサマリーの数字の見方は、以前Wikiの方に書きました。

WEB上のレポート画面では、重複ページの回数を抜いて、パーセンテージにして表示してます。(移動先のデータ(nextPage)のデータがよく分かってないのですが、、)

今回は、Data Export APIでデータを持ってくるので、重複ページを抜かないとと思っていたら、uniquePageviewの数字がそのままの数字なので、端折って、uniquePageviewの数字を使いました。uniquePageviewを使う妥当性について、Forumなどで質問があったみたいですが、数値を見る分には大丈夫そうなので、uniquePageviewを使います。単に重複を抜くためだけですが。(もう少し考えてみた。けっこう注意が必要かも)

とにかく、ディメンジョンに, “priviousPagepath”と”nextPagePath”。指標に uniquePageviewを使います。

Google Chart APIの GraphViz Chart

前回のデータを表示する時に、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認証でのデータ表示に対応するのは、詳しい人なら簡単にできそうなので、そういう人に任せます。

]]>
0
shirai <![CDATA[google apps scriptと chart apiを使ったレポート自動化]]> http://abc-analytics.com/automate_reporting_google_apps_script 2010-07-17T18:10:10Z 2010-06-18T10:19:12Z Continue reading ]]> 今回は、アクセス解析の話ではないのですが、データとして GAを使ったので、ここに書きます。

前回、スクロール率をgoogle analyticsで計測した話を書きました。 レポート部分は、エクセルでの表示だったんですが、今回、自動化のひとつの方法として、タイトルの方法をやったので、報告します。

Google Apps Script(GAS)は、かなりの便利ツール

データを外部から引っ張ってきて、加工して、メール配信というのが、割合簡単にできます。

今回は、google analyticsのデータを引っ張って、加工して、HTMLメールで配信という形の紹介です。

GASの記述は、javascriptです。 google analyticsの集計の設定も javascriptですので、 javascriptは習得しがいのある技術かもしれません。

手順としては、URLFetchで google analytics Data Export  Api からデータを取得して、それを google chart apiを使って、 HTMLメールで送る作業です。

Google Chart APIも、自動化ツールとして価値大。

URLを指定すれば、チャート画像を返してくれます。

ですので、変化する数字を自動的にグラフ化してレポーティングしたい場合に価値が高いです。

パラメータがたくさんあって、マニュアル(解説)が英語なのでとっつきにくいですが、データ、チャートタイプ、ラベル、軸、などの概念とapiでのパラメータが一致すれば、それなりに使えるのではと思ってます。

別サイトにメモみたいなのを書きましたが、、、あまり参考にならないかも。

GMailの問題?

いろいろいじって、なんとか自動化にこぎつけたのですが、GmailのHTML表示の時に、imageタグの部分が上手く表示されなくて、いろいろ探したのですが、結局あきらめました。 たぶん、文字数の問題のような気がしてるのですが、、、どうなんでしょう。 Yahoo Japanの Web mailなら問題なかったです。

出力画像と スクリプトを貼りつけます。

スクロール計測とそのレポート配信を試してみたい方は連絡下さい(コメントでスクロール計測希望と書いてくだい)。 設定して、週次 or 日次で、特定のディメンジョンで切ったスクロール率のよる閲覧割合をメールレポーティングします。無料です。 以下の画像のような形でのHTMLメールが送られます。

画像レポートの後に、参考に、GASのスクリプトを貼りつけておきます。 貼り付けた以外にも追加の定義(関数、代入式)などがあるので、全部ではないです。

2010-06-03から2010-06-15のセッション数上位3ページの精読率調査です。数字はセッション数です。

以下、レポート配信に使った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 data    
  64:      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("&", "&amp;");  
 112:  
 113:  }
]]>
0