2019 年度 OSS リテラシ 3 : 散布図, 相関係数, 回帰直線

はじめに. Daru と Statsample

IoT デバイスで得られたデータを分析する上で統計操作は必須である. 前回は SQL の関数も用いて代表値を求めたが, 今回は Ruby から統計操作および作図方法を演習する. 近年, データサイエンスでは Python が使われることが多いが, Ruby でも基本的な統計量を簡単に計算することができる.

本演習では以下のライブラリを用いて, 散布図, 相関係数, 回帰直線などを計算・描画することにする.

  • Daru (Data Analysis in RUby)
    • Ruby でのデータの操作や可視化を実現するライブラリ.
    • Python の Pandas に相当.
  • Statsample
    • 統計的な分析・検定手法を提供するライブラリ
    • Python の StatsModels に相当
  • Numo::gnuplot
    • UNIX の伝統的描画ツール gnuplot のラッパー. Ruby から簡単に gnuplot で図が書けるようになる.
  • Numo::Narray

Statsample, Daru で何が出来るかについては上記でリンクしている公式ドキュメントを見て欲しい. もちろん, Google 検索で日本語のドキュメントが多くヒットする. 例えば<URL:http://medfreak.info/?page_id=4267>, <URL:https://qiita.com/yohm/items/b63e20634f86b4f66f9d>, <URL:https://qiita.com/kojix2/items/81303d371760539e29c4>, なども参考になるだろう.

ライブラリのインストール

Debian パッケージで入れる必要のあるものをインストールしておく. 既にインストール済みかもしれないが念の為.

$ sudo -s

# sudo apt-get update 

  ... (略) ...

# sudo apt-get install gnuplot-x11 fonts-noto-cjk ruby-dev make 

  ... (略) ...

gem を使って Daru, Statsample, gnuplot をインストールする. gem は Ruby のライブラリの管理システムである.

# gem install daru statsample numo-gnuplot

  ... (略) ...

NArray のインストール.

# gem install specific_install
# gem specific_install https://github.com/ruby-numo/numo-narray.git

# exit
$

散布図, 相関係数, 回帰直線などの計算・描画

データベースと連携する場合は ActiveRecord を使うと良い. ActiveRecord については, J4 前期 データベース概論の 資料 (1), 資料 (2) を参照して復習して欲しい.

まずは ActiveRecord を用いてデータ取得・表示をするスクリプトを作成する. なお, 後ほど使うので, 'numo/gnuplot' や 'statsample' を require している ('statsample' を require すれば Daru も自動的に require される). また,

以下の例ではテーブル monitoring_10min からカラム 'temp','humi','dp','pres','bmptemp' の値を取り出しているが, 指定するカラムは自分の使っているセンサー群に合わせて適宜変更すること. また, 指定時刻はデータの保存されている時間帯に設定すること. そのようにしないと null が入り動かない.

$ vi sample-daru.rb

require 'yaml'
require 'active_record'
require 'numo/gnuplot'
require 'statsample'

###
### 変数宣言
###

# データベースへの接続情報の置き場. 
# ~/public_html 以下には置かないこと.
conf = "/home/hogehoge/iotex-server/conf/db_info.yml"    # <= 書き換えること!!

# 設定ファイルの読み込み
mydb = YAML.load_file( conf )

# データベースへの接続
ActiveRecord::Base.establish_connection(
  adapter:  mydb["ADPT"],
  host:     mydb["SERV"],
  username: mydb["USER"],
  password: mydb["PASS"],
  database: mydb["DBNM"]
)

# ホスト名
host  = 'sxxxx'

# 利用する時間帯. 開始時刻
time0 = '2019-12-28 00:00:00'

# 利用する時間帯. 終了時刻
time1 = '2020-01-02 00:00:00'

###
### データアクセス & 統計
###

# テーブルにアクセスするためのクラスを宣言. テーブル名指定.
class User < ActiveRecord::Base
  self.table_name = "monitoring_10min"
end

# 取得データの表示. 
User.select('temp','humi','dp','pres','bmptemp').where(hostname: host).where("time > ?", time0).where("time < ?", time1).each do |item|
  p item
end

作成したスクリプトを実行してみると以下のようにデータが表示される. もしこの段階でデータがおかしければスクリプトのバグを疑ってみよ.

$ ruby sample-daru.rb

  ....(略)....
  #<User temp: 12.180000000000001, humi: 58.92099999999999, dp: 4.38, pres: 102339.26699999999, bmptemp: 12.400000000000002>
  #<User temp: 12.178, humi: 58.910000000000004, dp: 4.374, pres: 102327.68400000001, bmptemp: 12.400000000000002>
  #<User temp: 12.162, humi: 58.919999999999995, dp: 4.362, pres: 102326.367, bmptemp: 12.381>
  #<User temp: 12.158, humi: 58.91799999999999, dp: 4.359, pres: 102328.601, bmptemp: 12.342>
  #<User temp: 12.151, humi: 58.92100000000001, dp: 4.351999999999999, pres: 102329.01499999998, bmptemp: 12.319999999999999>
  #<User temp: 12.148181818181818, humi: 58.9290909090909, dp: 4.3500000000000005, pres: 102328.90818181816, bmptemp: 12.308181818181817>
  ....(略)....

ActiveRecord を用いてテーブルからデータを取り出せることを確認できたら 統計量を計算する部分を付け加える.

