虎の穴開発室ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

CSVで取り込んだデータを綺麗なグラフで表示する

昨今データの重要性が増しており、分析にグラフは欠かせないものとなっていますが
エクセルで打ち込んでグラフを作成するのもあまりかっこよくないですし、
実際の運用者がエンジニアではないことが多く、データ活用が進んでいないように感じます。

今回はCSVから入力されたデータからRuby on Railsのライブラリを利用して綺麗なグラフを出力してデータ分析する方法を考えて行きたいと思います。 将来的にどんなデータにも対応しうることを想定し、SQLに頼らないように作ってみたいと思います。

Gemを選択する

Railsではgemを利用することでグラフを簡単に出すことができます。 グラフを出力するGemはいくつかあるので、比較して行きたいと思います。

lazy_high_charts

Gemfileに以下を記載します。

gem 'lazy_high_charts'

bundle installします。

bundle install --path=vendor/bundle

application.js に以下を追加します。

//= require highcharts/highcharts
//= require highcharts/highcharts-more

公式のRead.meには下記の記載もありますが、こちらを入れると Highcharts Error #16が発生します。 特に支障はないため外します。 highchartsはグラフにアニメーション効果を追加してくれます。*1

//= require highcharts/highstock
class HomeController < ApplicationController
  def index
    category = [1,3,5,7]
    current_quantity = [1000,20000,1500,18000]

    @graph = LazyHighCharts::HighChart.new('graph') do |f|
      f.title(text: '売上')
      f.xAxis(categories: category)
      f.series(name: '売上', data: current_quantity)
    end
  end
end

chartkick

application.js に以下を追加します。

//= require chartkick

Controllerに追記します。

class HomeController < ApplicationController
  def index
    category = [1,3,5,7]
    current_quantity = [1000,20000,1500,18000]

    # Lazy_High_chart
    @lazegraph = LazyHighCharts::HighChart.new('graph') do |f|
      f.title(text: '売上')
      f.xAxis(categories: category)
      f.series(name: '売上', data: current_quantity)
    end

    # Chartkick
    @chartkickgraph = {"1": 1000,"3": 20000,"5": 1500,"7": 18000}
  end
end

Viewは以下のように記載します。

<%= line_chart(@chartkickgraph, curve: false, label: "売り上げ", xtitle: "", ytitle: "個数" ) %>

出力した結果です。

f:id:toranoana-lab:20181116194257p:plain
lazy_high_chartsとchartkickの比較

LazyHighChartsはデフォルトでHighChartsが使えますが、 Chartkickは別途インストールする必要があります。

微妙な差はありますが、個人的にChartkickが気に入ったので、 こちらを使って行くことにします。

CSVインポート

続いてCSVインポートを実装していきます。

Viewの実装

<%= line_chart @chartkickgraph , curve: false, label: "売り上げ", xtitle: "", ytitle: "個数" %>

route.rb

post "/", to: 'home#index'

コントローラーの実装

class HomeController < ApplicationController
  require 'csv'
  def index
    file = params[:file]
    datas = []
    unless file.nil?
      ActiveRecord::Base.transaction do
        CSV.foreach(file.path, headers: true) do |row|
          datas.append(Hash[row])
        end
      end
    end
    @chartkickgraph = datas[0]
  end
end

テスト用CSVを用意します。

カラム1, カラム2, カラム3, カラム4
1, 2, 3, 4

出力結果 f:id:toranoana-lab:20181126155210p:plain

簡単にかつ動きのあるグラフが作れましたが、 グラフを見てみると、ヘッダがそのまま下のメモリになっているようです。 これだとあまり実用性がないので、実用性ある形にしたいです。 そこで、日付ごとに金額を合計してグラフを出力することを目指します。

class HomeController < ApplicationController
  require 'csv'
  def index
    file = params[:file]
    datas = {}
    unless file.nil?
      ActiveRecord::Base.transaction do
        CSV.foreach(file.path, headers: true, encoding: "UTF-8", col_sep:",", quote_char: '"') do |row|
          aggregate(datas, row["集計日"] , row["売上"])
        end
      end
    end
    @chartkickgraph = datas
  end

private
  def aggregate(datas, key , value)
    if datas.has_key?(key)
      datas[key] = datas[key].to_i + value.to_i
    else
      datas[key] = value.to_i
    end
    return datas
  end
