2017年5月4日木曜日

Webスクレイピングで自動車販売データを取得する

こちらのサイトから個人的な趣味としての分析用のデータを取得したいと思ったのでその内容を記す。

分析結果イメージ

実際に取得したデータをplotしたものが以下の画像となる。

分析結果イメージ:ブランド別の月度売上台数分布

関数作成編

URLからデータフレームを取得する

def generateDataFrame(url):#get DataFrame from url
        df = pd.io.html.read_html(url)
        return df

変数はurlのみ。このurlは後ほど言及するがhtmlファイルの保存ディレクトリを指定する。 Webページの仕様上、各年度のデータごとにurlが定義されていないため。 



データ整形①:不要な行を削除する

def dropunnecessaryRow(df,colList_tmp,dropColumns):#change columns name and drop unnecessary row
        df.columns = colList_tmp#一時的なカラム名を定義
        df = df.drop(0)#不要な行が1行目に含まれているため削除
        df = df.drop(1)#不要な行が2行目に含まれているため削除
        df = df.drop(dropColumns,axis=1)#削除したいカラム名を予め定義していた"dropColumns"というリストでカラムを削除する
        return df

 実はそのままデータフレームを生成すると以下のようにNaNを含んだり”3月”や”4〜3月”のように使いづらいカラム名が設定されている。一つずつ作り直すのも手間なので行丸ごと新たなものに入れ替える。加えてデータフレーム内に2つの時系列データが含まれている(1月と2月のように)のでそれらを別々のデータフレームに分離する。そのためにここからの処理ではdf1/2と分けてdropColumns1/2のようにそれぞれのデータフレームに対応したデータを後々用意することになる。

ちなみに以下はこちらのリンク先データを抜き出したもの。0-1行目に不要なカラム名が設定されているのがわかる。


0    月       3月    NaN      月     4~3月 NaN  NaN      NaN    NaN     NaN   
1   順位  ブランド通称名  ブランド名     台数      前年比 NaN   順位  ブランド通称名  ブランド名      台数   
2    1      ノート     日産  24383    178.2 NaN    1     プリウス    トヨタ  225066   
3    2     プリウス    トヨタ  22447     71.4 NaN    2      アクア    トヨタ  155566   
4    3      アクア    トヨタ  17798     76.3 NaN    3     シエンタ    トヨタ  127392   
5    4     C-HR    トヨタ  16816  (28-12) NaN    4      ノート     日産  123938   
6    5     フリード    ホンダ  14799    319.5 NaN    5     フィット    ホンダ   98923   
7    6      セレナ     日産  14577    192.7 NaN    6    ヴォクシー    トヨタ   92421   
8    7     シエンタ    トヨタ  13336    103.5 NaN    7      セレナ     日産   90369   
9    8     ヴィッツ    トヨタ  12654    128.9 NaN    8     カローラ    トヨタ   81391   
10   9     フィット    ホンダ  11629     74.8 NaN    9     ヴィッツ    トヨタ   78634   
11  10    ヴォクシー    トヨタ  10831    100.6 NaN   10     フリード    ホンダ   75384   
12  11   インプレッサ    スバル   9969    223.0 NaN   11      パッソ    トヨタ   74707   
13  12     CX-5    マツダ   9669    323.3 NaN   12     ヴェゼル    ホンダ   73583   
14  13     カローラ    トヨタ   9409     83.1 NaN   13       ノア    トヨタ   55061   
15  14     ヴェゼル    ホンダ   8976    100.1 NaN   14  ヴェルファイア    トヨタ   53799   
16  15     ルーミー    トヨタ   8253  (28-11) NaN   15      デミオ    マツダ   53321   
17  16      タンク    トヨタ   7614  (28-11) NaN   16  エクストレイル     日産   52948   
18  17     ハリアー    トヨタ   7360    152.6 NaN   17   インプレッサ    スバル   52756   
19  18  エクストレイル     日産   7289     86.3 NaN   18     ハリアー    トヨタ   48366   
20  19      デミオ    マツダ   7082     93.2 NaN   19  ステップワゴン    ホンダ   48110   
21  20      パッソ    トヨタ   7053    133.8 NaN   20      ソリオ    スズキ   47795   
22  21  ヴェルファイア    トヨタ   6538    130.6 NaN   21   エスクァイア    トヨタ   44136   
23  22       ノア    トヨタ   5934     88.4 NaN   22     C-HR    トヨタ   43600   
24  23  ステップワゴン    ホンダ   5884     82.0 NaN   23   アルファード    トヨタ   39243   
25  24     スイフト    スズキ   5465    195.0 NaN   24     シャトル    ホンダ   38367   
26  25   エスクァイア    トヨタ   5317     86.0 NaN   25     クラウン    トヨタ   36483   
27  26      ソリオ    スズキ   5244    103.4 NaN   26     ルーミー    トヨタ   30058   
28  27   アルファード    トヨタ   4978    123.7 NaN   27     アクセラ    マツダ   28770   
29  28     アクセラ    マツダ   4105    132.8 NaN   28    オデッセイ    ホンダ   28584   
30  29     シャトル    ホンダ   3935     75.1 NaN   29      タンク    トヨタ   27537   
31  30     クラウン    トヨタ   3692     69.5 NaN   30     CX-5    マツダ   27193  

