EurekaMoments

ロボットや自動車の自律移動に関する知識や技術、プログラミング、ソフトウェア開発について勉強したことをメモするブログ

Juliaで学ぶ確率ロボティクス~センサデータの概要把握~

目次

はじめに

上記の書籍を参考に、Juliaコードにより
ロボティクスの技術を学べるプロジェクトを
前回記事からスタートしました。

www.eureka-moments-blog.com

今回からは書籍の第2章にあたる、確率と
統計の基礎について紹介していくとして、
まずはプロジェクト内で扱うセンサデータの
概要把握について解説します。

GitHubリポジトリ

本記事で紹介するJuliaコードは全てこちらの
リポジトリにて公開しています。

github.com

センサデータを分析することの意義

自律移動するロボットには様々なセンサが
搭載されます。そのセンサ群から得られる
データというのは決して綺麗なものではなく、
ノイズや取付による偏差やばらつきを持つ
ことが多いです。
こういったデータを使ってロボットを制御
するに、まずはデータをじっくり分析して、
それらが持つ特徴を把握しておくことが
必要になります。

センサのサンプルデータ

本プロジェクトでは、赤外線LEDを照射して
物体までの距離を測る光センサと、レーザ
光線をスキャンして外界の形状を計測する
LiDARの2種類を外界認識センサとして用います。
これらを壁に向けて、そこまでの2次元距離を
計測した際のデータをサンプルデータとして
扱います。

データは全てリポジトリのdataフォルダにあり、
スペース区切りのテキスト形式で保存されています。

f:id:sy4310:20210324212652p:plain

これらは左から順にデータを取得した日付、
時刻、光センサによる計測距離、LiDARに
よる計測距離となっています。

データの概要把握

データを細かく分析する前に、大体どのような
特徴を持ったデータなのかをざっくり把握して
みることにします。
詳細な分析というものはどうしても時間が
必要なので、急ぎで何かしらのアウトプットを
出したい場合は、とりあえず手っ取り早く
全体像を掴んでみるというのも大事です。

必要なJuliaパッケージの読み込み

まずは、上記のテキストファイルから
データを読み込んで、各列に名前を
割り当てたデータフレームに変換します。
そのために下記二つのパッケージを
読み込みます。

using DataFrames, CSV

一つ目はデータフレーム作成用、二つ目は
CSV形式データを扱うために使用します。

データファイルのパスの指定

続いてデータのファイルを読み込みます。
そのために下記のようにファイルのパスを
指定します。

data_path = joinpath(split(@__FILE__, "src")[1], "data/sensor_data_200.txt")

単純に相対パスで与えることもできますが、
こういう書き方をすることで絶対パスで
指定することが出来ます。
わざわざ絶対パスで指定する理由として、
サンプルコードを実行するのにsrc以下の
ディレクトリまで移動する必要がなくなると
いうのがあります。
@__FILE__マクロで実行されたファイルの
絶対パスを取得し、split()関数でsrcを境に
分割します。
ここで分割されたパスはsrcより手前部分と
後部分の2つが要素となる配列になるので、
前者を[1]で指定し、dataフォルダ以下の相対
パスとjoinpath関数で連結します。
ちなみにJuliaでは、Pythonと違い配列の要素に
アクセスするためのインデックスが1始まりと
なるので注意しましょう。

ファイルの読み込みとデータフレーム作成

上記のように指定したファイルを下記のコードで
読み込み、データフレームに変換します。

df_200_mm = CSV.read(data_path, DataFrame, 
                     header=["date", "time", "ir", "lidar"],
                     delim=' ')

入力引数のdelimで区切り文字(今回はスペース)を
指定し、それで4列に区切られたCSV形式のデータに
します。そして、headerによって各列にカラム名を
設定し、データフレームとして出力します。

データの概要の出力

最後に下記のコードでデータの概要を出力します。

println("sensor_data_200.txt")
println("Data size = ", size(df_200_mm))
println("Column Names = ", names(df_200_mm))
println("Data type = ", eltype.(eachcol(df_200_mm)))
println("Describe = ", describe(df_200_mm))

出力結果は下記のようになり、上から順に
ファイル名、データサイズ、カラム名、
各列のデータのタイプ、統計量になります。

Data size = (58988, 4)
Column Names = ["date", "time", "ir", "lidar"]
Data type = DataType[Int64, Int64, Int64, Int64]
Describe = 4×7 DataFrame
 Row │ variable  mean            min       median       max       nmissing  eltype   
     │ Symbol    Float64         Int64     Float64      Int64     Int64     DataType 
─────┼───────────────────────────────────────────────────────────────────────────────
   1 │ date           2.01801e7  20180122    2.01801e7  20180124         0  Int64
   2 │ time      117592.0               0    1.13958e5    235959         0  Int64
   3 │ ir           308.682           283  308.0             338         0  Int64
   4 │ lidar        209.737           193  210.0             229         0  Int64

特にdescribe関数を使うと、各データのカラム名、
タイプ、最小値、最大値、中央値、平均値などを
テーブルにまとめて表示してくれるので分かりやすいです。

コードのモジュール化

最後に、ここまでのコードをまとめて実行するために
モジュール化したものを紹介しておきます。
src/prob_stats/sensor_data/print_sensor_data.jl

module PrintSensorData
    using DataFrames, CSV

    function print_data_200()
        data_path = joinpath(split(@__FILE__, "src")[1], "data/sensor_data_200.txt")
        df_200_mm = CSV.read(data_path, DataFrame, 
                             header=["date", "time", "ir", "lidar"],
                             delim=' ')

        println("sensor_data_200.txt")
        println("Data size = ", size(df_200_mm))
        println("Column Names = ", names(df_200_mm))
        println("Data type = ", eltype.(eachcol(df_200_mm)))
        println("Describe = ", describe(df_200_mm))
        println("")

        return df_200_mm
    end

    function print_data_600()
        data_path = joinpath(split(@__FILE__, "src")[1], "data/sensor_data_600.txt")
        df_600_mm = CSV.read(data_path, DataFrame, 
                             header=["date", "time", "ir", "lidar"],
                             delim=' ')

        println("sensor_data_600.txt")
        println("Data size = ", size(df_600_mm))
        println("Column Names = ", names(df_600_mm))
        println("Data type = ", eltype.(eachcol(df_600_mm)))
        println("Describe = ", describe(df_600_mm))
        println("")

        return df_600_mm
    end
end

センサから壁までの距離が200mmのデータと、
600mmのデータをそれぞれデータフレームに
変換し、概要を出力するプログラムです。
Juliaを起動し、下記のコマンドで実行できます。

julia> include("src/prob_stats/sensor_data/print_sensor_data.jl")
julia> PrintSensorData.print_data_200()
julia> PrintSensorData.print_data_600()

まとめ

サンプルのセンサデータをCSV、データフレームに
変換し、その中身の概要をざっと確認する方法に
ついて解説しました。
今後は、データをいろんな観点で可視化し、
詳細な傾向を分析していきます。