LoginSignup
1
2

More than 5 years have passed since last update.

Google Colaboratory上で某ビジネス系オンラインメディアのPV推移のグラフをmatplotlibを用いて描画する

Last updated at Posted at 2018-10-02

1.概要

そういえば某ビジネス系オンラインメディアのPV推移を個人的な趣味で定点観測しているのですが、そのオンラインメディアのADページで、PVやUB(UniqueBrowser)の数値をHTMLページのTableタグで表記して公開している事に気付いたので、最近覚えたGoogle colaboratoryを使ってPandasのread_HTML()でPV数値を読み込んで、Matplotlibでグラフ描画しようというのが、本イシューの趣旨。
(注:今まで1個1個PVやUBの数値をExcel等にてコピペしてグラフ描画する的な作業をやっていたけれど、その作業が超絶面倒くさいので、数値取得からグラフ描画までをpython用いて全自動で描画して楽をしよう今流行りのRPA業務自動化?というものを目指そうというのが本イシューの趣旨)

※東洋経済広告HP
[デジタル広告]-[東洋経済オンラインとは]
https://biz.toyokeizai.net/ad/digital/about/

2.グラフアンチパターン

 まず本題に入る前に1点だけ。該当ADページにあるグラフが全くもってイケてないのではないかと思うのだが如何?(以下スクリーンショット画像を参照 ※2018年9月下旬時点のグラフ)↓
graph3.png

いろいろツッコミどころはあれど、とりあえず1点だけ言及すると。Y軸左右上にあるPVとUBの単位が「(千)」となっていて、これだと2億PVではなく2000億PV、3000万UBではなく300億UBということになってしまっているので、至急「(千)」の文字はトルツメしたほうが良いかと思われ。(← まあこれが本イシューで述べたかった事ではないので、とりあえずこのウンコグラフを他山の石にして、以下Matplotlibでのグラフ描画に続く...)

※参考
qiita:「データ視覚化のデザイン #1」をmatplotlibで実装する
https://qiita.com/skotaro/items/cdb0732ad1ad2a4b6236
ていうかこのADページのグラフを描画した中の人は、これを読んでグラフデザインについてお勉強することをオススメします。

3.オーソドックスなグラフ描画

3-1.スクリプトと実行結果
test.py
# encoding: utf-8

# 事前準備1:足りないライブラリをインストール
!pip install html5lib 
!pip install beautifulsoup4

# 事前準備2:pyライブラリ一覧を確認
!pip freeze

#########################

import pandas as pd
import matplotlib.pyplot as plt
import datetime

plt.style.use('classic')

def fn_hoge():

      # 対象URLの指定
      url = 'https://biz.toyokeizai.net/ad/digital/about/'

      # webページからtable読み込み
      df = pd.read_html( url ,flavor='bs4' )[0] 

      # 不要な列の削除、Prdの整形、不要な行の削除
      df.columns = ['prd','pv', 'ub', 'tpv']
      df = df.drop(df.columns[3], axis=1)
      df = df.drop(df.columns[2], axis=1)
      df['prd'] = df['prd'].str.replace(u'年 ','/')
      df['prd'] = df['prd'].str.replace(u'年','/')
      df['prd'] = df['prd'].str.replace(u'月', '')
      df['prd'] = pd.to_datetime(df['prd'])

      # Prdを昇順で並び替え
      df = df.set_index('prd')
      df = df.sort_index() 

      #print( df )

      # グラフ描画
      ax = df.plot( color='royalblue', kind='bar',label = 'main_pv', legend=False, figsize=(15 , 10 ) )
      plt.title('Toyokeizai Online: pageviews');

# main
if __name__ == '__main__':
      fn_hoge() 

実行した結果のグラフ↓
graph1.png

3-2.上記スクリプトの言及点

