読者です 読者をやめる 読者になる 読者になる

Cyclemeterのデータを読む(2)

 前回『Cyclemeterのデータを読む』ということで、メール共有のされたサマリを読んでRで散布図など書いてみました。今回はもう少しつっこんでKMLからデータをスクレイピングしてより詳細なデータを読んでみたいと思います。

 わたしは、いつも荒川CRの決まった区間をトレーニングしています。そしてそのデータは「ラップ」によって細かく記録されています。CyclemeterのWeb上の記録で見るとインターバルというやつです。

f:id:seuzo:20150907180806p:plain

 ここではラップ1が自宅から河川敷の取りつきまで、ラップ2が1往復目、ラップ3が2往復目でそれぞれほぼ10km、ラップ4が帰路になっています。このうち、距離をたよりにラップ2と3のデータだけを抽出して比べられれば、より正確なレコードとして考えてもいいでしょう。

 Cyclemeterのインポート用KMLを読むと、ラップデータを含んだデータはJSONとして内包されていました。JSON Pretty Printで確認すると、この選択部分がラップ1ということになりそうです。

f:id:seuzo:20150907181508p:plain

 大まかな手順としては、(1)前回同様mboxを読んで(2)KMLにアクセス(3)NokogiriでXMLとしてパース(4)JSON部分をパース(5)"distance"(距離)が9900以上10000未満のラップの"averageSpeed"(平均速度)をタブ区切りで出力(6)Rで解析、です。

 ここでまず注意しておきたいのは、KML上のデータ単位は距離がm(メートル)で時間がs(秒)で表されていることです。つまり速さはすべてm/sが単位になっています。km/hに換算するには3600/1000で3.6を乗じています。

 mboxを読んでタブ区切りで出力させるためのスクリプトはこんな感じで書いてみました。

#!/usr/bin/env ruby
# coding: utf-8

=begin
cyclemeterのメールログからKMLを読んでのtsvへ書き出す
データはGmailのmbox形式を想定
Gmailの特定のラベルをダウンロードする方法(参照:http://p--q.blogspot.jp/2014/05/gmail1pc.html)
kmlを読む必要がない場合は前回のスクリプトで十分

●以下変更して楽しむ項目
→日時によるフィルタ設定
→完了日時のラベル名を適宜変更
→ヘッダや出力項目を好みに合わせて変更

2015-09-08 ひとまず

=end

require "time"
require 'open-uri'
require 'nokogiri'
require 'json'


#日時によるフィルタ設定
start_time = Time.parse("2014-09-12 00:00:00 +0900")#開始日時
end_time = Time.now#終了日時

#完了日時のラベル名
my_label = "自転車完了"

#ヘッダの出力
print "Date\tDistance\tAverage\n"


#cyclemeterのインポート用KMLを読む(汎用性なし)
def read_kml(my_date, my_url)
  my_doc = Nokogiri::XML(open(my_url))
  my_json = my_doc.xpath('//abvio:json')[0].text #要素<abvio:json>を読む(ひとつしかない)
  
  JSON.parse(my_json)["intervalArray"].each {|item| # JSONでパース。それからインターバルの配列をひとつづつ読む
    tmp_d = item["distance"].to_i
    if (tmp_d > 9900) && (tmp_d < 10000) then # ラップの距離が9.9km〜10km
      tmp_s = (item["averageSpeed"].to_f * 3.6).round(2) # averageSpeedの単位はm/sなので、km/hする。小数点第二位で丸め。
      print "#{my_date}\t#{tmp_d.to_s}\t#{tmp_s.to_s}\n"#出力はメソッドで(かっこわるい)
    end
  }
end


while line = ARGF.gets
  if line =~ /^From \d.+@.+\d$/ then
    my_data = Hash.new #メールの先頭でデータ初期化
  
  #ハッシュの生成
  elsif line =~ /^([^\n:]+): ?(.+)/ then
    my_data[$1] = $2.gsub(/\t+/,'')
  end
  
  #メールの最後にあるcyclemeterのURLに出会ったら出力
  if line =~ /^http:\/\/www\.cyclemeter\.com$/ then
        if my_data.key?(my_label) then
          tmp_time = Time.parse(my_data[my_label])
          if (start_time .. end_time).cover?(tmp_time) then
            read_kml(my_data[my_label], my_data["インポートリンク"])#出力はメソッドで
           end
       end
  end
  
end

 クラウド上にある146ファイルのKMLを読んで、7分30秒かかりました。KMLって普段走りの35kmくらいで450〜500KB、先日172km走ったレコードが2.4MBくらいありますから、146ファイルも読むとちょっと膨大ですね。それにまあ、読み方がダサすぎます。rubyとか学んでいる人は参考にしない方がいいです。取りたいデータがはっきりしてるなら、XMLをDOMで展開するよりNokogiri::XML::SAXとか使った方がいいんでしょうね、わかんないけど^^

Rでデータ解析

 この部分は前回と同じです。まずRでタブ区切りファイルを読み込んで、平均速度のsummaryを見て、箱ひげ図を書きます。

> data = read.delim("kml.tsv", sep="\t", header=TRUE)
> summary(data[,"Average"])
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  22.66   27.38   28.56   28.33   29.59   32.37 
> boxplot(data[,"Average"])

f:id:seuzo:20150908144549p:plain

 今回もデータはほぼ時系列に並んでいるので、そのまま平均速度を散布図に落とし込みます。

> plot(data[,"Date"], data[,"Average"])

f:id:seuzo:20150908145010p:plain

 ほとんどの場合、1回で2往復しているのでそれぞれの日付が重なっているものは小さく箱ひげになっています。そして、あきらかに右上がり、あきらかにな! お手盛りなグラフを見てオラにやにや止まんねえよ。