データ整形②:新たなカラムを追加する

def addDatetoDataFrame(df,dateList,date):#ex)df以外のそれぞれの変数はdate:2017/1,dateList=[]のように定義される
        for i in range(0,len(df.index)):#空のdateListにdateで指定した日付をデータフレームに含まれるデータ数分追加する
                dateList.append(date)
        df['date'] = dateList#dateListを'date'カラムとして追加する
        return df

先ほどは不要なカラムを削除した。次は新たに必要なカラムを追加する。
時系列でデータを眺めたいのだがその日付データがない。
そのためdateで追加したい日付を定義する。

データ整形③:新たにカラム名を定義する

def convertColumns(df,colList):#colList1 or 2
        df.columns = colList
        return df

このままではデータフレームのカラム名が歯抜けの状態なので事前に定義しているcolListをカラム名として追加する。

データ整形④:データフレームを結合する

def connectDataFrame(df1,df2):
        df = pd.concat([df1,df2])
        return df
先ほども述べたように一つのデータフレームを二つに分けたのでそれを再び一つにする。
これで時系列でデータを見られるようになる。

データ整形⑤:インデックスを新たに定義する

def resetIx(df):
        df = df.reset_index(drop=True)
        return df
2つのデータフレームを一つにしただけではインデックスがごちゃごちゃしていて何かと都合が悪いのでインデックスを振り直す。

データ整形⑥:文字列を数値に変換する

def convertStringtoInt(df):#replace string to int value and Japanease to English
        for i in range(0,len(df.index)):
                df['volume'][i] = int(df['volume'][i])
        return df
売上台数などのデータが全てstring型なのでint型に変換する。

データ整形⑦:日本語を英語表記にする