・Google ColaboratoryはGoogleDrive(Googleアカウント)があれば無料で利用可能なPython実行環境で、pandasやmatplotlibなどの主要なpythonライブラリは既にpip installされている。
・上記ではpandasのread_html()を利用して該当URLのHTMLファイルからtableタグの数値を読み込んで、pandasのdataframeオブジェクトを生成、そこからmatplotlibでグラフ描画をしている。
・pandasのread_html()の引数で読み込むURLを設定するのだが、上記スクリプトのようにflavorを指定することで利用するパースエンジンを変えることが可能(デフォルトはlxml)。
・Google Colaboratoryでread_html()利用時に、lxmlがない的なエラーが表示されるので、上記スクリプトでは、flavorをbs4(beautiful Soup)等lxml以外のパースエンジンを指定してエラーを回避している。
※2018年10月追記
・flavorをbs4に指定してもbs4がないとエラーが出るようになった。なので最初にpipコマンドでhtml5libやbeautifulsoup4などを入れておく。

table1.png
・該当ページのPVやUBなどの数値が表記されているTableタグの部分は上記スクリーンショットの画像のようになっているのだが、年月の列が「2018年 1月」と「2017年12月」と「2016年 12月」といったように半角スペースの入れ方がバラバラなので、これを「YYYY/mm/dd hh: mm:sss」のdatetime型に変換するためにreplace関数など用いてゴニョゴニョやっている。
・今回は純PVの数値を用いて棒グラフを描画する。総PVとUBは今回は割愛。ていうか恐らく一つのグラフにPVとUBなど複数の数値を描画させると見づらくなるのではないかと思...
・上記スクリプトのデータの参照先はWeb上で公開されたHTMLファイルのTableタグで記述された箇所なので、Google Colaboratory上でセルにコピペすれば実行可能。Google Colaboratoryの使い方については、この記事などを参考されたし。

3-3.グラフ描画時のデザイン的な言及点

