クイックノート

ちょっとした発見・アイデアから知識の発掘を

食べログ3.8問題を検証

先日、twitter上で食べログの星の数について、
ある問題が話題になりました。

食べログの闇として話題になったその問題とは、
「評価3.8以上は年会費を払わなければ3.6に下げられる」
というものです。

食べログは飲食店についての口コミを集めるサイトで、
その評価は実際のユーザーによって形成されるものとして広く認知されています。

専門的なグルメリポーターでもなく、
一般の人々の素直な感想を集めることで、
その飲食店のリアルな価値が知れると期待して、
利用しているユーザーも多いでしょう。

それだけに、
食べログが評価を恣意的に操作しているかもしれない」という話は、
瞬く間にネットで話題となりました。

さて、この話は実際に行われていることなのでしょうか。

食べログでは、当然評価点は公開されているので、
このような恣意的な操作があれば、
何らかの形で偏りが見つかるはずです。

ということで、食べログの評価点の偏りを分析して、
奇妙な偏りが存在しないかどうかを調べてみましょう。

方法

食べログにアクセスして公開されている各店舗の評価を取得し、
評価の分布に異常な偏りが存在しないかを確かめます。

評価数が少ないと、小数のユーザーで評価点がバラついてしまうので、
一定以上の評価数がある店舗に絞って情報を取得します。

取得したデータ

試しに地域を大阪に限定して、
評価数が一定数以上ある店舗1200店舗について、
食べログでの評価点を取得しました。

下の図は、横軸を評価数、縦軸を評価点として、
データをプロットしたものです。

f:id:u874072e:20191008100357p:plain
評価数と評価点

直観的には評価の数が多いほど評価点が高いイメージがありましたが、
グラフを見る限り、そのような傾向は見えませんね。

また、なんとなく、3.8付近に壁があり、
3.8以下は多いが、3.8以上は少ない様子が見えます。

この傾向をより正確にみるために、ヒストグラム化してみます。

評価点の分布

評価点に操作が加えられているなら、
特定の評価点に偏りが現れるはずです。

そこで、評価点がどのように分布しているかを調べます。

下の図は横軸を評価点、縦軸のその評価を獲得した店舗数として、
ヒストグラムを示したものです。

f:id:u874072e:20191008102755p:plain
評価点のヒストグラム

このグラフで注目したい特徴は以下の二つです。

  • 評価3.8の前後で大きく傾向が変わる
  • 評価3.6付近が異常に多い

評価3.8の壁

グラフを見ると、評価3.8までは、
その評価が付けられる店舗数が徐々に増えていますが、
3.8の直前でピークを迎えた後、
3.8を超える店舗は極めて稀になります。

評価3.8が壁として一つの天井になっていると言えます。

確かに、3.8以上を強制的に引き下げるという操作をしていれば、
このグラフに矛盾しませんね。

評価3.6が異常に多い

評価の高い店舗が少ないのは当然と言われればそうかもしれません。
ところが、3.8の壁以上にグラフで注目すべきは、
評価3.6付近が異常に多いことです。

3.6前後の評価は40店舗くらいにも関わらず、
その3~4倍の数の店舗が3.6付近の評価になっています。

明らかに全体の傾向から外れた異常な偏りです。

なるほど、3.8を超えた店舗を3.6に下げるような操作をすれば、
確かにこのような分布の偏りを矛盾なく説明できます。

まとめ

食べログの評価は「3.8を超えると3.6に下げられる」という話の真実性を、
食べログの評価の分布の偏りを調べることで調査しました。

結果としては、
- 3.8 を超える評価は極端に少ない
- 3.6 付近の評価は異常に多い
という話に矛盾しないような分布の偏りが認められることが分かりました。

もちろん、これだけでは真相は分かりませんが、
他にこの奇妙な偏りを説明することは難しいように思います。

仮に本当に操作が行われていれば、
ユーザーからのリアルな評価が知れるという
口コミサイトの特徴を大きく損なってしまうため、
今後の利用方法を考え直す必要があるかもしれません。

おまけ

大阪だけだと不安なので、
東京でも調べてみました。

下のグラフが東京の食べログ評価の分布です。

f:id:u874072e:20191008163225p:plain
東京の食べログ評価

結果は同様ですが、東京の方がより傾向が明らかに見えます。

ナンバーズ4の必勝法はあるのか?ランダムな抽選と比べてみた

先日のテレビ番組で、
ナンバーズ4の必勝法なるものが紹介されていました。

