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

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

NEW : SVG初心者です。

# データについて

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

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

そのために何をすればいいのか、それはグラフの作成です。elmにはSVGを操作するライブラリがたくさんあるのでその中から[TypedSvg](https://github.com/elm-community/typed-svg/tree/6.0.0)というライブラリを使ってグラフを作成していきたいと思います。

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

```elm

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で実装していくと。

```elm

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

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

## 先程のスタージェスの公式を使って階級幅を決める。
まずは境界の値を求めてみたいと思います。

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

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

```elm
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`を実装しようと思います。

```elm

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を返す関数を実装してみたいと思います。

```elm

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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/719395/87140a93-be5a-42a8-4420-df362b6add87.png)

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

***追記:色々パワーアップさせてこんなふうになりました。***
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/719395/6f62d8c8-aab3-ed52-e754-cd01307613a5.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/719395/6aca5278-8de5-eda9-79c1-dfea34fa2f14.png)

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

[[Elm] SVGで円グラフを表示する方法](https://superhahnah.com/elm-svg-pie-chart/)

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

```elm
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 を受け取ったら

```elm
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
```

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

```elm
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テキストの方ですが以下のやり方で実装しました。

```elm
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
```