3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

elmを使って統計ライブラリを作って実際にそれを利用するまで。-第三話 ヒストグラム-

Last updated at Posted at 2021-02-11

私はelmと統計学の初心者でかつ初学者です。何か間違っているところがあったらコメントお願いします。
ソースコード:https://github.com/saburooo/simple-stat
成果物:https://mysimple-stat.web.app

NEW : SVG初心者です。

データについて

統計に使うデータは数字の羅列でかつすぐに計算に使う、私はそう思っていましたが違うみたいです。

データを実際に収集してまず行うことは収集されたデータ達を読むことです。

そのために何をすればいいのか、それはグラフの作成です。elmにはSVGを操作するライブラリがたくさんあるのでその中からTypedSvgというライブラリを使ってグラフを作成していきたいと思います。

ヒストグラムの原型を作る。


histgramBar: Float -> Float -> Svg.Svg msg
histgramBar x h =
    Svg.rect [ InEm.width 1, height (Types.percent 100), InEm.x x, InEm.height h 
        , TypedSvg.Attributes.style "transform: scale(1, -1)"
        , fill (Types.Paint Color.blue)
        , strokeWidth (Types.px 2)
        , stroke ( Types.Paint Color.darkBlue )
        ] []

これでヒストグラムを表現できると考えたわけですがこのまま適用した場合、画面内に四角形が収まらなくなる危険性があります。
そこで以下の概念を使ってヒストグラムを作成していこうと思います。

度数分布表

計算を行う前にとりあえず作っておくものでデータは大体どんな感じなのかを見極めるために作成されます。

度数とは簡単に言うと「何何以上ホゲホゲ未満」の範囲に入った数値の数のことです。

階級

階級とは手に入れたデータをグループ分けするために作るものです。

階級値

階級値とはちょうどその階級を代表するものとされており、大体においてその階級の範囲の値の真ん中くらいの値になります。
テーブルにするとこんな感じです。

階級 階級値 度数
hoge以上fuga未満 hogeとfugaの狭間 hogefuga
fuga以上piyo未満 fugaとpiyoの間 fugapiyo

階級幅

階級幅とはグラフを作る際に幅をどうやって決めるか計算する方法で、目安の決め方には色々なやり方がありますが、今回は受け取ったデータの中で最も大きいものと小さい物を引いてデータの数の分だけ割っていく方式で求めていきます。

階級の数を決めるために

グラフを作っていく上で必要になっていくのは階級の数、つまり区切りをどうするかなのですがそれを求める式としてスタージェスの公式があります。(あくまで目安です。)以上を踏まえてelmで実装していくと。


starJes : List Float -> Int
starJes argList =
    round <| 1 + logBase 2 (toFloat (List.length argList))

これを使っていい感じに階級が分かれたグラフを作っていこうと思います。

先程のスタージェスの公式を使って階級幅を決める。

まずは境界の値を求めてみたいと思います。

bundary : Float
bundary = ( Maybe.withDefault 0 ( Maybe.map2 (-) ( List.maximum floatList ) ( List.minimum floatList ) ) ) / toFloat star                                                             

この辺は私のオリジナル実装ですがこのようにしました。

classInterval : List (Float, Float)
classInterval = ( List.map2 (Tuple.pair) ( List.map (\s -> toFloat s * bundary) ( List.range 0 star ) ) ( List.map (\s -> toFloat s * bundary) ( List.range 1 ( star + 1 ) ) ) )

これらを変数として組み込んだ関数としてappendClassを実装しようと思います。


appendClass: List Float -> Dict ( Float, Float ) Float 
appendClass floatList =
    let
        star = Utility.starJes floatList
        bundary = ( Maybe.withDefault 0 ( List.maximum floatList ) - ( Maybe.withDefault 0 ( List.minimum floatList ) ) ) / toFloat star
        classInterval = ( List.map2 (Tuple.pair) ( List.map (\s -> toFloat s * bundary |> roundNum 2) ( List.range 0 ( star + 1 ) ) ) ( List.map (\s -> toFloat s * bundary |> roundNum 2) ( List.range 1 ( star + 2 ) ) ) )
        frequencyList = ( List.map (\cls -> frequency cls floatList) classInterval )
    in
        Dict.fromList frequencyList

