2021 年度 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 ruby-activerecord 

  ... (略) ...

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) を参照して復習して欲しい.

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

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

$ 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  = 'seimaSalt'  #自分のホスト名に変更すること.

  # 利用する時間帯. 開始時刻
  time0 = '2021-10-28 12:00:00'    #適切な日時に設定すること

  # 利用する時間帯. 終了時刻
  time1 = '2021-10-28 15:00:00'    #適切な日時に設定すること

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

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

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

作成したスクリプトを実行してみると以下のようにデータが表示される. もしこの段階でデータがおかしければスクリプトのバグを疑ってみよ. また, 出力に NULL (nil) が含まれていると後述の統計量の計算でエラーが出るので, そうなっていないことを確認すること.

$ ruby sample-daru.rb

  ....(略)....
  #<User time: "2021-10-28 12:00:00", temp: 19.81, humi: 65.0, lux: 60.0>
  #<User time: "2021-10-28 12:01:00", temp: 19.83, humi: 64.96, lux: 61.0>
  #<User time: "2021-10-28 12:02:00", temp: 19.83, humi: 64.86, lux: 61.0>
  #<User time: "2021-10-28 12:03:00", temp: 19.83, humi: 64.84, lux: 62.0>
  #<User time: "2021-10-28 12:04:00", temp: 19.84, humi: 64.79, lux: 63.0>
  #<User time: "2021-10-28 12:05:00", temp: 19.83, humi: 64.88, lux: 62.0>
  #<User time: "2021-10-28 12:06:00", temp: 19.83, humi: 64.9, lux: 61.0>
  #<User time: "2021-10-28 12:07:00", temp: 19.79, humi: 64.8, lux: 62.0>
  #<User time: "2021-10-28 12:08:00", temp: 19.77, humi: 64.84, lux: 64.0>
  ....(略)....

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

$ vi sample-daru.rb

  ...(略)...

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

  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

  ...(略)...

  #<Daru::DataFrame(5x3)>
                    humi        lux       temp
        count        177        177        177
         mean 66.3759887 43.1807909 20.0975706
          std 1.18194142 15.3160296 0.11760042
          min      64.61       19.0      19.77
          max       68.6       69.0      20.33

  (df.corr の出力. 値が ±1 に近いほど相関が強い)
  #<Daru::DataFrame(3x3)>
            temp   humi    lux
     temp    1.0  0.436 -0.481
     humi  0.436    1.0 -0.949
      lux -0.481 -0.949    1.0

  (df.cov の出力)
  #<Daru::DataFrame(3x3)>
                    temp       humi        lux
         temp 0.01382986 0.06060554 -0.8661492
         humi 0.06060554 1.39698552 -17.182963
         lux -0.8661492 -17.182963 234.580765

  = Regression of temp over humi
    Table 1
  +----------+---------+
  | Variable |  Value  |
  +----------+---------+
  | r        | 0.436   |
  | r^2      | 0.190   |
  | a        | -21.696 |
  | b        | 4.382   |
  | s.e      | 1.067   |
  +----------+---------+

ここまで出来ると, 次は散布図や回帰直線を描きたくなる. 描画方法はいくつもあるが, ここでは 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: "pngcairo"
    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

修正したスクリプトを実行すると, 散布図 + 回帰直線の図が作成できる. 作成した図を ~hogehoge/public_html/ 以下にコピーすればブラウザでアクセスできるようになる (hogehoge はユーザ名で置き換えること).

$ ruby sample-daru.rb

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

$ cp output-test.png ~hogehoge/public_html/

ブラウザで開く. https://iot-XX.epi.it.matsue-ct.jp/~hogehoge/output-test.png にアクセス (XX と hogehoge は適宜読み替えること).

参考までに, とあるデータ (temp: 温度, humi: 湿度, dp: 露点温度, pres: 気圧, bmptemp: 壁の温度) に対して上記のスクリプトを実行したところ, 以下のような相関行列が得られた. 相関行列からすると, 温度と湿度, 温度と露点温度は相関が強そうである. 一方で温度と圧力は相関が弱そうである. これらを図にしてみると以下のようになる.

(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

課題

[1] (提出不要) 「標準偏差」,「共分散」,「相関」の定義式を示し,何を意味するか・どのように使われるものであるかを説明せよ.

[2] (Extra) 自分の IoT センサーの出力を用いて前述のような散布図・回帰直線を作成して提出せよ. さらに,得られた相関係数の値, および散布図・回帰直線から何が言えるかを考察せよ.

  • 10 分平均値を用いること. 利用するデータの時間範囲は 24 時間以上とすること.
  • 利用しているセンサーの全ての組み合わせについて図を作成すること. 例えば, 温度・湿度・照度の 3 つのデータがあるなら, 3C2 = 3 枚の図が出来るはずである.
  • 図中に相関係数の値も書き入れること.

[3] (Extra) 以下のいずれかに取り組み,作図結果を提出すること.なお,(3) の評点は (1), (2) に比べると低くなることに注意されたい.

  • (1) 前回作成した 10 分平均値や 1 時間平均値などを用いて散布図や回帰直線を作成し,[2] との違いを論じよ.
  • (2) Daru, Statsample のドキュメントを読み,何らかの統計量を算出してプロットしてみよ.
  • (3) gnuplot のマニュアルを読み,作図を工夫してみよ.