ナンバーズでは4つの数字を選び、
抽選で選ばれる数字と一致していたら賞金をもらえるというものですが、
ある法則に従って数字を選べば当選しやすいというのです。

番組中では、抽選で選ばれる番号が法則を満たす確率は〇〇%!と謳われていますが、
これだけでは、その法則が有効であるかは全く不明です。

抽選に規則性がなく、まったくのランダムであったとしても、
例えば、4つの数字の中に偶数が含まれる確率は93.75%と高確率ですが、
だからといって偶数を入れましょうというのは必勝法でもなんでもないでしょう。

ナンバーズ4に本当に規則性があって、
必勝法があるかどうかは、ランダムな抽選結果と比較してみないと分かりません。

ということで、ここでは、
無作為に4桁の数字を選んだ場合に、
3つの必勝法がどの程度の確率で成り立つのか、
そして実際にナンバーズ4に規則性がありそうなのかを調べてみましょう。

ナンバーズ4の必勝法

ナンバーズ4では4桁の数字を選び、
抽選で選ばれた4桁の数字と一致していれば、
賞金がもらえます。

抽選で選ばれる数字に偏りがあり、
なんらかの規則性があるなら、
その規則性を利用して当たりやすい数字を選ぶことができます。

これがいわゆる必勝法となります。

番組では、ナンバーズ4の必勝法として、
3つ紹介されていました。

足す9理論

4桁の数字のうち、どこか二つの数字を足すと9になる数字が選ばれるという法則です。

例えば、1801は上2桁の「18」を足して9になります。

また、二つの数字は離れていてもよく、
「1732」は「7」と「2」を足して9になります。

この足す9理論は、過去100回の当選くじの49%で成り立ったと言われています。

連続数字理論

4桁の数字のうち、どこか二つの数字が連番になっているという法則です。

例えば、「1268」は上二けたの「12」が連番になっています。

また、足す9理論と同様に、二つの数字は離れていてもよく、
「1682」は「1」と「2」が連続した数字になります。

連続数字理論は、過去100回の当選くじの75%で成り立ったと言われています。

引っ張り理論

こちらは、前回の当選番号の数字が選ばれるという法則です。

例えば、前回の当選番号が「2869」だった場合、
「3215」には、前回の当選番号の「2」が含まれているので、
この法則が成り立っているということになります。

引っ張り理論は、過去100回の当選くじの82%で成り立ったと言われています。

無作為な当選番号のシミュレーションの準備

紹介されていた必勝法では、
過去100回の実際の当選番号について、
何パーセントが必勝法を満たしていたかが示されていました。

冒頭でも述べたように、この確率だけでは、必勝法が有効であるかは分かりません。

抽選になんらかのパターンがあるかどうかは、
ランダムに抽選した場合と比べて、
そのパターンが起こりやすいかを見るまで分からないのです。

ということで、無作為に当選番号を選んだ場合のシミュレーションをしてみて、
それぞれの法則がどの程度成り立つかを調べてみましょう。

無作為な当選番号の選択

下の関数は4桁の数字をランダムに選択するもので、
ナンバーズ4の抽選1回分に相当します。

numbers = function(){
  sample(0:9, 4, replace=T)
}

足す9理論の検証

下の関数は、与えられた4桁の数字について、
足す9理論が成り立っているか否かを確認する関数です。

tasu9 = function(ns){
  ns[1] + ns[2] == 9 ||
    ns[1] + ns[3] == 9 ||
    ns[1] + ns[4] == 9 ||
    ns[2] + ns[3] == 9 ||
    ns[2] + ns[4] == 9 ||
    ns[3] + ns[4] == 9
}

連続数字理論の検証

下の関数は、与えられた4桁の数字について、
連続数字理論が成り立っているか否かを確認する関数です。

renzoku = function(ns){
  abs(ns[1] - ns[2]) == 1 ||
    abs(ns[1] - ns[3]) == 1 ||
    abs(ns[1] - ns[4]) == 1 ||
    abs(ns[2] - ns[3]) == 1 ||
    abs(ns[2] - ns[4]) == 1 ||
    abs(ns[3] - ns[4]) == 1 
}

引っ張り理論の検証

下の関数は、与えられた4桁の数字について、
引っ張り理論が成り立っているか否かを確認する関数です。

hippari = function(ns){
  kako = numbers()
  sum(ns %in% kako) > 0
}

過去の当選番号も無作為に選ばれるものとして、
過去の当選番号が4桁の中に含まれているか否かを返します。

以上で準備が整いました。
これらを使ったシミュレーションで、
必勝法の有効性を確認してみましょう。

ランダムな抽選で必勝法が成り立つ確率

必勝法が有効に働くためには、
選ばれる数字に規則性が存在する必要があります。

これは、ランダムに抽選した場合と、
実際の抽選の場合で、
必勝法の成り立つ確率にちゃんと差があるかどうかを確認すれば良さそうです。

実際の抽選で必勝法が成り立つ確率は番組で紹介されていたので、
後は、ランダムな抽選で必勝法が成り立つ確率を計算して比べてみましょう。

ランダムな抽選を行う

ランダム性があるため、
実行の度に結果が若干変わりますが、
ある程度正確な値を計算するために、
多数回の抽選を行って検証することにします。

ここでは、10万回のランダムな抽選結果に対して、
必勝法が成り立つ確率を計算していきましょう。

N = 100000
samples = sapply(1:N, function(i){numbers()})

足す9理論が成り立つ確率

10万回のランダムな抽選について、
足す9理論が成り立つ確率を計算します。

mean(apply(samples,2,tasu9))

結果は約46.3%でした。

連続数字理論が成り立つ確率

続いて、連続数字理論が成り立つ確率を計算します。

mean(apply(samples,2,renzoku))

結果は約66.6%でした。

引っ張り理論が成り立つ確率

さらに、引っ張り理論が成り立つ確率を計算します。

mean(apply(samples,2,hippari))

結果は、約80.5%でした。

ランダムと実際の抽選の比較

結果をまとめると表のようになります。

ランダム 過去100回
足す9理論 46.3% 49%
連続数字理論 66.6% 75%
引っ張り理論 80.5% 82%

表を見ると、どの必勝法についても、
ランダムに抽選を行った場合よりも、
実際の抽選の方が成り立つ確率が多少なりとも高いことが分かります。

とはいえ、足す9理論や引っ張り理論については、
その差がかなり小さく、本当に規則性があると言っていいか疑問が残ります。

100回の統計の信頼性

必勝法として紹介されていた法則は、
一応ランダムよりも高い確率で現れているようでしたが、
その差がたまたまなのか、本当に規則性をもっているかは判断に迷うところです。

ところで、番組で紹介されていた確率は、
過去100回の当選番号について法則が成り立つ確率でしたが、
100回程度の検証では、かなりばらつきが出ることが想像できます。

ということで、そのバラつきを調べてみましょう。

100回の当選数字で確率を計算するシミュレーション

100回の当選数字だと、
当選数字の出方がたまたま偏っただけで、
法則が成り立つ確率が大きく変わってしまうことが予想されます。

そこで、100回の当選数字で確率を計算するシミュレーションを何度もおこなって、
どの程度、結果にバラつきが出てくるかを調べます。

kako100 = function(trick,N=100,iter=10000){
  sapply(1:iter,function(x){
    samples = sapply(1:N, function(i){numbers()})
    mean(apply(samples,2,trick))
  })
}

res = NULL

res = cbind(
  kako100(trick=tasu9),
  kako100(renzoku),
  kako100(hippari)
)
colnames(res) = c("tasu9","renzoku","hippari")

boxplot(res,ylab="probability")
points(c(1,2,3),c(0.49,0.75,0.82),col=2)

結果

下の図はそれぞれの法則を、100回の当選数字で検証した場合に、
結果がどの程度ばらつくかを示したものです。

計算されたそれぞれの法則が成り立つ確率のバラツキを箱ひげ図でプロットしています。
赤丸は、番組で紹介されていた実際の過去100回について法則が成り立つ確率を表します。

f:id:u874072e:20191007130408p:plain
法則を満たす確率のバラツキ

やはり、100回のみで確率を計算するとバラつきが大きい様子が分かります。

足す9理論と引っ張り理論については、
ランダムな抽選結果でも誤差の範囲内と言えそうです。

一方で、連続数字理論については、
ランダムに比べると実際に成り立つ確率の方が高いと言えるかもしれません。

まとめ

ナンバーズ4の必勝法について、
実際の当選番号とランダムな当選番号を比較して、
その規則性の有効性を調べました。

結果として、足す9理論と引っ張り理論は、
規則性はそこまで認められず、
連続数字理論については、ランダムと比べて、
それなりに規則性がありそうという結果になりました。

ただし、100回の検証でのバラつきを見て分かるように、
法則が成り立つ確率は上下するので、
何十パターンも法則を考えておいて、
たまたま上振れした法則をピックアップすれば必勝法と言うこともできるので、
過度に信頼しないように注意しましょう。