これをもとにList Floatを受け取ってヒストグラムなSVGを返す関数を実装してみたいと思います。


listHistgram:List Float -> Svg.Svg msg
listHistgram floatList =
    let
        indexed = appendClass floatList
        dictRange = Dict.size indexed |> List.range 0 |> List.map (\x -> toFloat x) 
        inserted = List.map2 (Tuple.pair) dictRange (Dict.values indexed )
    in
        Svg.svg [ viewBox 0 0 800 250 ]
          [ backColor Color.lightBlue
          , TypedSvg.g [ transform [ Types.Translate 0 198 ] ] (List.map (\h -> histgramBar ( Tuple.first h ) ( Tuple.second h )) inserted)
          , TypedSvg.text_ [ transform [ Types.Translate 0 230 ], fontSize 0.7, fill (Types.Paint Color.white) ] [ text ( listTupleStr (Dict.keys indexed) ) ]
          ]

以上の実装をした結果
image.png

結構いい感じになりましたがまだまだ見た目が気になるのと想定した形にならなくなる不具合があるので試行錯誤を重ねていく所存です。

追記:色々パワーアップさせてこんなふうになりました。
image.png

更に追記:円グラフが実装できました。

image.png

参考になったのはこちらのサイトです。

[Elm] SVGで円グラフを表示する方法

色々手を加えたあとに書き足したので元サイトとは違ったものになっていますがソースコードはこんな感じになります。

listCircle: List Float -> Svg.Svg msg
listCircle floatList =
    let
        indexed = appendClass floatList
        dictRange = Dict.size indexed |> List.range 0 |> List.map (\x -> toFloat x) 
        inserted = List.map2 (Tuple.pair) dictRange (Dict.values indexed )
        total = List.sum ( List.map (\c -> Tuple.second c) inserted )
    in
        Svg.svg [ viewBox 0 0 63.6619772368 63.6619772368, width (Types.px 300) ]
        [ backColor Color.lightBlue
        , TypedSvg.g [ transform [ Types.Translate -0.1 63.5 ] ]
            ( List.map (\c -> circleMap c total (offsetPickUp c inserted)) inserted )
        ]

この関数で List Float を受け取ったら

offsetPickUp: (Float, Float) -> List (Float, Float) -> Float
offsetPickUp tuple tupleList =
    let
        total = List.sum (List.map (\c -> Tuple.second c) tupleList)
        pickUp = List.filter (\c -> Tuple.first c <= Tuple.first tuple) tupleList
    in
        List.foldl (+) 0 (List.map (\c -> Tuple.second c) pickUp) * 100 / total |> roundNum 2

こちらで回転の度合いを求めつつ

circleMap: (Float, Float) -> Float -> Float -> Svg.Svg msg
circleMap tuple total offset =
    let
        first = Tuple.first tuple
        counts = Tuple.second tuple
        parcentage = 100.0 * counts / total
    in
        TypedSvg.circle
            [ cx (Types.px 31.8309886184 )
            , cy (Types.px -31.8309886184 )
            , r (Types.px 15.9154943092 )
            , fill Types.PaintNone
            , stroke ( Types.Paint ( Color.rgb ( first / 10 + 0.2 ) ( first / 10 + 0.2 ) ( first / 10 + 0.5 ) ) ) 
            , strokeWidth (Types.px 31.8309886184 )
            , strokeDashoffset ( 25 - offset + parcentage |> String.fromFloat )
            , strokeDasharray (( parcentage |> String.fromFloat ) ++ "," ++ ( 100.0 - parcentage |> String.fromFloat ))
            ]
            [ ]

こうやって円グラフを実装しました。見ての通りviewBoxに対して円が大きすぎるのでできれば小さくしていきたいです。

おまけ

このSVGテキストの方ですが以下のやり方で実装しました。

listTupleStr: List (Float, Float) -> String
listTupleStr listTuple =
    let
        first = List.unzip listTuple |> Tuple.first |> List.map (\f -> String.fromFloat f ++ "から")
        second = List.unzip listTuple |> Tuple.second |> List.map (\f -> String.fromFloat f ++ "まで, ")
    in
        List.map2 (++) first second |> String.concat
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?