$ vi sample-daru.rb

  ...(略)...

  # 必要ないので, コメントアウト
  ## 取得データの表示. 
  #User.select('temp','humi','dp','pres','bmptemp').where(hostname: host).where("time > ?", time0).where("time < ?", time1).each do |item|
  #  p item
  #end

  # テーブルから取り出した情報を Daru のデータフレームに. 
  # :temp, :humi, :dp, :pres, :bmptemp は列を示すキーワード (col1, .... や tmp1, .... のような機械的な名前でも構わない). 
  df = Daru::DataFrame.from_activerecord(
    User.select('temp','humi','dp','pres','bmptemp').where(hostname: host).where("time > ?", time0).where("time < ?", time1),
    :temp, :humi, :dp, :pres, :bmptemp
  )

  # p df    ##いったん出力して null が入っていないか確認すること!

  # 基礎統計量
  p df.describe

  # 相関行列. 3 桁に丸めて表示. 
  p df.corr.round(3)

  # 共分散
  p df.cov

  # 単回帰分析. 温度と湿度を対象として. 
  lr = Statsample::Regression.simple(df.temp, df.humi)
  puts lr.summary

スクリプトの修正を終えたら実行してみる. 統計量が以下のように表示されるはずである. 非常に手軽に統計量が出せるのが感動的である.

$ ruby sample-daru.rb

  (df.describe の出力. カウント数, 平均, 標準偏差, 最小, 最大)
  #<Daru::DataFrame(5x5)>
                 bmptemp         dp       humi       pres       temp
        count        651        651        651        651        651
         mean 14.6162016 6.27412591 57.8497514 102090.400 14.5081195
          std 1.45235124 1.00256783 2.91686258 384.597412 1.51298806
          min 12.3081818 4.35000000     44.163 101331.483 12.1481818
          max 21.0299999      9.451     62.711 102563.301 21.2922222

  (df.corr の出力. 値が ±1 に近いほど相関が強い)
  #<Daru::DataFrame(5x5)>
              temp    humi      dp    pres bmptemp
      temp     1.0  -0.705   0.851  -0.085   0.999
      humi  -0.705     1.0  -0.229  -0.424  -0.701
        dp   0.851  -0.229     1.0  -0.434   0.853
      pres  -0.085  -0.424  -0.434     1.0  -0.085
   bmptemp   0.999  -0.701   0.853  -0.085     1.0

  (df.cov の出力)
  #<Daru::DataFrame(5x5)>
                    temp       humi         dp       pres    bmptemp
         temp 2.28913289 -3.1095742 1.29125434 -49.496003 2.19581126
         humi -3.1095742 8.50808733 -0.6686673 -475.11355 -2.9714567
           dp 1.29125434 -0.6686673 1.00514226 -167.40086 1.24210849
         pres -49.496003 -475.11355 -167.40086 147915.169 -47.595542
      bmptemp 2.19581126 -2.9714567 1.24210849 -47.595542 2.10932413

  (単回帰分析の結果)
  = Regression of temp over humi
    Table 1
  +----------+--------+
  | Variable | Value  |
  +----------+--------+
  | r        | -0.705 |
  | r^2      | 0.496  |
  | a        | 77.558 |
  | b        | -1.358 |
  | s.e      | 2.071  |
  +----------+--------+

ここまで出来ると, 次は散布図や回帰直線を描きたくなる. 描画方法はいくつもあるが, ここでは UNIX の伝統的な描画ツールである gnuplot を用いることにする (なお, 今まで使ってきた grafana では散布図を書けない). 以下の例では画像 (png 形式) のファイルにグラフを書き出している. また, 描画に使う温度と湿度のデータは .to_a メソッドで配列に変換している.

$ vi sample-daru.rb

  (末尾に以下を追加)

  output = "output-test.png"
  Numo.gnuplot do
    debug_on
    set title:    "温度と湿度"
    set xlabel:   "温度 (C)"
    set ylabel:   "湿度 (%)"
    set terminal: "png"
    set output:   "#{output}"
    plot [df.temp.to_a, df.humi.to_a, w: :points, pt:7, title:"ALL"],
         ["#{lr.b}*x+#{lr.a}", lw: 3, t:"#{lr.b.round(3)}*x+#{lr.a.round(3)}"]
  end

修正したスクリプトを実行すると, 散布図 + 回帰直線が得られる.

$ ruby sample-daru.rb

  ...(メッセージは略)...

なお, 上記のスクリプトの相関行列の値からすると, 温度と圧力 (pres) は相関が弱そうであり, 温度と露点温度 (dp) は相関が強そうである. 試しに計算した結果が以下の図である.

課題

以下の 2 つについて, wbt に提出せよ.

  • 自分の IoT センサーの出力を用いて前述のような散布図・回帰直線を作成してせよ.
    • 利用しているセンサーの全ての組み合わせについて図を作成すること. 例えば, 温度・湿度・照度の 3 つのデータがあるなら, 3C2 = 3 毎の図が出来るはずである.
    • 図中に相関係数の値も書き入れること.
  • これまでに得られた時系列データ・散布図・相関係数・回帰直線などから何が言えそうか考えてみよ. 友人のデータとの比較検討を行えるとなお良い. さらに, その知見を使った IoT システムを考えてみよ.
    • IoT と称されるシステムは, センサーから送られてきたデータを解析し, 何らかのアクションを起こすものである. 統計解析を用いて得られたデータの特徴を把握することは IoT システム構築の第一歩である.