・Y軸の単位は、PVのスケールが億単位なのでLe8(Log対数表記)になる模様。
・ただしLe8だとあれなので、指数表記で10の4乗(万)PVにしたい。
・X軸は日付(yyyy-MM-dd hh: mm:sss)にすると、当然横に長くなる表記なので、すべてを表現しようとすると回転させて縦記述になる。これは見づらくなるので避けたいところ。
・X軸の日付は、そもそもすべて表記する必要はなく、今回のグラフの場合、数年レンジなのでyyyy-MM-dd hh: mm:sssで記述する必要もないかと。今回のグラフの場合は、年もしくは月まであればOKなのではないかと思う。
・ページのHTMLのテーブル表は2013年3月以降の数値があるので、グラフは2013年3月からスタートしている。 当該オンラインメディアのPV数値の成長度合いをグラフで表現しようとするならば、変に2016年10月から描画するのではなく、オンラインメディアがリニューアルした2012年11月以降の3000万PV前後くらいで推移していた時期を起点にしてグラフ描画したほうが、成長度が著しいグラフが描画できてよりアピールできるのではないかと思うのだが...(前半のノリ佐々木時代は黒歴史であまり触れたくないということなのだろうか?

4.もろもろを改良したグラフ描画

test.py
# encoding: utf-8
import pandas as pd
import numpy as np

import matplotlib.pyplot as plt
from matplotlib.ticker import ScalarFormatter

import datetime
plt.style.use('classic')

# Y軸の指数表記用Formatterクラス
class FixedOrderFormatter(ScalarFormatter):
    def __init__(self, order_of_mag=0, useOffset=True, useMathText=True):
        self._order_of_mag = order_of_mag
        ScalarFormatter.__init__(self, useOffset=useOffset, 
                                 useMathText=useMathText)
    def _set_orderOfMagnitude(self, range):
        self.orderOfMagnitude = self._order_of_mag

def fn_hoge():

      # 対象URLの指定
      url = 'https://biz.toyokeizai.net/ad/digital/about/'

      # webページからtable読み込み
      df = pd.read_html( url ,flavor='bs4' )[0]

      # 不要な列の削除、Prdの整形、不要な行の削除
      df.columns = ['prd','pv', 'ub', 'tpv']
      df = df.drop(df.columns[3], axis=1)
      df = df.drop(df.columns[2], axis=1)
      df['prd'] = df['prd'].str.replace(u'年 ','/')
      df['prd'] = df['prd'].str.replace(u'年','/')
      df['prd'] = df['prd'].str.replace(u'月', '')
      df['prd'] = pd.to_datetime(df['prd'])

      # Prdを昇順で並び替え
      df = df.set_index('prd')
      df = df.sort_index() 

      #print( df )

      # グラフ描画
      ax = df.plot( color='royalblue', kind='bar',label = 'main_pv', legend=False, figsize=(15 , 10 ) )
      plt.title('Toyokeizai Online: pageviews');


      # X軸の設定
      ln = len(df.index)
      a,b = np.arange(ln)+1,list()

      for i in a-1:
          if i==0: #始点
             b.append('') 
          elif i>=ln-3:  #終点
             b.append('') 
          elif (i-3)%12!=0: 
             b.append('') 
          else: 
             b.append(str(df.index[i].strftime('%Y'))) 

      ax.set_xticklabels(b,rotation =0 ,horizontalalignment ='left') #回転なし/左寄せ
      ax.set_xlabel("")

      # Y軸の設定
      ax.yaxis.offsetText.set_fontsize(24) #font-size
      ax.yaxis.set_major_formatter(FixedOrderFormatter(4)) #指数表記:10^4
      ax.yaxis.offsetText.set_color('gray')
      ax.yaxis.label.set_color('gray')
      ax.grid(axis='y')
      ax.set_ylabel("PV")

      ax.tick_params(bottom=False, left=False)
      ax.tick_params(axis='y', colors='gray') 
      ax.tick_params(axis='x', colors='dimgray')

      # 再描画
      plt.gcf().canvas.draw()

      # X軸にて年度毎に縦線を入れる
      for key, gr in df.groupby(df.index.year):
          i = np.where(df.index == gr.index[0])[0][0]
          pos = ax.get_xmajorticklabels()[i].get_position()

          #print( key )
          #print(pos[0])

          if i!=0:
             ax.annotate('', xy=(pos[0]+0.1, 0), xycoords='data',
                         xytext=(0, -20), textcoords='offset points', color='dimgray',
                         ha='center', va='bottom',arrowprops={'arrowstyle':'-', 'color':'dimgray'})


# main
if __name__ == '__main__':
      fn_hoge() 

実行した結果のグラフ↓
graph2.png
・Y軸の目盛りをこの記事を参考にしてFormatterクラスを宣言して指数表記(10の4乗、つまり万)に変更した。(← 一瞬「千万」か「億」単位にしようかとも思ったが、5000万PVが0.5億の表記、1億PVが10千万PVの表記がなんとなくいやだなーと思ったので、今回は単位を万単位にした。
・Y軸の目盛りの数値にはカンマは不要でしょ?
・X軸は年のみ表記して、この記事を参考にして最後に年単位に縦棒を挿入してみた。確かに年単位に縦棒を挿入する前に、一度グラフ再描画のコマンドplt.gcf().canvas.draw()をする必要がある模様。

5.蛇足

・現在多くのオンラインメディアのADページは、媒体資料pdfが公開されていて、PV等の数値はそのpdfに記載があるケースが大多数なのだが。pandasを利用することを前提に考えると、例えばcsvファイルを置いたり、excelファイルを置いたり、このオンラインメディアのようにHTMLページにTableタグで数値を表記したりしてもらうと、容易にpandasで数値を読み取ってdataframeを生成することが可能(pdfを読み込んでpandasのdataframeを生成するとかは恐らく超煩雑だと思うので、他の多くのオンラインメディアも是非ともADページでpandasユーザーファーストでPV等の数値をHTMLページにTableタグで表記するかcsvファイル等で公開してもらいたいものである)

・このADページにあるTableタグのPV数値の年月の列の表記が、半角スペースを入れたり入れなかったりとにかくバラバラなので、表記を統一してほしい。もっというなればpandasでdataframeを生成することを考えると「yyyy年mm月」ではなく「yyyy-mm-dd」の表記でお願いしたい!

・あとこのTableタグの数値からPV/UB(1UB当たりのPV数)をpandasで計算してグラフ描画を試みて気付いたのだけど、2015年6月以前のUB数値の桁が間違ってませんでしょうか?(2015年6月以前のPV/UBが70前後で推移していて、2015年7月に数値が1/10に激しく小さくなってそれ以降はPV/UBの数値が7前後で推移していることから推測。そんなある時期を境にPVの水準が大きく変わらないまま、UB数がいきなり10倍になるものなのかなー?)
table2.png

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2