【GAS】時系列を図解するスライドを自動生成

ある一連のできごとを図解する場合、
時系列に沿って出来事を並べると前後の関係が一目瞭然になります。

また、単に順に並べるだけではなく、
出来事と出来事の間の時間を視覚的なスペースとして表現すれば、
より正確に時間の流れを説明することができます。

下の図は、出来事が生じた年を点の位置で表現しています。
「AAAA」と「BBBB」
「CCCC」と「DDDD」
の間はどちらも同じ7年の間が空いていますが、
図でも同じ間隔が空くように配置されています。

f:id:u874072e:20191004141724p:plain
年によって位置をずらした図

問題は、このような図を手で作るとなると結構手間がかかるということです。

まず、手作業で正確な位置に図形を移動させるのは至難の業です。
きわめて正確なマウス捌きが要求されることになります。

一応、座標を指定して図形を移動させることもできますが、
一つ一つの図形に座標を設定していくのも中々に骨が折れます。

そこで、このスライド作成作業を自動化してみましょう。

自動生成の流れ

下のように時間と出来事を並べたデータを用意して、
このデータをもとに時系列を図示したスライドを生成します。

f:id:u874072e:20191004144813p:plain
時系列のデータファイル

自動生成では、上のデータを読み込んで、
順にふさわしい位置に出来事を配置していきます。

GASのコード

以下ではデータを読み込んで、
自動的に図を生成するGASのコードを紹介します。

関数

データの読み込み

まずは、Google スプレッドシートから、
図のもととなるデータを取得します。

データを取得するシート名をDATA_SHEETで指定します。

var DATA_SHEET = "データ領域"
function load_data(){
  var ss = SpreadsheetApp.getActive()
  var s = ss.getSheetByName(DATA_SHEET)
  
  var row = s.getLastRow()
  var col = s.getLastColumn()
  
  var X = s.getRange(1, 1,row,col).getValues()
  return(X)
}

読み込んだデータは二次元配列として保持します。
二次元配列から列を取り出せるようにしておくと便利なので、
列を取り出す関数getColumnを定義しておきます。

function getColumn(array,col){
  var ans = []
  for(var i=0;i<array.length;i++){
    ans.push(array[i][col])
  }
  return ans
}

まっさらなスライドの生成

図形を配置していく前に、
その土台となるスライドを生成します。

デフォルトでは、タイトル領域などが最初から配置されていますが、
今回は、まっさらなスライドに図形を配置していきたいので、
clear_pageでページの要素を全て削除しています。

function clear_page(slide){
  var els = slide.getPageElements()
  for(var i=0;i<els.length;i++){
    els[i].remove()
  }
}

function slide_initialize(){
  var pres = SlidesApp.create("自動生成された年表")
  var slides = pres.getSlides()
  var sl = slides[0]
  clear_page(sl)
  return [pres,sl]
}

図形の位置計算

データに含まれる時間に応じて、
いい感じの位置に図形を配置するための位置を計算します。

基本的には、一番古い出来事と、一番新しい出来事が、
スライドの両端にくるようにして、
後はそれぞれの時間に応じて相対的に配置します。

function rel_position(x,start,end){
  var max_x = Math.max.apply([],x)
  var min_x = Math.min.apply([],x)
  var ans = []
  Logger.log(max_x)
  for(var i=0;i<x.length;i++){
    ans.push((x[i]-min_x)/(max_x-min_x) * (end-start) + start)
  }
  return ans
}

図の自動生成

上で定義した関数を利用して、図を自動生成します。
少しいじることで様々なバリエーションの図が生成できます。

箱型の時系列

ひとつめは下のような箱を時系列にそって並べたものです。
箱の中に出来事の名前を表示しています。

f:id:u874072e:20191004151928p:plain
箱型の時系列

function chro_box() {
  var X = load_data()
  var [pres, sl] = slide_initialize()

  var years = getColumn(X,0).map(Number)
  var pos = rel_position(years,50,pres.getPageWidth()-100)
  for(var i=0;i<X.length;i++){
    var text = X[i][1]
    var rect = sl.insertShape(SlidesApp.ShapeType.RECTANGLE,pos[i],50,50,30)
    
    rect.getText().setText(text).getTextStyle().setForegroundColor("#FFFFFF")
    rect.getFill().setSolidFill("#004ea8")
    rect.getBorder().setTransparent()
  }
}

点型の時系列

次は、箱の代わりに小さい丸、点を並べて時系列を図示してみます。
出来事の名前は点の上にテキストボックスで配置しています。

f:id:u874072e:20191004152228p:plain
点型の時系列

function chro_dot() {
  var X = load_data()
  var [pres, sl] = slide_initialize()

  var years = getColumn(X,0).map(Number)
  var pos = rel_position(years,50,pres.getPageWidth()-50)
  for(var i=0;i<X.length;i++){
    var text = X[i][1]
    var rect = sl.insertShape(SlidesApp.ShapeType.ELLIPSE,pos[i],50,10,10)
    var tbox = sl.insertShape(SlidesApp.ShapeType.TEXT_BOX,pos[i]-15,20,200,30)
    
    tbox.getText().setText(text)
    rect.getFill().setSolidFill("#004ea8")
    rect.getBorder().setTransparent()
  }
}

点型の時系列+時間

最後に、冒頭で示した点の時系列に、
時間も表示したものを生成します。

f:id:u874072e:20191004141724p:plain
点型の時系列+時間

function chro_dot_with_year() {
  var X = load_data()
  var [pres, sl] = slide_initialize()

  var years = getColumn(X,0).map(Number)
  var pos = rel_position(years,50,pres.getPageWidth()-50)
  for(var i=0;i<X.length;i++){
    var text = X[i][1]
    var rect = sl.insertShape(SlidesApp.ShapeType.ELLIPSE,pos[i],50,10,10)
    var tbox = sl.insertShape(SlidesApp.ShapeType.TEXT_BOX,pos[i]-15,20,200,30)
    sl.insertShape(SlidesApp.ShapeType.TEXT_BOX,pos[i]-15,80,200,30).getText().setText(years[i])
    
    tbox.getText().setText(text)
    rect.getFill().setSolidFill("#004ea8")
    rect.getBorder().setTransparent()
  }
}

まとめ

手作業では微調整がとても面倒な、
時系列の図解スライドをGASで自動生成する方法を紹介しました。

これを応用すれば、ガートナーのハイプサイクルのように、
グラフ上に大量の点と文字を配置した図も一発でつくれそうです。

【GAS】サイト改善の結果を自動的にチェックする

サイトの分析の結果、
サイトのデザインを修正するなど、
日々改善を行うことは重要です。

ただ、改善しっ放しもよくありません。

その改善で本当に効果が出たのかを確認して、
効果が出なかったとしたら、
打ち手の方向性を見直すことが必要です。

ところが、サイト改善の効果は、
すぐに現れるないことも多いので、
結果の確認を忘れがちです。

「忘れやすいことは自動化する」

ということで、
ここでは、サイトの改善効果を自動的にチェックする仕組みを実現します。

効果測定の自動化はなぜ必要か

冒頭でも述べたように、
サイトを改善しっ放しはよくありません。

改善の方向性があっているのかどうかを確認せずに改善を続けることは、
暗闇の中をひたすら真っ直ぐに走っているようなものです。

このまま続けるべきか、
方向性を見直した方がいいのかを考えるために、
結果を確認することが重要になってきます。

PDCAサイクル

サイトの改善でPDCAサイクルが重要だとよく言われています。

PDCAとはPlan, Do, Check, Actionの頭文字を取ったもので、
サイト改善の場合は次のような4つのフェーズに対応します。

  • Plan : 現状の問題を認識してどのような改善をするかを計画する
  • Do: サイトを修正する
  • Check: 改善の効果を確認する
  • Act: 結果を元に次の手を考える

f:id:u874072e:20191003150152p:plain
PDCAサイクル

この一連の流れを繰り返すことで、
より良いサイトへとしていくことが重要です。

サイト改善ではDCのギャップが大きい

ところが、サイト改善の場合、
改善した効果がすぐに現れるとは限りません。

例えば、新しいブログ記事を投稿したとして、
そのブログ記事がアクセスを集めるようになるには、
何百日かかることもザラにあります。

clean-copy-of-onenote.hatenablog.com

つまり、Doの後にCheckを行うまでの期間が長くなりがちです。

f:id:u874072e:20191003150729p:plain
DCのギャップ

これのどこが問題でしょうか。

こういうデザインにしよう(Plan)と思ってから、
実際にホームページを修正する(Do)まではすぐに行えます。

一方で、修正した効果を確認するのは何日、
何ヶ月と経ってからとなると、
そもそも修正したことを忘れてしまうことになりがちです。

PDCAサイクルのうち、D→Cの流れが忘れ去れることで、
サイクルが回らなくなってしまうのです。

この「忘れる」という問題に対する処方箋として、
「自動化」することが有効になります。

GASによる改善結果の自動チェック

サイトを修正した直後は、
当然、修正したことを覚えています。

そこで、サイト改善後にその履歴を記録することにします。

f:id:u874072e:20191003151712p:plain
サイト改善の履歴

この時、改善の効果を測定する指標を一緒に記録しておいて、
あとは自動的に効果を測定してくれれば、
Checkのプロセスを忘れることはなくなるでしょう。

ということで、このような表の情報を元に、
自動的に効果測定を行うGASのスクリプトを記述します。

変数の設定

改善の効果はGoogle Analyticsから得られる指標を用います。
そのため対象となるサイトのビューIDを変数で指定しておきます。

var viewId = "google analyticsのビューID"
var EVAL_DATE_RANGE = 30

ビューIDはGoogle Analytics上のビューに表示される数字です。

f:id:u874072e:20191003152354p:plain
ビューID

EVAL_DATE_RANGEは改善の前後何日のデータを使って、
効果を測定するのかを表す変数です。

指定した日付の前後のデータを取得

下の関数は、dateで指定した日付の前後の指標を取得します。
取得する指標はmetricsで指定します。

function get_ga_before_after(date,metrics) {
  
  s_date_b = add_date(date,-EVAL_DATE_RANGE)
  e_date_b = add_date(date,-1)
  s_date_a = add_date(date,1)
  e_date_a = add_date(date,EVAL_DATE_RANGE)
  
  var data = AnalyticsReporting.Reports.batchGet({
    reportRequests: [{
      viewId: viewId,
      dateRanges: [{
        startDate: fmt_date(s_date_b),
        endDate: fmt_date(e_date_b)
      },
                   {
                     startDate: fmt_date(s_date_a),
                     endDate: fmt_date(e_date_a)
                   }],
      metrics: [{
        expression: metrics,
        formattingType: "FLOAT"
      }]

    }]
  });
  Logger.log(JSON.parse(data))
  return JSON.parse(data)
}

関数の中で下で説明するいくつかのサブ関数を読んでいます。

日付を操作するためのサブ関数

改善前後の期間を取り出すために、
日付の足し算を行う関数add_dateを定義します。

また、日付を文字列として表す時の形式を整えるために、
関数fmt_dateを定義しています。

関数leftPaddingは8月→08のように日付の0埋めをするために利用します。

function add_date(date,add){
  tmp = new Date(date)
  tmp.setDate(tmp.getDate() + add)
  return(tmp)
}

function fmt_date(date){
  var y = leftPadding("" + date.getFullYear(),"0",4)
  var m = leftPadding("" + (date.getMonth()+1),"0",2)
  var d = leftPadding("" + date.getDate(),"0",2)
  
  return "" + y + "-" + m + "-" + d
}

function leftPadding(str,pad_char,num){
  if(str.length < num){
    return (Array(1+num - str.length).join(pad_char) + str)
  }
  return(str)
}

効果を測定して記録する

下のmyFunctionは、
スプレッドシートの各行を読み込んで、
必要な指標をgoogle analyticsから取得して、
結果をスプレッドシートに記録します。

これを1日毎など、定期的に実行することで、
自動的にサイト改善の効果をチェックし、
スプレットシート上に記録されます。

function myFunction(){
  var ss = SpreadsheetApp.getActive()
  var s = ss.getSheetByName("events")
  
  var row = s.getLastRow()
  
  var cells = s.getRange(2, 1, row, 3).getValues()
  
  for(var i = 0; i < (row-1); i++){
    var date = new Date(cells[i][0])
    var title = cells[i][1]
    var metrics = cells[i][2]
    
    if(add_date(date,EVAL_DATE_RANGE) > new Date()){
      var res = get_ga_before_after(date,metrics)
      var vb = res.reports[0].data.totals[0].values
      var va = res.reports[0].data.totals[1].values    
      saveEval(i+2,date,title,metrics,vb,va)
    }
  }
}

function saveEval(row,date,title,metrics,vb,va){
  var ss = SpreadsheetApp.getActive()
  var s = ss.getSheetByName("evaluation")
  
  s.getRange(row, 1,1,6).setValues([[date,title,metrics,vb,va,(va-vb)/vb*100]])
}

自動チェックの結果例

定期実行するスケジューラーを設定すれば、
あとは放っておくだけで下のように効果測定が行われます。

f:id:u874072e:20191003153734p:plain
自動効果測定の結果例

上の結果を見ると、メニューバーなどのサイトのデザインの修正の前後で、
いくつかの指標が向上していることがわかります。
特にリピーター率は約13%改善していることがわかります。

また、広告を自動挿入することで、
ユーザーあたりの広告クリック数が約66%改善していることがわかります。

clean-copy-of-onenote.hatenablog.com

clean-copy-of-onenote.hatenablog.com

まとめ

PDCAサイクルを回すための最大の障壁は、
D→Cへのギャップが大きいことが挙げられます。

やりっぱなしにならないように、
自動的にチェックする仕組みを導入することで、
しっかりとPDCAサイクルを回して、
正しい方向で改善が進められるようにしていきたいですね。

ブログの記事は投稿後何日でアクセスされるようになるのか

テクノロジーにはライフサイクルという考え方があります。

ある新規のテクノロジーは開発された当初は、
開発した本人やその周辺の限られた人しかその存在を知りません。

その後、アーリーアダプターと呼ばれる
テクノロジーに敏感な人たちに知られるようになり、
彼らが使っているうちに、その他の大勢の人たちが追随するように、
流行していきます。

ブログ記事も同じように、
更新を毎回チェックしてくれる人のアクセスから始まって、
その他大勢の人がアクセスするまでには時間がかかると考えられます。

今日書いたブログは何日後にアクセスを集めるようになるのでしょうか。

今回は、このブログにある各記事の
アクセス数の推移パターンを調べることで、
ブログ記事のライフサイクル、
つまり、投稿後何日でアクセスが集まるかを明らかにしてみましょう。

方法

Google Analytics から各ページのユーザー数の時系列を取得し、
各ブログ記事の投稿日と付き合わせて、
投稿後の経過日数毎のユーザー数を取得します。

記事毎に人気度のバラツキがありますが、
今興味があるのは、投稿後の経過日数によって、
いつ人気のピークを迎えるかということなので、
記事毎にアクセス数のピークを1に揃えます。
具体的には、記事毎の最大アクセス数で時系列を割ります。

すると、各記事毎に下のようなグラフを得ることができ、
ユーザー数がピークを迎えるタイミングや、
ピークがどれだけ持続するかを見ることができます。

f:id:u874072e:20190926105712p:plain
各記事のユーザー数の時系列

当然、記事毎にピークを迎えるタイミングや、
ピークが持続する期間に違いはありますが、
この中から全体に共通するパターンを抜き出すことで、
平均的なブログ記事のライフサイクルを見つけることにします。

結果

平均的な変化

共通パターンを取り出す簡単な方法は、
平均を取ることでしょう。

ピーク時のアクセス数を1とした各記事のアクセス時系列を、
平均化したのが下のグラフです。

f:id:u874072e:20190926115623p:plain
平均的なブログ記事のライフサイクル

グラフを見ると、投稿してすぐのタイミングで小さなピークがあり、
一旦、アクセスが減少してから、徐々にアクセスが増えていき、
300日経過する辺りでピークを迎えて横ばいの状態が続く様子が見えます。
経過日数が500日を過ぎた頃にグラフが乱れていますが、
これは、経過日数500日を超える記事の数が少なく、
少数の記事のアクセス変動に平均値が大きく振られてしまうためです。
そのため、グラフの後半の方は無視した方が良さそうです。

最初のピークは、まさにブログの更新をチェックしてくれている方々が、
投稿後すぐにアクセスしてくれることによるものでしょう。
その後、徐々に大勢の方にアクセスされるようになり、
平均的には300日程度経過した後に良くアクセスされるようになり、
その状態は少なくとも200日以上続きそうです。

各記事のライフサイクル上の位置付け

上で求めたライフサイクルを利用すれば、
各記事の経過日数から、
その記事へのアクセスがもうすぐ増えそうなのか、
すでにピークを迎えているのかの目安がわかります。

下の図のように、グラフ上に記事の経過日数をプロットすると、
どの記事がライフサイクルのどの辺りにいるか一目瞭然です。

f:id:u874072e:20190926120027p:plain
記事がライフサイクルのどのあたりにいるかをプロット

まとめ

記事がアクセスを集め始めるまでには時間がかかりますが、
それがどのくらいかかるものなのかを調べるため、
ブログ記事のライフサイクルを求めてみました。

このブログでは、記事を投稿後、
平均300日程度でアクセスのピークを迎えることがわかりました。

記事を投稿した直後のアクセスで一喜一憂することなく、
一年は寝かせるくらいの気長な気持ちで考えることが重要そうですね。

はてなブログで注目記事の順位を表示する方法

はてなブログのサイドバーに設置できる
「注目記事」の一覧に順位を表示してみます。