def convertJaptoEng(df):#replace Japanease to English
        df['model'] = df['model'].replace(u'ノート',u'note')
        df['model'] = df['model'].replace(u'セレナ',u'serena')
        df['model'] = df['model'].replace(u'プリウス',u'prius')
        df['model'] = df['model'].replace(u'C-HR',u'C-HR')
        df['model'] = df['model'].replace(u'シエンタ',u'sienta')
        df['model'] = df['model'].replace(u'フリード',u'freed')
        df['model'] = df['model'].replace(u'アクア',u'aqua')
        df['model'] = df['model'].replace(u'ヴォクシー',u'voxy')
        df['model'] = df['model'].replace(u'ルーミー',u'roomy')
        df['model'] = df['model'].replace(u'フィット',u'fit')
        df['model'] = df['model'].replace(u'インプレッサ',u'impreza')
        df['model'] = df['model'].replace(u'ヴェゼル',u'vezel')
        df['model'] = df['model'].replace(u'タンク',u'tank')
        df['model'] = df['model'].replace(u'ヴィッツ',u'vitz')
        df['model'] = df['model'].replace(u'ハリアー',u'hurrier')
        df['model'] = df['model'].replace(u'エクストレイル',u'xtrail')
        df['model'] = df['model'].replace(u'ヴェルファイア',u'velfire')
        df['model'] = df['model'].replace(u'カローラ',u'carolla')
        df['model'] = df['model'].replace(u'デミオ',u'demio')
        df['model'] = df['model'].replace(u'ノア',u'noah')
        df['model'] = df['model'].replace(u'スイフト',u'swift')
        df['model'] = df['model'].replace(u'ソリオ',u'solio')
        df['model'] = df['model'].replace(u'パッソ',u'passo')
        df['model'] = df['model'].replace(u'ステップワゴン',u'stepwgn')
        df['model'] = df['model'].replace(u'アクセラ',u'axela')
        df['model'] = df['model'].replace(u'アルファード',u'alfard')
        df['model'] = df['model'].replace(u'エスクァイア',u'esquire')
        df['model'] = df['model'].replace(u'シャトル',u'shuttle')
        df['model'] = df['model'].replace(u'クラウン',u'crown')
        df['model'] = df['model'].replace(u'フォレスター',u'forester')
        df['model'] = df['model'].replace(u'CX-5',u'CX-5')
        df['model'] = df['model'].replace(u'ランドクルーザーW',u'landcruiser-w')
        df['model'] = df['model'].replace(u'オデッセイ',u'odyssey')
        df['model'] = df['model'].replace(u'リーフ',u'leaf')
        df['model'] = df['model'].replace(u'レヴォーグ',u'levorg')
        df['model'] = df['model'].replace(u'プレミオ',u'premio')
        df['model'] = df['model'].replace(u'スペイド',u'spaid')
        df['model'] = df['model'].replace(u'エスティマ',u'estima')
        df['model'] = df['model'].replace(u'イグニス',u'igunis')
        df['model'] = df['model'].replace(u'オーリス',u'auris')

        df['brand'] = df['brand'].replace(u'トヨタ',u'toyota')
        df['brand'] = df['brand'].replace(u'ホンダ',u'honda')
        df['brand'] = df['brand'].replace(u'日産',u'nissan')
        df['brand'] = df['brand'].replace(u'マツダ',u'mazda')
        df['brand'] = df['brand'].replace(u'スバル',u'subaru')
        df['brand'] = df['brand'].replace(u'スズキ',u'suzuki')

        return df

plot時に文字列が日本語だと文字化けするので全てローマ字表記に変換する。
ブランド名とモデル名を全て変換する。何を変換するかは手作業で洗い出すしかない。

処理実行編


モジュールのインポート

import pandas as pd
import matplotlib.pyplot as plt
import htmltable_mod as hl

先ほどの関数編で作成したスクリプトファイルをhtmltable_mod.pyで同じディレクトリに保存したのでモジュールとしてインポート


事前データの定義

colList1 = ['rank','model','brand','volume','compare','NaN','date']
colList2 = ['NaN','rank','model','brand','volume','compare','date']
colList_tmp = ['rank01','model01','brand01','volume01','compare01','NaN','rank02','model02','brand02','volume02','compare02']
dropColumns1 = ['rank02','model02','brand02','volume02','compare02']
dropColumns2 = ['rank01','model01','brand01','volume01','compare01']
date1 = '2016/01'
date2 = '2016/02'
date3 = '2016/03'
date4 = '2016/04'
date5 = '2016/05'
date6 = '2016/06'
date7 = '2016/07'
date8 = '2016/08'
date9 = '2016/09'
date10 = '2016/10'
date11 = '2016/11'
date12 = '2016/12'
url = 'file:///Users/xxx/Downloads/2016.htm'
$HOME/xxx/Documents/mymod

関数編でそれぞれの関数の変数としていたデータを処理前に事前定義しておく。
同じ名前の番号違いは元データの都合上データフレームを2つに分けざるを得ないので
それぞれに対するデータを定義する。
dateは一年間の各月分作る。また、urlはhtmlファイルの保存先を指定している。

処理パターン①:単月データ(ex:1月、2月)のみがデータフレームに含まれている場合

