関数型でデータサイエンス#2：インプットしたデータを変換する

（この記事は、[「fukuoka.ex(その2) Elixir Advent Calendar 2017」](https://adventar.org/calendars/2871)の22日目、
　　および[「ディープラーニングのエンジニアリング Advent Calendar 2017」](https://qiita.com/advent-calendar/2017/deep-learning-engineering)の6日目です）

昨日は、@takasehideki さんの[「Erlang/OTPのソースビルドでHiPEが入らなかった時の対処法」](https://qiita.com/takasehideki/items/4a619d869643f8d0e272)でした

----
[fukuoka.ex](https://fukuokaex.fun/)代表の[piacere](https://twitter.com/piacere_ex)です
今回もご覧いただいて、ありがとうございます:bow:

この連載の、前回までの記事は、以下になります
　|> [<b>関数型でデータサイエンス#1：様々なデータをインプットする</b>](https://qiita.com/piacere_ex/items/f7077f0ff1b1a6c7a959)

今回は、インプットしたデータを、後続の分析処理で使いやすくするための「データ変換」をElixirを行うコードについて解説します

Elixirが、データサイエンスに向いた言語であることを実感する回となります
<br>
:shamrock::shamrock::shamrock::shamrock:<b>　お礼：各種ランキングに83回のランクインを達成しました　:shamrock::shamrock::shamrock::shamrock:

4/27から、37日間に渡り、毎日お届けしている[「季節外れのfukuoka.ex Elixir Advent Calendar」](https://qiita.com/advent-calendar/2017/elixir-or-phoenix)と[「季節外れのfukuoka.ex(その2) Elixir Advent Calender」](https://adventar.org/calendars/2871)ですが、[Qiitaトップページトレンドランキング](https://qiita.com/trend)に12回入賞、[Elixirウィークリーランキング](https://qiita.com/tags/elixir)では6週連続で１／２／３フィニッシュを飾り、各種ランキング通算で、<font color="red"><b>トータル83回ものランクイン</b></font>を達成しています

みなさまの暖かい応援に励まされ、<font color="red"><b>合計552件ものQiita「いいね」</b></font>もいただき、fukuoka.exアドバイザーズとfukuoka.exキャストの一同、ますます季節外れのfukuoka.ex Advent Calendar、頑張っていきます:rocket:

[![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/bbe9d349-c978-e9e8-ba7d-f8feda74be91.png)](https://adventar.org/calendars/2871)

# データ分析活動の要である「データ前処理」

[前回](https://qiita.com/piacere_ex/items/f7077f0ff1b1a6c7a959)もお伝えした通り、既にあるデータを、どのように搔き集め、データ分析側へと回すか、ということに、データ分析活動の80～90%が掛かっており、ここがうまくいかないと、機械学習やディープラーニングも、宝の持ち腐れとなります

こうした、「データ収集」と「データ前処理」は、需要がとても大きな領域で、中でも「データ前処理」は、エンジニアリングの比重がとても大きい、システム開発・運用・性能担保のノウハウを必要とする部分ですが、何故かクローズアップされる機会が、あまり多くありません

今回と次回で、この「データ前処理」を扱っていきますが、今回は、「インプットしたデータの変換」について取り上げます

なお、集約を伴う変換については、次回で扱いますので、今回は除外します

# 参考：「データ収集」と「データ前処理」以外の工程

データ分析／データサイエンスにおける工程として、「データ収集」の前に、「データ分析の目的設定」「分析計画」「分析設計とデータ設計」といった工程があります

また、「データ前処理」の後続には、「分析モデルの構築」「分析結果の評価」「未知データの分析」「未知データの分析結果の検証」といった工程があります

これら工程のうち、「分析モデルの構築」「分析結果の評価」については、本連載の範囲内として扱っていく予定です

その他の工程については、タイミングがあれば本連載中に、無ければ別の機会で扱いたいと思います

# インプットデータの型の種類

インプットデータの各値には、以下のような、型の種類があります

- 数値型
- 文字列型
- カテゴリ型
- 日付・時刻型
- 位置情報型
- 画像・バイナリデータ型

「カテゴリ型」という名称に馴染みが無い方向けに解説すると、「red」「blue」「green」のような、バリエーションの個数が限られた文字列型を指します（enumのようなものをイメージしていただくと近いでしょう）

なお本連載では、CSVのような文字テキストデータを中心に扱うため、位置情報型と画像・バイナリデータ型は、対象外とし、本コラムでは、それ以外のデータ型についてのデータ変換を扱います

それでは、Phoenixサーバーを起動し、ブラウザで「```http://localhost:4000```」のWebページが見れる状態にしたら、以下を試していきましょう

# サンプルデータ

前処理の処理サンプルに使うデータは、以下の少額ローン借入のサンプルデータが入ったCSVファイルとします

```:loan.csv
ID, Gender, Profession, ApplicantIncome, CoapplicantIncome, LoanAmount, LoanStartDatetime
1, Male, Unemployed, 307, 150, 126, 2001/05/01 13:24
2, Male, Employee, 500, 180, 208, 1998-12-02T17:46:21Z
3, , Management, 2340, 255, 100, 1989/02/03 15:54
4, Male, Employee, 328, 0, 78, 2000/06/01 12:23
5, Male,  SelfEmployed, 572, 0, 110, 2012/04/23 09:22:43
6, Male, Employee, 216, , 152, 2016/07/31 14:25:54
7, Female, Management, 2020, 222, 59, 2017-08-26 10:23:38Z
8, Male, Employee, 388, 0, 147, 2001/02/27 15:54:21
9, Male, SelfEmployed, 309, 0, 90, 2015/11/24 09:59:59
10, Female, SelfEmployed, 466, 0, 124, 2013-03-24 19:10:10Z
11, Male, Unemployed, 100, 566, 131, 2005/07/19 12:54:23
12, Male, SelfEmployed, 15, 291, 200, 2011/09/21 13:30
13, Male, Employee, 922, 791, 300, 2008-07-04 14:12:09Z
14, Male, Employee, 218, 151, 162, 2009/09/09 17:43:29
15, Male, Employee, 1217, 0, 166, 2012/11/09 11:43:15
16, Male, SelfEmployed, 130, 347, 100, 2014-10-27T14:54:22Z
17, Male, Unemployed, 188, 162, 48, 2000/03/23 15:41:54
18, , SelfEmployed, 390, 0, 101, 1999-12-24 13:34:43Z
19, Female, SelfEmployed, 376, 0, 125, 2012-10-08 13:11:12Z
20, Male, Employee, 540, 438, 290, 2014/01/23 14:33:45
```

各サンプルは、下記コードの「datas」作成部分を変更して、対応します

```erb:lib/sample_db_web/templates/page/index.html.eex
<%
result = "sample.csv"
  |> File.stream!
  |> CSV.decode( strip_fields: true, headers: true )
  |> Enum.map( &( elem( &1, 1 ) ) )
datas = result
columns = datas |> List.first |> Map.keys
%>
<table border="1">
<tr>
  <%= for column <- columns do %>
  <th><%= column %></td>
  <% end %>
</tr>
<%= for record <- datas do %>
<tr>
  <%= for column <- columns do %>
  <td><%= record[ column ] %></td>
  <% end %>
</tr>
<% end %>
</table>
```

# 「数値型」のデータ変換

数値型のデータ変換としては、以下がポピュラーです

- 正規化
- NULL値／外れ値の補完
- NULL値／外れ値を含む行の削除

## 正規化

「正規化」とは、特定の値を基準とするための、各数値に対する加減乗除の実施を指します

以下は、該当列の最小値が1となるような除算を施す正規化を行う例です

```elixir:lib/sample_db_web/templates/page/index.html.eex
…
min = result |> Enum.map( &( &1[ "ApplicantIncome" ] ) ) |> Enum.min |> String.to_integer
datas = result
  |> Enum.map( &( Map.put( &1, "ApplicantIncome", String.to_integer( &1[ "ApplicantIncome" ] ) / min ) ) )
…
```

## NULL値／外れ値の補完

NULL値、もしくは外れ値と見なす数値を、適切な数値へと補完する場合は、以下のような操作を行います

なお、補完後の値には、その数値列全体の平均値／中央値、最大値／最小値、0などを用いることが多いですが、その選び方は、分析の業務内容によります

以下の例では、NULL値（空文字列）に対して、平均値を補完しました

```elixir:lib/sample_db_web/templates/page/index.html.eex
…
sum = result |> Enum.filter( &( &1[ "CoapplicantIncome" ] != "" ) ) |> Enum.map( &( String.to_integer( &1[ "CoapplicantIncome" ] ) ) ) |> Enum.sum
average = sum / ( result |> Enum.map( &( &1[ "CoapplicantIncome" ] ) ) |> Enum.count )
datas = result
  |> Enum.map( &( Map.put( &1, "CoapplicantIncome", if &1[ "CoapplicantIncome" ] == "" do average else &1[ "CoapplicantIncome" ] end ) ) )
…
```

## NULL値／外れ値を含む行の削除

NULL値、もしくは外れ値と見なす数値が含まれる行は除外したい、というケースもあり、以下のように行います

```elixir:lib/sample_db_web/templates/page/index.html.eex
…
datas = result
  |> Enum.filter( &( &1[ "CoapplicantIncome" ] != "" ) )
…
```

# 「文字列型」のデータ変換

文字列型のデータ変換としては、以下がポピュラーです

- 文字列両端のトリム
- 特定文字列長でのカット
- 文字列の（部分）置換
- NULL値／外れ値の変換
- NULL値／外れ値を含む行の削除

末尾2つは、数値型と同一の操作のため割愛します

## 文字列両端のトリム

以下のように、文字列両端のトリムを行います

```elixir:lib/sample_db_web/templates/page/index.html.eex
…
datas = result
  |> Enum.map( &( Map.put( &1, "Profession", String.trim( &1[ "Profession" ] ) ) ) )
…
```

## 特定文字列長でのカット

以下のように、文字列のカットを行います

```elixir:lib/sample_db_web/templates/page/index.html.eex
…
datas = result
  |> Enum.map( &( Map.put( &1, "Profession", String.slice( &1[ "Profession" ], 0..4 ) ) ) )
…
```

## 文字列の（部分）置換

以下のように、文字列の（部分）置換を行います

```elixir:lib/sample_db_web/templates/page/index.html.eex
…
datas = result
  |> Enum.map( &( Map.put( &1, "Profession", String.replace( &1[ "Profession" ], "Un", "Not " ) ) ) )
…
```

# 「カテゴリ型」のデータ変換

カテゴリ型のデータ変換としては、以下がポピュラーです

- カテゴリ値の数値化
- NULL値／外れ値の補完
- NULL値／外れ値を含む行の削除

末尾2つは、数値型と同一の操作のため割愛します

## カテゴリ値の数値化

後続の処理として、機械学習／ディープラーニングに使用したい場合、文字列であるカテゴリ値を、数値に変換することで、特徴量として扱えるようにすることは、よくあるケースです

以下のように、まずカテゴリ値毎の通し番号のマップを作り、文字列を通し番号に変換します

```elixir:lib/sample_db_web/templates/page/index.html.eex
…
categories = result
  |> Enum.map( &( &1[ "Profession" ] ) ) |> Enum.uniq |> Enum.with_index |> Enum.into( %{} )
datas = result
  |> Enum.map( &( Map.put( &1, "Profession", categories[ &1[ "Profession" ] ] ) ) )
…
```

# 「日付・時刻型」のデータ変換

日付・時刻型のデータ変換としては、以下がポピュラーです

- デリミタの変換
- 妥当で無い日付・時刻の補完
- 妥当で無い日付・時刻を含む行の削除
- NULL値／外れ値の変換
- NULL値／外れ値を含む行の削除

2～3番目については、次回の集約と共に解説します

末尾2つは、数値型と同一の操作のため割愛します

## デリミタの変換

日付や時刻を区切る文字（デリミタ）の変換は、文字列の（部分）置換と同等の変換を行います

```elixir:lib/sample_db_web/templates/page/index.html.eex
…
datas = result
  |> Enum.map( &( Map.put( &1, "LoanStartDatetime", String.replace( &1[ "LoanStartDatetime" ], "-", "/" ) ) ) )
…
```

# 終わり

今回は、「データ前処理」のうち、「インプットしたデータの変換」について取り上げました

次回は、「データ前処理」の続きとして、<b>[「インプットしたデータの集約」](https://qiita.com/piacere_ex/items/ef434011d154dc73f0cd)</b>について解説します

明日は @tuchiro さんの「ElixirでSI開発入門 #9 Railsからのモデルの移行2(DDLをパースする)」です

:stars::stars::stars::stars::stars:<b>　お知らせ：Elixir MeetUpを6月末に開催します　</b>:stars::stars::stars::stars::stars:

[<b>「fukuoka.ex#11：DB／データサイエンスにコネクトするElixir」</b>](https://fukuokaex.connpass.com/event/87241)を6/22（金）19時に開催します

fukuoka.exの発足から、ちょうど1周年となる、記念的なイベントでもあるため、このコラムを気に入っていただいた方と、ぜひ一緒に盛り上がりたいです

福岡近辺にお住まいの方であれば、遊びに来てください

<font color="red"><b>大人気により、一度は満席となりましたが、増枠しましたので、下記URLよりご参加ください</b></font>
https://fukuokaex.connpass.com/event/87241

特別ゲストは、Erlang／Elixirの両面で世界的に有名な「力武 健次さん」と、北九州の飯塚市で「e-ZUKA Tech Night」を6年間主催し続けるハウインターナショナルの「谷口 耕平さん」のお二人と、実に豪華なイベントです

私は、今回のシリーズを踏まえた、「1家に1台、パーソナルなデータ分析AIを全員が持つ2020年を作る」というタイトルで、Elixirによる、ブラウザUI上からサクっとデータ分析プラットフォームを披露するLTをお届けします

[![image.png](https://connpass-tokyo.s3.amazonaws.com/thumbs/cb/13/cb130a03343bc310b07244c308b940db.png)](https://fukuokaex.connpass.com/event/87241)

# p.s.「いいね」よろしくお願いします

よろしければ、ページ左上の ![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/4d515047-cc48-382e-c2b1-3ad0cc50dbbf.png) や ![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/a4e3da58-70a3-4197-95a2-6a6906650d01.png) のクリックをお願いしますー:bow:
ここの数字が増えると、書き手としては「ウケている」という感覚が得られ、連載を更に進化させていくモチベーションになりますので、もっとElixirネタを見たいというあなた、私たちと一緒に盛り上げてください！:tada: