やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

【サンプルコード有り】ElixirとFakerを使ってサンプルデータを作る方法

githubの探検中に面白いものを見つける

なんだこれは(褒め言葉

github.com
hexdocs.pm

Faker is a pure Elixir library for generating fake data.

fmfm...
どうやらフェイクデータを色々作れるライブラリだそうです
公式ドキュメントを見てみると

など様々なフェイクデータが用意されている模様

Fakerのインストール

今回は作成したデータのcsv出力までを行いたいのでついでにcsvもインストールしておく
いつも通りに新規プロジェクトを作成する

mix new create_fake

./create_fake/mix.exsのdepsに以下を追記

#この2つを追加
defp deps do
    [
      {:faker, "~> 0.12"},
      {:csv, "~> 2.3"},
    ]
  end

いつも通りコマンドを叩き、外部ライブラリをダウンロードする

cd create_fake
mix deps.get

さらにtest/test_helper.exsに以下を追加

Faker.start()

これで準備は完了

とりあえずデータを作ってみる

まずは存在しない人間の情報を作ってみることにした
持たせる情報は

上記の6つでマップ形式でサンプルデータを作成する

FakerErlangモジュールの:randを使ってコードを書くとこんな感じに

iex> %{
  email: Faker.Internet.email(),
  city: Faker.Address.En.city(),
  name: Faker.Name.En.first_name() <> " " <> Faker.Name.En.last_name(),
  phone: Faker.Phone.EnUs.phone(),
  food: Faker.Food.dish(),
  age: trunc(:rand.uniform() * 100)
}

%{
  age: 26,
  city: "East Malachi",
  email: "dorcas1964@bergstrom.biz",
  food: "Fettuccine Alfredo",
  name: "Aniya Herzog",
  phone: "957/391-6739"
}

うまく生成された
もう一度、実行してみると全く別のデータが作成された

%{
  age: 39,
  city: "Nolan",
  email: "ashly.romaguera@johnson.org",
  food: "Chilli con carne",
  name: "Marta Yost",
  phone: "409.287.2291"
}

このままだと単一のデータしか生成されないので
指定数分のデータをリストに格納して作成できるように変更

defmodule CreateMock do
  def user_info(num) do
    1..num |> Enum.map(fn _ -> _user_info() end)
  end
  defp _user_info do
    %{
      email: Faker.Internet.email(),
      city: Faker.Address.En.city(),
      name: Faker.Name.En.first_name() <> " " <> Faker.Name.En.last_name(),
      phone: Faker.Phone.EnUs.phone(),
      food: Faker.Food.dish(),
      age: trunc(:rand.uniform() * 100) #Erlangモジュールを使って乱数を生成
    }
  end
end

呼び出してみると良さげに生成されている

iex(11)> CreateMock.user_info(3)
[
  %{
    age: 40,
    city: "Nicolas",
    email: "david2068@langosh.org",
    food: "Philadelphia maki",
    name: "Gwen Weber",
    phone: "930.422.1730"
  },
  %{
    age: 52,
    city: "Bayer",
    email: "yvonne2077@reynolds.biz",
    food: "Linguine with clams",
    name: "Favian Cummings",
    phone: "421/864-1117"
  },
  %{
    age: 86,
    city: "Haley",
    email: "gracie2021@hettinger.info",
    food: "Cheeseburger",
    name: "Margarette Ratke",
    phone: "(867) 614-8718"
  }
]

次にありそうなテストの成績データを生成してみる
持たせる情報は

  • 名前
  • 点数
  • 科目名

の3つ

もう説明することは特にないのでコードと結果を先にお見せします

defmodule CreateMock do
  #デフォルトでmath(数学)を設定
  def exam_info(num, subject \\ "math") do
    1..num |> Enum.map(fn _ -> _exam_info(subject) end)
  end
  defp _exam_info(subject) do
    %{
      name: Faker.Name.En.first_name() <> " " <> Faker.Name.En.last_name(),
      score: trunc(:rand.uniform() * 100),
      subject: subject
    }
  end
end
iex(12)> CreateMock.exam_info(3)
[
  %{name: "General Wisoky", score: 55, subject: "math"},
  %{name: "Alessia Cummings", score: 23, subject: "math"},
  %{name: "Lilian Senger", score: 43, subject: "math"}
]

いいですね
フェイクデータの作成に関してはここで終了です
せっかくなのでcsvに書き出すことにします

生成したデータのcsvへの書き出し

生成したデータ(リストinマップ)の形式を
csvライブラリを使って書き出せるように指定形式の2次元のリストに変換します
ついでにheaderも挿入する

def output_data_to_csv(lst, header, file_name) do
    #ファイルを作成
    file = File.open!(file_name <> ".csv", [:write, :utf8])
    lst
      |> Enum.map(&(Enum.map(Map.to_list(&1), fn x ->
            {_, val} = x
            val
          end)
        ))
      |> List.insert_at(0, header)
      |> CSV.encode
      |> Enum.each(&IO.write(file, &1))
  end

少しトリッキー(自称)なのはこの部分

Enum.map(&(Enum.map(Map.to_list(&1), fn x ->
            {_, val} = x
            val
          end)
        ))

まずは作成したデータをMap.to_listを使ってまずはリスト形式に変換する

res = [
  %{name: "Kennith Kling", score: 18, subject: "math"},
  %{name: "Oliver O'Connell", score: 36, subject: "math"},
  %{name: "Nicole Gutkowski", score: 6, subject: "math"},
  %{name: "Blanche Deckow", score: 19, subject: "math"},
  %{name: "Lysanne Gleichner", score: 62, subject: "math"},
]

iex> Enum.map(res, &(Map.to_list(&1)))
[
  [name: "Kennith Kling", score: 18, subject: "math"],
  [name: "Oliver O'Connell", score: 36, subject: "math"],
  [name: "Nicole Gutkowski", score: 6, subject: "math"],
  [name: "Blanche Deckow", score: 19, subject: "math"],
  [name: "Lysanne Gleichner", score: 62, subject: "math"],
]

このままだとkeyの値も含まれているため取り除きたい

data = [name: "Kennith Kling", score: 18, subject: "math"],

このデータに対してIO.inspectをそれぞれの要素に実行するとタプルになっていることが分かる

iex> Enum.map(data, &(IO.inspect(&1)))
{name: "Kennith Kling"}
{score: 18}
{subject: "math}

なのでパターンマッチを使ってvalueのみを取り出す必要があり
こんなコードになったわけです

Enum.map(&(Enum.map(Map.to_list(&1), fn x ->
            {_, val} = x
            val
          end)
        ))

あとはcsvのドキュメントに従ってheaderを追加して書き出しているだけです
呼び出してみます

iex> fake_data = CreateMock.exam_info(20)
iex> CreateMock.output_data_to_csv(fake_data, ["name", "score", "subject"], "score")
:ok

無事にscore.csvが生成されました

name,score,subject
Kennith Kling,18,math
Oliver O'Connell,36,math
Nicole Gutkowski,6,math
:
:
Blanche Deckow,19,math

これうまく使えれば、勉強会とかでのデータ準備がめちゃくちゃ楽になりますね