end

それっぽいテスト用CSVを用意します。

"商品名","カテゴリー","集計日","売上"
"商品A","マンガ","2018/10/1","1000000"
"商品A","マンガ","2018/10/2","1200000"
"商品B","マンガ","2018/10/1","500000"
"商品C","DVD","2018/10/1","600000"

出力して見ます。 f:id:toranoana-lab:20181126155646p:plain

X軸のラベルがおかしいですが、出力できました。

日付が直せない

X軸の日付ラベルがおかしくなってしまったのが直せず、2時間ほど悪戦苦闘しました。 結論を言うとこれで解決です。

<%= line_chart @chartkickgraph, discrete: true, curve: false, label: "売り上げ", xtitle: "", ytitle: "個数" %>

Viewに以下の設定を追加するだけでした。。。

discrete: true

f:id:toranoana-lab:20181126155523p:plain

データ分析っぽいことをする

CSVからいい感じのグラフは作れましたがこれだけではただグラフができただけでビジネスに全く活用できていません。 販売した商品のうちどれが売れているのかを分析できるようにします。

以下のような形式のオブジェクトを渡せば作成できます。

[{name:"商品A", data: {"2018/10/01": "10000000", ...}},
 {name:"商品B", data: {"2018/10/01": "20000000", ...}}
 ...
]

コントローラーを実装します。
商品名をname:、同じ商品名のデータだけを抽出し、日付ごとに集計した結果をdata:とします。

class HomeController < ApplicationController
  require 'csv'
  def index
    file = params[:file]
    data_groups = []
    unless file.nil?
      logger.info("開始")
      ActiveRecord::Base.transaction do
        CSV.foreach(file.path, headers: true, encoding: "UTF-8", col_sep:",", quote_char: '"') do |row|
          datas = {}
          datas[:name] = row["商品名"]
          data = data_groups.find{|h|h[:name] == row["商品名"]}
          if data.nil?
            aggregate(datas[:data]={}, row["集計日"] , row["売上"])
            data_groups << datas
          else
            aggregate(data[:data], row["集計日"] , row["売上"])
          end
        end
      end      
    end
    @chartkickgraph = data_groups
  end

private
  def aggregate(datas, key , value)
    if datas.has_key?(key)
      datas[key] = datas[key].to_i + value.to_i
    else
      datas[key] = value.to_i
    end
    return datas
  end
end

テストデータ

"商品名","カテゴリー","集計日","売上"
"商品A","マンガ","2018/10/01","1000000"
"商品B","マンガ","2018/10/01","500000"
"商品C","DVD","2018/10/01","600000"
"商品A","マンガ","2018/10/02","800000"
"商品B","マンガ","2018/10/02","1500000"
"商品C","DVD","2018/10/02","1600000"
"商品A","マンガ","2018/10/03","6000000"
"商品B","マンガ","2018/10/03","400000"
"商品C","DVD","2018/10/03","300000"
"商品A","マンガ","2018/10/04","1700000"
"商品B","マンガ","2018/10/04","600000"
"商品C","DVD","2018/10/04","700000"
"商品A","マンガ","2018/10/05","2000000"
"商品B","マンガ","2018/10/05","400000"
"商品C","DVD","2018/10/05","900000"

f:id:toranoana-lab:20181126154738p:plain

出力したいグラフが出力できました。 CSVデータをプログラム上で集計したのでかなり大変でした。 今回はDBを使わずにグラフを生成しましたが、 DBに格納してからデータを抽出する方が圧倒的に楽です(泣)

まとめ

今回はRailsでグラフを出すまでが課題なのでここまでですが、 作業をしているうちに課題ができたので、今後のために列挙しておきます。

  • MongoDBやRedisを利用したNoSQLな分析ツールを作る。
  • 分析したい内容を画面から選択できるようにする。

上記は今後実装していきたいと思います。

今回ご紹介したChartkickはRails以外でも利用できるようなので、 ぜひ使って見てください。

*1:highchartsを商用で利用する場合は有償となります。ご注意ください。http://www.altech-ads.com/Others/Highcharts.htm?gclid=CLOoiPif1MMCFRUGvAodPAEA0g#highcharts