def main1(df,colList_tmp,dropColumns1,dropColumns2,date1,date2):
        dateList1 = []
        dateList2 = []
        colList1 = ['rank','model','brand','volume','compare','NaN','date']
        colList2 = ['NaN','rank','model','brand','volume','compare','date']
        df1 = hl.dropunnecessaryRow(df,colList_tmp,dropColumns1)#データ整形①:不要な行を削除する
        df2 = hl.dropunnecessaryRow(df,colList_tmp,dropColumns2)#データ整形①:不要な行を削除する
        df1 = hl.addDatetoDataFrame(df1,dateList1,date1)#データ整形②:新たなカラムを追加する
        df2 = hl.addDatetoDataFrame(df2,dateList2,date2)#データ整形②:新たなカラムを追加する
        df1 = hl.convertColumns(df1,colList1)#データ整形③:新たなカラム名を定義する
        df2 = hl.convertColumns(df2,colList2)#データ整形③:新たなカラム名を定義する
        df = hl.connectDataFrame(df1,df2)#データ整形④:データフレームを結合する
        df = hl.resetIx(df)#データ整形⑤:インデックスを新たに定義する
        df = hl.convertStringtoInt(df)#データ整形⑥:文字列を数値に変換する
        df = hl.convertJaptoEng(df)#データ整形⑦:日本語を英語表記にする

        return df

単月データのみが含まれているデータフレーム用に関数を組み合わせた処理を定義する。
二つのデータフレームに対して重複した処理を行っていく。

処理パターン②:複数月データ(ex:3月、3~4月)がデータフレームに含まれている場合

def main2(df,colList_tmp,dropColumns,date):
        dateList = []
        colList = ['rank','model','brand','volume','compare','NaN','date']
        df = hl.dropunnecessaryRow(df,colList_tmp,dropColumns)
        df = hl.addDatetoDataFrame(df,dateList,date)
        df = hl.convertColumns(df,colList)
        df = hl.resetIx(df)
        df = hl.convertStringtoInt(df)
        df = hl.convertJaptoEng(df)

        return df

先ほどと同じ処理、複数月データは除外するので全ての処理は一つずつ行う。
データフレームの結合はなしとする。

データフレームの生成

df = hl.generateDataFrame(url)
#select dataframe from list object
df1 = df[1]
df2 = df[2]
df3 = df[3]
df4 = df[4]
df5 = df[5]
df6 = df[6]
df7 = df[7]
df8 = df[8]

df1 = main1(df1,colList_tmp,dropColumns1,dropColumns2,date1,date2)
df2 = main2(df2,colList_tmp,dropColumns1,date3)
df3 = main1(df3,colList_tmp,dropColumns1,dropColumns2,date4,date5)
df4 = main2(df4,colList_tmp,dropColumns1,date6)
df5 = main1(df5,colList_tmp,dropColumns1,dropColumns2,date7,date8)
df6 = main2(df6,colList_tmp,dropColumns1,date9)
df7 = main1(df7,colList_tmp,dropColumns1,dropColumns2,date10,date11)
df8 = main2(df8,colList_tmp,dropColumns1,date12)

元データで対象とするのは8つのデータフレーム。それぞれに対して先ほど定義したmain関数で処理をしていく。奇数番号のデータフレームでは一つのデータフレームを2つに分けてまた一つに結合する、という処理を行う。


データフレームの結合

df = pd.concat([df1,df2,df3,df4,df5,df6,df7,df8])

先ほど生成した8つのデータフレームを一つに結合する。これでほとんど目的は達成した。

プロット

pt = pd.pivot_table(df, index=['brand','model'],columns='date',values='volume',aggfunc='sum')
pt.plot.bar(cmap='rainbow')
plt.legend(loc=4,bbox_to_anchor=(0,0),fontsize=5)
plt.tick_params(labelsize=6)
#legend location
##upper right:1,upper left:2,lower left:3,lower right:4,right:5
##center left:6,center right:7,lower center:8,upper center:9,center:10
plt.show()

おまけ。最終的にプロットしたい形にデータフレームを変えるためにピボットテーブルで整形する。この形が適しているのかどうかは別としてとりあえずプロットしてみたのが冒頭の画像である。

0 件のコメント:

コメントを投稿