【みんな大好きカレーパン】Rubyでe-Statから東京のカレーパンの時系列売価を引っ張るプログラム

## はじめに
e-StatのAPIを使って、外部からデータを引っ張るプログラムの紹介です。
外部データを引っ張れるようになるとかなり楽しいです！

今回はみんな大好き**カレーパン**の時系列売価を引っ張ってみました！

まだ触ったことのない方に向けて記載してみました！

## この記事が役立つ方
- WebAPI初心者で
- 外部からデータを引っ張ってみたい
- Ruby初心者

## この記事のメリット
- 外部データを引っ張る具体的な手順が分かるようになる

## 環境
-  OS： macOS Mojave version 10.14.6
* シェル： zsh
* 使用言語： Ruby 2.6.3
* HTTPクライアントライブラリ： HTTPClient
* 使用するWeb API： [e-Stat−API](https://www.e-stat.go.jp/api/)
* 取得するデータ：JSON形式

## ディレクトリ構成
今回のディレクトリ構成は以下の通りです。

price_check_app/
├ conf/ ─ config.yml
├ src/ ─ Price.rb
│　　　 	    └ PriceInfo.rb
├ test/ ─ test.rb
└ lib/

では、実際にプログラムを組んでみましょう。
まずは事前準備からです。

## 【準備 １】e-Statアカウント作成とアプリケーションID発行
<img width="837" alt="スクリーンショット 2019-11-02 9.10.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483292/6bf600be-29ed-6942-f070-635245c29e86.png">

[利用ガイド | 政府統計の総合窓口(e-Stat)−API機能](https://www.e-stat.go.jp/api/api-info/api-guide)

上記リンクに従い、アカウント作成とアプリケーションIDの発行を実施してください。
API機能（アプリケーションID発行）で取得できます。
作成したアプリケーションを外部公開しない場合、入力するURLは http://localhost/ で大丈夫です。

## 【準備 ２】e-StatのDBからどのデータを拾うかを決める
[小売物価統計調査 小売物価統計調査（動向編） 主要品目の都市別小売価格－県庁所在市及び人口15万以上の市(2015年1月～) 月次 | データベース | 統計データを探す | 政府統計の総合窓口](https://www.e-stat.go.jp/stat-search/database?page=1&layout=datalist&toukei=00200571&bunya_l=07&tstat=000000680001&cycle=1&statdisp_id=0003105586&result_page=1&tclass1val=0)
今回はこれを使ってみようと思います。

ここからは欲しいデータを引っ張るために、いくつかIDなどをメモしておく必要があります。
<img width="414" alt="スクリーンショット 2019-11-02 8.57.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483292/1d40de16-5c65-1e1c-dda2-dff122e1bb90.png">
**1.統計表表示 ID**：　0003105586
<img width="575" alt="スクリーンショット 2019-11-02 8.57.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483292/08e566dd-14c5-b749-a81f-7de4e9a26de9.png">
今回は大好きなカレーパンの物価推移を見てみることにします。
**2.銘柄(H27年基準)**：　1023（カレーパン）

<img width="867" alt="スクリーンショット 2019-11-02 8.55.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483292/7af04267-996c-5286-f377-843fe998b9b7.png">
[市区町村を探す | 政府統計の総合窓口](https://www.e-stat.go.jp/municipalities/cities/areacode)
あとは上記リンクから、拾いたい都市名の地域コードもメモしましょう。

今回は東京都の特別区部のカレーパンの物価を見てみることにします。
拾うデータによって対応している地域がまちまちなので、欲しい地域が見当たらないかもしれません。

**3.標準地域コード**：　13100（東京都 特別区部）

## 【準備 3】HTTPClientのgemインストール
プログラムの下準備です。
今回はHTTPクライアントライブラリにHTTPClientを使用します。

```zsh
$ gem install httpclient
```
なければ上記のコマンドでHTTPClientのgemをインストールして下さい。

## 【準備 4】出力されるJSONの構造理解のために必要なもの
<img width="621" alt="スクリーンショット 2019-11-02 9.45.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483292/b64e6ae9-fc12-f20f-280b-be4c68223b68.png">

[JSON Formatter - Chrome ウェブストア](https://chrome.google.com/webstore/detail/json-formatter/bcjindcccaagfpapjjmafapmmgkkhgoa?hl=ja)

Chromeの拡張機能の紹介です。
JSONの可読性を高めるために、このJSON Formatterが大活躍でした。

出力データが配列 in ハッシュ in 配列 in ...みたいな内容なので、冷静に`[]`と`{}`を見ないと混乱します。

【Before】　JSON Formatterなし
「もうマヂ無理…」
<img width="1276" alt="スクリーンショット 2019-11-02 9.09.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483292/ceeafc15-2ddf-ade3-c2a1-c5048313ed55.png">

【After】　JSON Formatter**あり**
「スッキリ！なるほどこういう構造ね！」
<img width="1277" alt="スクリーンショット 2019-11-02 9.09.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483292/74251bd1-4678-7df0-a930-dfcd4dd27b1e.png">


絶対入れておいた方がいいです。

次からソースコードの紹介に移ります。


## 【ソースコード】conf/config.yml
まずは`config.yml`です。
可変式にするために抜き出しました。
ここのIDなどを変えることで、拾える情報が変わります。

```ruby:conf/config.yml
#API取得先URL。appの後の「json」でファイル形式を指定
url : 'http://api.e-stat.go.jp/rest/2.1/app/json/getStatsData'
#e-Statから取得したアプリケーションID
appId : '00000000000000000000000000'
#統計表ID
statsDataId : '0003105586'     
#銘柄(H27年基準)CD(5ケタ表記)  
cdCat01 : '01023'              
#標準地域コード                
cdArea : '13100'
```
## 【ソースコード】src/Price.rb
次に本体です。
各コードの詳細はコメントをご確認ください、

```ruby:src/Price.rb
require 'httpclient'
require 'resolv'
require 'json'
require 'yaml'
require './src/PriceInfo'

class Price

  #JSON形式で出力されたときの階層別に変数定義しておく。拾わないものは定義不要。

  #ルートパラメータ
  @@GET_STATS_DATA = "GET_STATS_DATA"
  #統計データの情報
  @@STATISTICAL_DATA = "STATISTICAL_DATA"

  #帳票情報
  @@TABLE_INF = "TABLE_INF"
  #大分類
  @@STATISTICS_NAME = "STATISTICS_NAME"
  #帳票名
  @@TITLE = "TITLE"
  #調査サイクル
  @@CYCLE = "CYCLE"
  #最終更新日
  @@UPDATED_DATE = "UPDATED_DATE"

  #クラス情報
  @@CLASS_INF = "CLASS_INF"
  @@CLASS_OBJ = "CLASS_OBJ"

  #データ情報
  @@DATA_INF = "DATA_INF"
  @@VALUE = "VALUE"

  #valueの中身
  @@TIME = "@time"

  #コンストラクタ
  def initialize()
  end 

  #メイン処理メソッド
  def doProcess()
    #API取得先URLの読み込み
    url = YAML.load_file('./conf/config.yml')["url"]
    #e-Statから取得したアプリケーションIDの読み込み
    appId = YAML.load_file('./conf/config.yml')["appId"]
    #統計表IDの読み込み
    statsDataId = YAML.load_file('./conf/config.yml')["statsDataId"]
    #銘柄(H27年基準)の読み込み
    cdCat01 = YAML.load_file('./conf/config.yml')["cdCat01"]
    #標準地域コードの読み込み
    cdArea = YAML.load_file('./conf/config.yml')["cdArea"]
    return analysisPrice(connectionAPI(url, appId, statsDataId, cdCat01, cdArea))
  end 

  #API接続部
  def connectionAPI(url, appId, statsDataId, cdCat01, cdArea)
    #http接続クライアントの生成
    client = HTTPClient.new
    #クエリの設定
    query = { 
      'appId' => appId,
      'statsDataId' => statsDataId,
      'cdCat01' => cdCat01,
      'cdArea' => cdArea
    } 
    #APIリクエスト
    res = client.get(url, query)
    #ハッシュ化して返却
    return JSON.parse(res.body)
  end 

  #connectionAPIで出力されたハッシュを解析
  def analysisPrice(hash)
    info = PriceInfo.new

    #出力したい情報をdigメソッドで掘り出すinfo.statistics_name=(convertNil(hash.dig(@@GET_STATS_DATA, @@STATISTICAL_DATA, @@TABLE_INF, @@STATISTICS_NAME)))
    info.title=(convertNil(hash.dig(@@GET_STATS_DATA, @@STATISTICAL_DATA, @@TABLE_INF, @@TITLE)))
    info.cycle=(convertNil(hash.dig(@@GET_STATS_DATA, @@STATISTICAL_DATA, @@TABLE_INF, @@CYCLE)))
    info.updated_date=(convertNil(hash.dig(@@GET_STATS_DATA, @@STATISTICAL_DATA, @@TABLE_INF, @@UPDATED_DATE)))
    info.class_obj=(convertNil(hash.dig(@@GET_STATS_DATA, @@STATISTICAL_DATA, @@CLASS_INF, @@CLASS_OBJ)))
    info.value=(convertNil(hash.dig(@@GET_STATS_DATA, @@STATISTICAL_DATA, @@DATA_INF, @@VALUE)))
    return info
  end 

  #nil判定、nilの場合は「-」を返却
  def convertNil(value)
    return value == nil ? "-" : value
  end 
end
```

## 【ソースコード】src/PriceInfo.rb
このファイルで欲しい情報を抜き出すための準備をしています。

```ruby:src/PriceInfo.rb
class PriceInfo
  attr_accessor :get_stats_data,
    :statistical_data,
    :table_inf,
    :statistics_name,
    :title,
    :cycle,
    :updated_date,
    :data_inf,
    :value,
    :time


  #コンストラクタ
  def initialize()
  end

  #ルートパラメータ
  def get_stats_data()
    @get_stats_data
  end
  def get_stats_data=(value)
    @get_stats_data = value
  end

  #統計データの情報
  def statistical_data()
    @statistical_data
  end
  def statistical_data=(value)
    @statistical_data = value
  end

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

  #表データの情報
  def table_inf()
    @table_inf
  end
  def table_inf=(value)
    @table_inf = value
  end

  #調査分類
  def statistics_name()
    @statistics_name
  end
  def statistics_name=(value)
    @statistics_name = value
  end

  #表のタイトル
  def title()
    @title
  end 
  def title=(value)
    @title = value
  end 
 #調査周期
  def cycle()
    @cycle
  end 
  def cycle=(value)
    @cycle = value
  end 

  #更新日時
  def updated_date()
    @updated_date
  end
  def updated_date=(value)
    @updated_date = value
  end

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

  #クラス情報
  def class_inf()
    @class_inf
  end
  def class_inf=(value)
    @class_inf = value
  end

  #クラスオブジェクト情報
  def class_obj()
    @class_obj
  end
  def class_obj=(value)
    @class_obj = value
  end
#############################

  #データの情報
  def data_inf()
    @data_inf
  end
  def data_inf=(value)
    @data_inf = value
  end

  #詳細データの情報
  def value()
    @value
  end
  def value=(value)
    @value = value
  end

  #調査時期の情報
  def time()
    @time
  end
  def time=(value)
    @time = value
  end

end
```

## 【ソースコード】test/test.rb
最後にターミナル上でプログラムを動かすファイルです。
欲しい情報を抜き出すために、JSONデータの階層を把握するのにかなり手間取りました。

前述の[JSON Formatter](https://chrome.google.com/webstore/detail/json-formatter/bcjindcccaagfpapjjmafapmmgkkhgoa?hl=ja)が無ければ出来ていなかったと思います。

```ruby:test/test.rb
require "./src/Price"
require "./src/PriceInfo"

#出力のためにデータを作成する
priceobj = Price.new
info = priceobj.doProcess()

#出力部
puts "【帳票分類】#{info.statistics_name}"
puts "【帳票名】#{info.title}"
puts "【調査周期】：#{info.cycle}"
puts "【最終更新日】：#{info.updated_date}"
puts "【調査品目】：#{info.class_obj[1]['CLASS']['@name']}"
puts "【対象地域】：#{info.class_obj[2]['CLASS']['@name']}"
puts "【データ】↓↓"

info.value.each do |n| 
  puts "    #{n['@time'][0..3]}年#{n['@time'][6..7]}月：#{n['$']}円"
end

puts "以上です。"
```

## 【結果】ターミナル上の出力
無事、欲しい情報を抜き出すことが出来ました！
カレーパンの価格安定しすぎ。笑

```zsh
$ ruby test/test.rb
【帳票分類】小売物価統計調査（動向編）
【帳票名】主要品目の都市別小売価格－県庁所在市及び人口15万以上の市(2015年1月～)
【調査周期】：月次
【最終更新日】：2019-10-18
【調査品目】：1023 カレーパン
【対象地域】：特別区部
【データ】↓↓
    2019年09月：101円
    2019年08月：101円
    2019年07月：102円
    2019年06月：103円
    2019年05月：103円
    2019年04月：105円
    2019年03月：104円
    2019年02月：106円
    2019年01月：106円
    2018年12月：106円
    2018年11月：106円
    2018年10月：106円
    2018年09月：105円
    2018年08月：105円
    2018年07月：106円
    2018年06月：103円
    2018年05月：105円
    2018年04月：105円
    2018年03月：105円
    2018年02月：104円
    2018年01月：106円
    2017年12月：107円
    2017年11月：106円
    2017年10月：105円
    2017年09月：105円
    2017年08月：106円
    2017年07月：106円
    2017年06月：105円
    2017年05月：102円
    2017年04月：104円
    2017年03月：102円
    2017年02月：103円
    2017年01月：103円
    2016年12月：103円
    2016年11月：103円
    2016年10月：103円
    2016年09月：103円
    2016年08月：101円
    2016年07月：102円
    2016年06月：101円
    2016年05月：103円
    2016年04月：101円
    2016年03月：103円
    2016年02月：106円
    2016年01月：104円
    2015年12月：105円
    2015年11月：105円
    2015年10月：105円
    2015年09月：103円
    2015年08月：104円
    2015年07月：102円
    2015年06月：102円
    2015年05月：103円
    2015年04月：102円
    2015年03月：100円
    2015年02月：98円
    2015年01月：100円
以上です。
```

## 2019/11/03 追記 グラフ風にしてみた
出力データの視認性を上げるようコードを改良してみました。
ついでに変動幅なども出ます。

### ソースコード

```ruby:test/test.rb
require "./src/Price"
require "./src/PriceInfo"
 
#出力のためにデータを作成する
priceobj = Price.new
info = priceobj.doProcess()
 
#出力部
puts "【帳票分類】   #{info.statistics_name}"
puts "【帳票名】     #{info.title}"
puts "【調査周期】   #{info.cycle}"
puts "【最終更新日】 #{info.updated_date}"
puts "【調査品目】   #{info.class_obj[1]['CLASS']['@name']}"
puts "【対象地域】   #{info.class_obj[2]['CLASS']['@name']}"
puts ""
 
price_array = info.value.map{|h| h['$'].to_i}
 
puts info.value.class
 
price_max = price_array.max
price_min = price_array.min
price_average = (price_max + price_min)/2
price_median = price_array.sort[price_array.size / 2]
price_width = price_max.to_r - price_min.to_r
price_split = (price_width / 19) 
 
puts "【最高価格】   #{price_max}円"
puts "【最低価格】   #{price_min}円"
puts "【平均価格】   #{price_average}円"
puts "【中央値】     #{price_median}円"
puts "【変動幅】     #{price_width.to_i}円"
puts "【時系列データ】"
info.value.each do |n| 
  year = n['@time'][0..3]
  month = n['@time'][6..7]
  price = n['$'].to_i
  graph_value = ((price - price_min) / price_split).to_i
 
  puts "#{year}年#{month}月 #{":" * (graph_value + 1)}#{" " * (20 - graph_value)}#{price}円"
end
 
puts "以上です。"
```
### 出力結果
今回は大阪市のPC価格の変動を確認。

```zsh
❯ ruby test/test.rb
【帳票分類】   小売物価統計調査（動向編）
【帳票名】     主要品目の都市別小売価格－県庁所在市及び人口15万以上の市(2015年1月～)
【調査周期】   月次
【最終更新日】 2019-10-18
【調査品目】   9077 パーソナルコンピュータ
【対象地域】   大阪市

Array
【最高価格】   200765円
【最低価格】   119583円
【平均価格】   160174円
【中央値】     148377円
【変動幅】     81182円
【時系列データ】
2019年09月 :::::::::            154563円
2019年08月 ::::::::             151014円
2019年07月 :::::::              148052円
2019年06月 ::::::::             151909円
2019年05月 ::::::::             151010円
2019年04月 :::::::::            157479円
2019年03月 ::::::::::           160118円
2019年02月 :::::::              148377円
2019年01月 ::::::::             150382円
2018年12月 :::::::::            154991円
2018年11月 :::::::::            157166円
2018年10月 ::::::::::           161019円
2018年09月 :::::::::::          162618円
2018年08月 :                    122910円
2018年07月 :                    119583円
2018年06月 :                    122052円
2018年05月 ::                   125589円
2018年04月 ::::                 132722円
2018年03月 :::                  128612円
2018年02月 ::::                 132769円
2018年01月 :::::                139423円
2017年12月 ::                   127840円
2017年11月 ::                   125689円
2017年10月 :::                  129954円
2017年09月 :::::                136801円
2017年08月 :::::                139424円
2017年07月 :::                  130937円
2017年06月 :::                  132135円
2017年05月 :::::                136986円
2017年04月 ::::::               143798円
2017年03月 ::::::::::           159537円
2017年02月 :::::::::::          166294円
2017年01月 :::::                139415円
2016年12月 :::::::              147539円
2016年11月 :::                  130639円
2016年10月 :                    123675円
2016年09月 ::                   125277円
2016年08月 :::                  131502円
2016年07月 :::::                139811円
2016年06月 ::::::::             151043円
2016年05月 :::::                139552円
2016年04月 ::::::               142751円
2016年03月 :::::::              148575円
2016年02月 ::::::::::           159789円
2016年01月 ::::::               144920円
2015年12月 :::::::              148945円
2015年11月 ::::::::::           158397円
2015年10月 :::::::::::          165929円
2015年09月 ::::::::::::         169976円
2015年08月 :::::::::::::        172414円
2015年07月 ::::::::::::::       178817円
2015年06月 :::::::::::::::::::: 200765円
2015年05月 :::::::::::          165267円
2015年04月 :::::::::::::        171534円
2015年03月 :::::::::::::::      181252円
2015年02月 :::::::::::::::::::  196497円
2015年01月 ::::::::::::         167964円
以上です。

```


## おわりに
初めてまともに外部からデータを引っ張ってみたので、かなり勉強になりました！

まだまだ冗長なコードだなと反省点は多いです。
もしこうした方がいい、などご指摘頂けるのであればコメント欄に記載して頂ければ幸いです。

これを応用すればWeb上でグラフを自動描画したり、幅が広がりそうでワクワクしますね！

この記事がどなたかのお役に立てればと思います。

## 参考にさせて頂いたサイト（いつもありがとうございます）
[個人でも使える！おすすめAPI一覧 - Qiita](https://qiita.com/mikan3rd/items/ba4737023f08bb2ca161)
[政府統計の総合窓口(e-Stat)−API機能へようこそ | 政府統計の総合窓口(e-Stat)−API機能](https://www.e-stat.go.jp/api/)
[政府統計の総合窓口(e-Stat)のAPIを使ってみよう - Qiita](https://qiita.com/mima_ita/items/44f358dc1bc4000d365d)
[小売物価統計でガソリン価格を調べる - Qiita](https://qiita.com/hayasix/items/0b65a481115662b9ee3e)
[Rubyを使って天気予報を取得してみた - Qiita](https://qiita.com/Co_Pilot/items/02255167813013dd4075)
[ディレクトリ構成図を書くときに便利な記号 - Qiita](https://qiita.com/paty-fakename/items/c82ed27b4070feeceff6)