完成のイメージは下のような形になります。

f:id:u874072e:20190917172204p:plain:w300
注目記事の順位表示

CSSの編集

今回設定が必要なのは、CSSのみです。

はてなブログでは、
「管理画面」→「デザイン」→「カスタマイズ」→「デザインCSS」で
CSSの編集ができます。

f:id:u874072e:20190917172729p:plain:w300
cssの編集

順位を表示するCSS

デザインCSSの欄に以下のコードを追記します。

/* ranking */
.entries-access-ranking{
  counter-reset: rank 0;
}

.entries-access-ranking-item:before{
  counter-increment: rank 1;
  content: counter(rank) " ";
  background: royalblue;
  padding: 1px 10px;
  color: white;
}

これで注目記事の一覧に順位が表示されます。

以下では、CSSで行なっていることの詳細を説明します。

順位のカウンタ

CSS ではカウンタという変数を利用できます。

counter-resetでカウンタをリセットし、
counter-incrementでカウンタの値をインクリメントします。

カウンタには自由に名前をつけることができて、
上のコードではrankという名前をつけています。

.entries-access-rankingは注目記事一覧全体を表す要素で、
ここで、カウンタの値を0にリセットしています。

以降では、各記事の要素.entries-access-ranking-itemが現れるたびに、
カウンタを1ずつ増やしてます。

順位の表示

順位は記事の手前に表示したいので、
.entries-access-ranking-item:beforeとして、
:before擬似要素を指定しています。

順位として表示する文字はcontent属性に指定します。
ここではカウンタの値をcounter(rank)で参照して、
順位を表示しています。

バッジ型の順位表示にする

単に数字だけを表示するのでは素っ気ないので、
箱の中に数字が表示されるように装飾します。

backgroudで箱を塗りつぶす色を設定して、
paddingで箱の大きさ(文字周りの余白)を指定します。

また、文字の色をcolorで指定します。 ここでは、箱の色のroyalblueとの相性を考えて、
白抜きの文字にしています。

まとめ

はてなブログのサイドバーに表示できる
注目記事一覧の中で順位を表示するCSSを紹介しました。

コードはシンプルなので、
何をやっているか理解しやすいと思います。
これをベースに色々カスタマイズしてみるのも楽しそうですね。

自動ではてなブログの記事途中に広告を設置する方法

はてなブログでは、
記事の上下に自由にHTMLコードを設置できますが、
上下だけではなく、
記事の途中で広告を設置したいという要望が少なからずあります。

もちろん、手動で設置する場合は、
記事編集中に広告コードをコピペすれば良いのですが、
記事を書くたびにいちいち貼り付けるのも面倒です。

ここでは自動的に記事の途中に広告を設置する方法を紹介します。

検索して見つかる方法は、
広告を配置した要素を後から移動させる方法ですが、
これだと、scriptが実行前に一瞬、
意図しない場所に広告が配置されるので、
ここでは直接配置したい場所に広告を挿入する方法を紹介します。

Adsenseの広告コードを文字列化

JavaScriptで広告を挿入するために、
広告のコードを文字列化しておきます。

Google Adsenseから取得した広告コードを、
下のテキストボックスに貼り付けると、
文字列化したコードに変換します。

Adsenseの広告コードを貼り付け↓ 文字列化した広告コード↓

上の文字列化した広告コードをコピーしておきましょう。

見出し前に広告を設置

広告を自動で設置するために、
設置する位置を決めておく必要があります。

ここでは、見出しh1タグを目印に広告を設置します。

はてなブログでは、
- 0番目のh1タグ: ブログタイトル
- 1番目のh1タグ: 記事のタイトル
- 2番目以降: 記事中の見出し
となるので、2番目以降の見出しを目印に広告を設置すれば、
記事内に広告が設置されます。

下のコードでは、3番目の見出しの手前、
つまり記事の中で2つ目の大見出しの直前に、
広告を配置します。

<script>
    $('h1').eq(3).before(【文字列化した広告コードを貼り付け】)
</script>

上のコードをはてなブログのフッターなど、
スクリプトを配置できる箇所に追記すれば、
自動的に広告が挿入されるようになります。

jQueryを用いているので、必要に応じて、
外部から読み込むようにしておきましょう。

<script type="text/javascript" src="https://code.jquery.com/jquery-1.9.1.min.js"></script>

まとめ

コード自体はほんの数行で、
記事中に広告を自動設置することができます。

文字列化の面倒な処理部分は、
上に変換装置を作ってあるので、
活用して下さい。

プライバシーポリシー