関数型でデータサイエンス#1：様々なデータをインプットする

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

昨日は、@kobatako さんの[「ElixirでSlack Botを作った with Qiita API」](https://qiita.com/kobatako/items/586f82b2845f7d1583ce)でした

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

[前回連載「Excelから関数型言語マスター」](https://qiita.com/piacere/items/6714e1440e3f25fb46a1)にて、Elixirによる関数型プログラミングは、Excelを扱うのと似たようなノリで始められ、Elixirの構文として、3つのEnum関数、2つのデータ構造という、たった5つを覚えれば、Web＋DBやWeb＋APIのアプリが開発できることを示しました

今回からスタートする新シリーズ「関数型でデータサイエンス」では、そのニュアンスを踏襲しつつ、ファイル操作やデータ処理を追加していくことで、データ分析エンジンを構築していきます
<br>
:shamrock::shamrock::shamrock::shamrock:<b>　お礼：各種ランキングに69回のランクインを達成しました　:shamrock::shamrock::shamrock::shamrock:

4/27から、30日間に渡り、毎日お届けしている[「季節外れの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)に8回入賞、[Elixirウィークリーランキング](https://qiita.com/tags/elixir)では5週連続で１／２／３フィニッシュを飾り、各種ランキング通算で、<font color="red"><b>トータル69回ものランクイン</b></font>（前週比+60.1%）を達成しています

みなさまの暖かい応援に励まされ、<font color="red"><b>合計452件ものQiita「いいね」</b></font>（前週差+103件）もいただき、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)

# 取り扱う元データ

「データ分析」というと、元データとして、HDFS（Hadoop Distributed File System、Hadoopのファイルシステム）やRDD（Resilient Distributed Datasets、Sparkのデータコレクション）、Bigtable／BigQuery、GCS、Redshift、S3、もしくはElasticsearchといった、クラウド前提のビッグデータ向けストレージや分析エンジンを想像するかも知れませんが、このコラムでは、より基本的な、以下4種類の元データの取り扱いから始めたいと思います

① CSV／TSV
② Excel
③ DB
④ API

IT／非IT含めた企業における、分析前データは、依然、これら形式のものが大半を占め、そもそもクラウドに載せる以前の部分で、戸惑っているのが現実です

そして、これらデータを、どうかき集め、有用なデータ分析側へと回すか、ということに、データ分析活動の80～90%が掛かっていると言っても大袈裟では無い位、需要がとても大きな領域であり、ここがうまくいかないと、機械学習やディープラーニングも、宝の持ち腐れとなります

# 事前準備：データ分析用PhoenixのPJ作成、DB作成、起動

それでは早速、データ分析用Phoenixプロジェクトを作成します

なお、Phoenixのインストールは、[「Excelから関数型言語マスター2回目：『列の抽出』と『Web表示』」のPhoenixインストール](https://qiita.com/piacere_ex/items/b7787580fce5f148242f#phoenix%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%ABpj%E4%BD%9C%E6%88%90%E8%B5%B7%E5%8B%95)を見て、予めインストールしておいてください

MySQLもしくはPostgreSQLも、[「Excelから関数型言語マスター3回目：WebにDBデータ表示」のDBインストール](https://qiita.com/piacere_ex/items/a7558adc6856e3577dc6#db%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)を見て、予めインストールしておいてください

なお、今回の説明中では、特に指定無い限り、PostgreSQLを使って説明しますので、MySQLを使いたい方は、適宜、読み替えてください

```
mix phx.new sample_analytics --no-webpack
```

DBを作成します　<font color="red">※作成しないとiex内でエラー連発となるのでお忘れなく</font>

```
mix ecto.create
```

Phoenixサーバーを起動します

```
iex -S mix phx.server
```

ブラウザで「```http://localhost:4000```」にアクセスすると、Phoenixで作られたWebページが見れます

# ① CSV／TSV

Elixirにて、CSV／TSVを読み込むには、[「CSV」](https://hex.pm/packages/csv)というライブラリを使います

mix.exsの「def deps do」配下の先頭に追記します

```elixir:mix.exs
defmodule SampleAnalytics.Mixfile do
  use Mix.Project
…
  defp deps do
    [
      {:csv, "~> 2.1"}, 
      {:phoenix, "~> 1.3.0"},
      {:phoenix_pubsub, "~> 1.0"},
    ]
  end
…
```

起動したPhoenixサーバーを一度、Ctrl+C2度押しで中断した後、ライブラリを取得します（要ネット接続）

```
mix deps.get
```

Phoenixサーバーを起動します

```
iex -S mix phx.server
```

PJフォルダ配下に、「sample.csv」というファイル名で、下記内容のCSVファイルを作成します

id | name | age | team | position
---|---|---|---|---
1 | enぺだーし | 49 | 有限会社デライトシステムズ | 代表取締役、性能探求者
2 | ざっきー | 45 | 公立大学法人 北九州市立大学 | 准教授、カーネルハッカー
3 | つちろー | 34 | カラビナテクノロジー株式会社 | リードエンジニア、アプリマイスター
4 | ゆじかわ | 30 | カラビナテクノロジー株式会社 | リードエンジニア、グロースハッカー
5 | piacere | 43 | 株式会社TechJIN | CTO、福岡Elixirプログラマ、重力プログラマ、技術顧問

CSVファイルを読み込んで、Web表示します

以前連載のDBやAPI同様、「<table～」で始まるHTML部分は変えていません

```erb:lib/sample_analytics_web/templates/page/index.html.eex
<%
result = "sample.csv"
  |> File.stream!
  |> CSV.decode( strip_fields: true, headers: true )
datas = result
  |> Enum.map( &( elem( &1, 1 ) ) )
columns = [ "id", "name", "position" ]
%>
<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>
```

コードの解説ですが、CSV.decode()の引数として、strip_fieldsを指定すると、カンマ区切りの間にあるスペースを削除し、headersを指定すると、先頭行をヘッダーとして扱うようになり、各データ行をヘッダー項目をキーとしたマップにしてくれます

読み込み結果は、以下形式でCSV.decode()から返ってきます

```elixir
[
  ok: %{ "age" => "49", "id" => "1", "name" => "enぺだーし", "position" => "代表取締役、性能探求者", "team" => "有限会社デライトシステムズ" },
  ok: %{ "age" => "45", "id" => "2", "name" => "ざっきー", "position" => "准教授、カーネルハッカー", "team" => "公立大学法人 北九州市立大学" },
  ok: %{ "age" => "34", "id" => "3", "name" => "つちろー", "position" => "リードエンジニア、アプリマイスター", "team" => "カラビナテクノロジー株式会社" },
  ok: %{ "age" => "30", "id" => "4", "name" => "ゆじかわ", "position" => "リードエンジニア、グロースハッカー", "team" => "カラビナテクノロジー株式会社" },
  ok: %{ "age" => "43", "id" => "5", "name" => "piacere", "position" => "CTO、福岡Elixirプログラマ、重力プログラマ、技術顧問", "team" => "株式会社TechJIN" }
]
```

これは、「キーワードリスト」と呼ばれる、順序付きキーバリューのデータ構造で、「ok:」とその直後のマップ（%{～}）で1組のキーバリューを、リストで束ねたものになります

マップに似ていますが、マップは順序無しキーバリューのため、キーバリューの順序が保証されないのに対し、キーワードリストは、リスト同様、順序が保証されます

キーワードリストは、以下のような、1項目目が「:ok」、2項目目がCSV内容を持つタプルの省略表記であるため、各行をEnum.map()で取り出し、elem( &1, 1 )で2項目目(≒0オリジンなので1を指定）のみ抜き出します

```elixir
[
  { :ok, %{ "age" => "49", "id" => "1", "name" => "enぺだーし", "position" => "代表取締役、性能探求者", "team" => "有限会社デライトシステムズ" } },
  { :ok, %{ "age" => "45", "id" => "2", "name" => "ざっきー", "position" => "准教授、カーネルハッカー", "team" => "公立大学法人 北九州市立大学" } },
  { :ok, %{ "age" => "34", "id" => "3", "name" => "つちろー", "position" => "リードエンジニア、アプリマイスター", "team" => "カラビナテクノロジー株式会社" } },
  { :ok, %{ "age" => "30", "id" => "4", "name" => "ゆじかわ", "position" => "リードエンジニア、グロースハッカー", "team" => "カラビナテクノロジー株式会社" } },
  { :ok, %{ "age" => "43", "id" => "5", "name" => "piacere", "position" => "CTO、福岡Elixirプログラマ、重力プログラマ、技術顧問", "team" => "株式会社TechJIN" } }
]
```

なお参考までに、Keywordモジュールを使って、下記のように取得することもできます

```elixir:lib/sample_analytics_web/templates/page/index.html.eex
…
datas = result
  |> Enum.to_list
  |> Keyword.get_values( :ok )
…
```

結果的に、以下のような、「:ok」部分が無くなったデータになります

```elixir
[
  %{ "age" => "49", "id" => "1", "name" => "enぺだーし", "position" => "代表取締役、性能探求者", "team" => "有限会社デライトシステムズ" },
  %{ "age" => "45", "id" => "2", "name" => "ざっきー", "position" => "准教授、カーネルハッカー", "team" => "公立大学法人 北九州市立大学" },
  %{ "age" => "34", "id" => "3", "name" => "つちろー", "position" => "リードエンジニア、アプリマイスター", "team" => "カラビナテクノロジー株式会社" },
  %{ "age" => "30", "id" => "4", "name" => "ゆじかわ", "position" => "リードエンジニア、グロースハッカー", "team" => "カラビナテクノロジー株式会社" },
  %{ "age" => "43", "id" => "5", "name" => "piacere", "position" => "CTO、福岡Elixirプログラマ、重力プログラマ、技術顧問", "team" => "株式会社TechJIN" }
]
```

上記のようなデータ変換を行い、CSVファイルの内容をWeb表示できるようになりました

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/bc5eab96-cd3f-43b5-6bf1-575acb982f8d.png)

なお、TSVを扱う場合は、下記のように、separatorとしてタブ文字を指定します

```elixir
…
  |> CSV.decode( separator: ?\t, strip_fields: true, headers: true )
…
```

# ② Excel

Elixirにて、Excelを読み込むには、[「Excelion」](https://hex.pm/packages/excelion)というライブラリを使います

また、小粒でピリリなユーティリティライブラリ[「smallex」](https://hex.pm/packages/smallex)のMapListモジュールも使います

mix.exsの「def deps do」配下の先頭に追記します

```elixir:mix.exs
defmodule SampleAnalytics.Mixfile do
  use Mix.Project
…
  defp deps do
    [
      {:smallex, ">= 0.1.2"}, 
      {:excelion, "~> 0.0.5"}, 
      {:csv, "~> 2.1"}, 
      {:phoenix, "~> 1.3.0"},
      {:phoenix_pubsub, "~> 1.0"},
    ]
  end
…
```

起動したPhoenixサーバーを一度、Ctrl+C2度押しで中断した後、ライブラリを取得します（要ネット接続）

```
mix deps.get
```

Phoenixサーバーを起動します

```
iex -S mix phx.server
```

PJフォルダ配下に、「sample.xlsx」というファイル名で、下記内容のCSVファイルを作成します（シート名は、何でも構いません）

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/18391ced-aeb2-d5f8-a986-7715ef979c32.png)

Excelファイルを読み込んで、Web表示します

DB／API／CSV同様、「<table～」で始まるHTML部分は変えていません

```erb:lib/sample_analytics_web/templates/page/index.html.eex
<%
result = "sample.xlsx"
  |> Excelion.parse!( 0, 1 )
header = result |> List.first
datas = result
  |> Enum.drop( 1 )
  |> Enum.map( fn row -> Lst.zip( header, row ) end )
columns = [ "name", "age" ]
%>
<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>
```

コードの解説ですが、Excelion.parse!()の第一引数は0オリジンのシート番号を指定し、第二引数は1オリジンの読み込み開始行番号を指定します

headerは、以下形式でExcelion.parse!()から返ってくるデータから、先頭行のみList.firstで取り出します

```elixir
[
  ["id", "name", "age", "team", "position"],
  ["1", "enぺだーし", "49", "有限会社デライトシステムズ", "代表取締役、性能探求者"],
  ["2", "ざっきー", "45", "公立大学法人 北九州市立大学", "准教授、カーネルハッカー"],
  ["3", "つちろー", "34", "カラビナテクノロジー株式会社", "リードエンジニア、アプリマイスター"],
  ["4", "ゆじかわ", "30", "カラビナテクノロジー株式会社", "リードエンジニア、グロースハッカー"],
  ["5", "piacere", "43", "株式会社TechJIN", "CTO、福岡Elixirプログラマ、重力プログラマ、技術顧問"]
]
```

datasは、上記データから、先頭行のヘッダー部分をEnum.drop( 1 )で削ぎ、残り全行を、headerをキーにしたマップへと、Enum.map()とSmallexのLst.zip()によって変換しています（以下のようなデータになります）

```elixir
[
  %{ "age" => "49", "id" => "1", "name" => "enぺだーし", "position" => "代表取締役、性能探求者", "team" => "有限会社デライトシステムズ" },
  %{ "age" => "45", "id" => "2", "name" => "ざっきー", "position" => "准教授、カーネルハッカー", "team" => "公立大学法人 北九州市立大学" },
  %{ "age" => "34", "id" => "3", "name" => "つちろー", "position" => "リードエンジニア、アプリマイスター", "team" => "カラビナテクノロジー株式会社" },
  %{ "age" => "30", "id" => "4", "name" => "ゆじかわ", "position" => "リードエンジニア、グロースハッカー", "team" => "カラビナテクノロジー株式会社" },
  %{ "age" => "43", "id" => "5", "name" => "piacere", "position" => "CTO、福岡Elixirプログラマ、重力プログラマ、技術顧問", "team" => "株式会社TechJIN" }
]
```

上記のようなデータ変換を行い、Excelの内容をWeb表示できるようになりました

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/78fc1c0b-8e00-134b-d3f7-dc848c4ba378.png)

# ③ DB

DBは、[「Excelから関数型言語マスター3回目：WebにDBデータ表示」](https://qiita.com/piacere_ex/items/a7558adc6856e3577dc6)で、DBアクセスライブラリ「Ecto」を[DBアクセスモジュール](https://qiita.com/piacere_ex/items/a7558adc6856e3577dc6#db%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%82%92%E4%BD%9C%E3%82%8B)で包んだものを作りましたが、全く同じ形式で、PJフォルダ内のlibフォルダ配下にutilフォルダを掘り、今回のPJに合わせて、下記のように作ります

なおEctoは、Phoenix PJを作成した際に、既にライブラリとしてインストールされるため、mix.exsに追加する必要はありません

```elixir:lib/util/db.ex
defmodule Db do
  def query( sql ) when sql != "" do
    { :ok, result } = Ecto.Adapters.SQL.query( SampleAnalytics.Repo, sql, [] )
    result
  end
  def columns_rows( result ) do
    result
    |> rows
    |> Enum.map( fn row -> Lst.zip( columns( result ), row ) end )
  end
  def rows( %{ rows: rows } = _result ), do: rows
  def columns( %{ columns: columns } = _result ), do: columns
end
```

なお、コード中の「SampleAnalytics.Repo」の部分は、作成するPJ名によって変わるため、別PJを作成した際は、「SampleAnalytics」の部分をPJ名と同じにしてください

DBデータを取得し、データを読み込むコードは、以下例のようになります

なお、以下を実行する際は、事前に[「Excelから関数型言語マスター3回目：WebにDBデータ表示」で作成したのと同じテーブル作成とデータ投入](https://qiita.com/piacere_ex/items/a7558adc6856e3577dc6#%E8%A4%87%E6%95%B0%E5%88%97%E3%83%87%E3%83%BC%E3%82%BF%E3%81%A8%E5%90%8C%E3%81%98%E6%A7%8B%E9%80%A0%E3%81%AE%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E4%BD%9C%E6%88%90%E3%83%87%E3%83%BC%E3%82%BF%E6%8A%95%E5%85%A5)を行っておいてください

```erb:lib/sample_analytics_web/templates/page/index.html.eex
<%
datas = Db.query( "select * from members" ) |> Db.columns_rows
columns = [ "id", "name", "position" ]
%>
<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>
```

# ④ API

APIは、[「Excelから関数型言語マスター4回目：Webに外部APIデータ表示」](https://qiita.com/piacere_ex/items/4c212615a4eb699dd109)で、小粒でピリリなユーティリティライブラリ「smallex」の[JSONアクセスモジュール](https://qiita.com/piacere_ex/items/4c212615a4eb699dd109#%E5%A4%96%E9%83%A8api%E3%82%92%E5%91%BC%E3%81%B6%E3%81%9F%E3%82%81%E3%81%AE%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)を使って作りましたが、今回も同じようにします

APIを呼び出し、JSONを取得した後、データを読み込むコードは、以下例のようになります

```erb:lib/sample_analytics_web/templates/page/index.html.eex
<%
datas = Json.get( "https://qiita.com", "/api/v2/items?query=elixir" )
columns = [ "title", "likes_count" ]
%>
<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>
```

# 終わり

今回は、CSV／Excel／DB／APIのデータを読み込む部分の基礎について説明しました

次回は、これらを実際に使い、<b>[「インプットしたデータを変換する」](https://qiita.com/piacere_ex/items/64c2127857599137e8a7)</b>を解説します

明日は @tuchiro さんの[「ElixirでSI開発入門 #7 Multiで使う関数を再利用可能な粒度に分割する」](https://qiita.com/tuchiro/items/8048e38404e0968d9b6e#_reference-bf783065ccc7bc38061b)です
<br>
<br>
: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: