(この記事は、「fukuoka.ex(その2) Elixir Advent Calendar 2017」の22日目、
および「ディープラーニングのエンジニアリング Advent Calendar 2017」の6日目です)
昨日は、@takasehideki さんの「Erlang/OTPのソースビルドでHiPEが入らなかった時の対処法」でした
fukuoka.ex代表のpiacereです
今回もご覧いただいて、ありがとうございます
この連載の、前回までの記事は、以下になります
|> 関数型でデータサイエンス#1:様々なデータをインプットする
今回は、インプットしたデータを、後続の分析処理で使いやすくするための「データ変換」をElixirを行うコードについて解説します
Elixirが、データサイエンスに向いた言語であることを実感する回となります
お礼:各種ランキングに83回のランクインを達成しました
4/27から、37日間に渡り、毎日お届けしている「季節外れのfukuoka.ex Elixir Advent Calendar」と「季節外れのfukuoka.ex(その2) Elixir Advent Calender」ですが、Qiitaトップページトレンドランキングに12回入賞、Elixirウィークリーランキングでは6週連続で1/2/3フィニッシュを飾り、各種ランキング通算で、トータル83回ものランクインを達成しています
みなさまの暖かい応援に励まされ、合計552件ものQiita「いいね」もいただき、fukuoka.exアドバイザーズとfukuoka.exキャストの一同、ますます季節外れのfukuoka.ex Advent Calendar、頑張っていきます
データ分析活動の要である「データ前処理」
前回もお伝えした通り、既にあるデータを、どのように搔き集め、データ分析側へと回すか、ということに、データ分析活動の80~90%が掛かっており、ここがうまくいかないと、機械学習やディープラーニングも、宝の持ち腐れとなります
こうした、「データ収集」と「データ前処理」は、需要がとても大きな領域で、中でも「データ前処理」は、エンジニアリングの比重がとても大きい、システム開発・運用・性能担保のノウハウを必要とする部分ですが、何故かクローズアップされる機会が、あまり多くありません
今回と次回で、この「データ前処理」を扱っていきますが、今回は、「インプットしたデータの変換」について取り上げます
なお、集約を伴う変換については、次回で扱いますので、今回は除外します
参考:「データ収集」と「データ前処理」以外の工程
データ分析/データサイエンスにおける工程として、「データ収集」の前に、「データ分析の目的設定」「分析計画」「分析設計とデータ設計」といった工程があります
また、「データ前処理」の後続には、「分析モデルの構築」「分析結果の評価」「未知データの分析」「未知データの分析結果の検証」といった工程があります
これら工程のうち、「分析モデルの構築」「分析結果の評価」については、本連載の範囲内として扱っていく予定です
その他の工程については、タイミングがあれば本連載中に、無ければ別の機会で扱いたいと思います
インプットデータの型の種類
インプットデータの各値には、以下のような、型の種類があります
- 数値型
- 文字列型
- カテゴリ型
- 日付・時刻型
- 位置情報型
- 画像・バイナリデータ型
「カテゴリ型」という名称に馴染みが無い方向けに解説すると、「red」「blue」「green」のような、バリエーションの個数が限られた文字列型を指します(enumのようなものをイメージしていただくと近いでしょう)
なお本連載では、CSVのような文字テキストデータを中心に扱うため、位置情報型と画像・バイナリデータ型は、対象外とし、本コラムでは、それ以外のデータ型についてのデータ変換を扱います
それでは、Phoenixサーバーを起動し、ブラウザで「http://localhost:4000
」のWebページが見れる状態にしたら、以下を試していきましょう
サンプルデータ
前処理の処理サンプルに使うデータは、以下の少額ローン借入のサンプルデータが入った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」作成部分を変更して、対応します
<%
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となるような除算を施す正規化を行う例です
…
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値(空文字列)に対して、平均値を補完しました
…
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値、もしくは外れ値と見なす数値が含まれる行は除外したい、というケースもあり、以下のように行います
…
datas = result
|> Enum.filter( &( &1[ "CoapplicantIncome" ] != "" ) )
…
「文字列型」のデータ変換
文字列型のデータ変換としては、以下がポピュラーです
- 文字列両端のトリム
- 特定文字列長でのカット
- 文字列の(部分)置換
- NULL値/外れ値の変換
- NULL値/外れ値を含む行の削除
末尾2つは、数値型と同一の操作のため割愛します
文字列両端のトリム
以下のように、文字列両端のトリムを行います
…
datas = result
|> Enum.map( &( Map.put( &1, "Profession", String.trim( &1[ "Profession" ] ) ) ) )
…
特定文字列長でのカット
以下のように、文字列のカットを行います
…
datas = result
|> Enum.map( &( Map.put( &1, "Profession", String.slice( &1[ "Profession" ], 0..4 ) ) ) )
…
文字列の(部分)置換
以下のように、文字列の(部分)置換を行います
…
datas = result
|> Enum.map( &( Map.put( &1, "Profession", String.replace( &1[ "Profession" ], "Un", "Not " ) ) ) )
…
「カテゴリ型」のデータ変換
カテゴリ型のデータ変換としては、以下がポピュラーです
- カテゴリ値の数値化
- NULL値/外れ値の補完
- NULL値/外れ値を含む行の削除
末尾2つは、数値型と同一の操作のため割愛します
カテゴリ値の数値化
後続の処理として、機械学習/ディープラーニングに使用したい場合、文字列であるカテゴリ値を、数値に変換することで、特徴量として扱えるようにすることは、よくあるケースです
以下のように、まずカテゴリ値毎の通し番号のマップを作り、文字列を通し番号に変換します
…
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つは、数値型と同一の操作のため割愛します
デリミタの変換
日付や時刻を区切る文字(デリミタ)の変換は、文字列の(部分)置換と同等の変換を行います
…
datas = result
|> Enum.map( &( Map.put( &1, "LoanStartDatetime", String.replace( &1[ "LoanStartDatetime" ], "-", "/" ) ) ) )
…
終わり
今回は、「データ前処理」のうち、「インプットしたデータの変換」について取り上げました
次回は、「データ前処理」の続きとして、「インプットしたデータの集約」について解説します
明日は @tuchiro さんの「ElixirでSI開発入門 #9 Railsからのモデルの移行2(DDLをパースする)」です
お知らせ:Elixir MeetUpを6月末に開催します
「fukuoka.ex#11:DB/データサイエンスにコネクトするElixir」を6/22(金)19時に開催します
fukuoka.exの発足から、ちょうど1周年となる、記念的なイベントでもあるため、このコラムを気に入っていただいた方と、ぜひ一緒に盛り上がりたいです
福岡近辺にお住まいの方であれば、遊びに来てください
大人気により、一度は満席となりましたが、増枠しましたので、下記URLよりご参加ください
https://fukuokaex.connpass.com/event/87241
特別ゲストは、Erlang/Elixirの両面で世界的に有名な「力武 健次さん」と、北九州の飯塚市で「e-ZUKA Tech Night」を6年間主催し続けるハウインターナショナルの「谷口 耕平さん」のお二人と、実に豪華なイベントです
私は、今回のシリーズを踏まえた、「1家に1台、パーソナルなデータ分析AIを全員が持つ2020年を作る」というタイトルで、Elixirによる、ブラウザUI上からサクっとデータ分析プラットフォームを披露するLTをお届けします
p.s.「いいね」よろしくお願いします
よろしければ、ページ左上の や のクリックをお願いしますー
ここの数字が増えると、書き手としては「ウケている」という感覚が得られ、連載を更に進化させていくモチベーションになりますので、もっとElixirネタを見たいというあなた、私たちと一緒に盛り上げてください!