6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Eixirで機械学習に初挑戦: Explorerでデータ処理 (Part 1)

Last updated at Posted at 2023-01-03

@piacerex さんは Elixir Advent Calendar 2022 の「Eixirで機械学習に初挑戦④:データ処理に強いElixirでKaggle挑戦(前半)…「データ前処理」の基礎編 ※最新Livebook 0.8に対応」という記事にデータ処理をEnumMapで処理しました。Explorerはこのために作れりてますので、その部分の処理を書き直しました。

Kaggle挑戦について、上記の記事を読むことをお勧めします。

準備

データが必要です。このチュートリアルは「タイタニック問題」の挑戦を利用してるので、(Titanic - Machine Learning From Disaster Dataset)[https://www.kaggle.com/competitions/titanic/data]ページの右下にいる「Download All」ボタンをクリックして、保存します。Zipファイルの中に3津CSVファイルがあります:

  1. gender_submission.csv - サンプルコンペの結果申し込むデータ
  2. test.csv - 実験・確認するためのデータ
  3. train.csv - 学習データ

ファイルを$HOME/data/titanicに解凍してください。これで他の挑戦のデータを区別するし、わかり安にパスにアクセンできるためですね。

生データをLivebookに読み込む

Livebook最上部の「Notebook dependencies and setup」で、下記を実行し、ライブラリ群をロードしてください

  Mix.install([
-   {:csv, "~> 3.0"},
    {:exla, "~> 0.4"},
    {:axon, "~> 0.3"},
+   {:explorer, "~> 0.4"},   # 追加
+   {:kino, "~> 0.6"}        # 追加
  ])

CSVパーサがExplorerにあるので、いらなくなります。ExplorerとKinoのライブラリーを以前の記事に追加します。

それから、ライブラリ「CSV」でtrain.csvをロードします

+ alias Explorer.DataFrame, as: DF
+ alias Explorer.Series, as: S
+ require Explorer.DataFrame              # For DF.mutate
  
- train_csv_raw =
-   File.stream!("train.csv")
+ train_df =
+   DF.from_csv!("data/titanic/train.csv")
-   |> CSV.decode!
-   |> Enum.to_list
  
+ Kino.DataTable.new(train_csv_raw)

DataFrameDFSeriesSにアリアスして便利です。それからDF.mutateなどのマクロを使うためにrequire Explorer.DataFrameをしています。

その後、直接train.csvをうちのパスからDataFrameにロードします。デコードやリストに変更することが必要ないです。

最後にデータをきれいなテーブルで出力します。

image.png

データ操作しやすくするためにマップ群に変換

リスト内リストのままだと、ヘッダーの列名で値にアクセスするのが不便なので、ヘッダーの列名をキーとするマップ群に変換します

まず、ヘッダーをhdで取り出し、小文字化し、アトム化します

  train_df =
    DF.from_csv!("data/titanic/train.csv")
+   |> DF.rename_with(& String.downcase(&1))
 
- train_csv_header =
-   train_csv_raw
-   |> hd
-   |> Enum.map(& 1 |> String.downcase |> String.to_atom)
+ train_df_header = DF.names(train_df)

下記のように、ヘッダーを小文字化/アトム化されたリストにします

ヘッダーがデータフレームの中にアクセスできるので、変数が本当に必要ないです。でも、すべてのヘッダーが小文字に変更することでデータフレームのマクロで使えるようになりますので、データフレームの初期化するところに追加しました。

["passengerid", "survived", "pclass", "name", "sex", "age", "sibsp", "parch", "ticket", "fare",
 "cabin", "embarked"]

ヘッダー列名をキーとするマップ群は、ヘッダー以降のデータ群をtlで取り出し、ヘッダーと各データをList.zipでキーワードリスト化し、Enum.intoでマップ化することで作成します

- train_csv_maps =
-   train_csv_raw
-   |> tl
-   |> Enum.map(& List.zip([train_csv_header, &1]) |> Enum.into(%{}))

これで各データをマップとして扱えるので、アクセスしやすくなります

マップに変更することも必要ないです。データフレームで全部処理を簡単にできます。

学習のための最低限の「データ前処理」

まず大前提として、機械学習では、文字列や空白のような数値データ以外を入力として使うことはできないので、空白無の数値データのみに変換する必要があり、これを「データ前処理」で行います(これは、学習データとラベルの両方に必要です)

また、以下2つも「データ前処理」で行います

  • データの中には、ラベルと相関性が無い、いわゆる「特徴と言えないデータ」が存在し得るので、その列の削除
  • ID/ラベル/学習データはモデル学習の際に別データとする必要があるため、分離

なお、本講義回では、学習のための最低限の「データ前処理」のみ行い、精度向上のための工夫は次回に預けます

①空白値の確認

まず、IDやラベルも含め、空白値をピックアップし、集計します

- train_csv_maps
- |> Enum.flat_map(fn map -> Map.filter(map, & elem(&1, 1) == "") |> Map.keys end)
- |> Enum.reject(& &1 == [])
- |> Enum.frequencies
+ train_headers
+ |> Enum.reduce(%{}, fn name, acc -> 
+   count = DF.pull(train_df, name) |> S.is_nil() |> S.sum()
+   if (count > 0), do: Map.put(acc, name, count), else: acc
+ end)

結果がデータフレームではないと、ちょっと複雑になります。パイプラインでできませんでした。

これは何をすると、それぞれの列(フィールド)に:

  • シリーズを取得して
  • それぞれの値がnilならtruenil以外ならfalseの新しいシリーズを作成して
  • trueの合計を数して
  • 数が0より以上ならマップにその結果をputする

以下の列に空白値があるようです

> %{"Age" => 177, "Cabin" => 687, "Embarked" => 2}

これらのうち、欠損値の補完が必要か、それとも列自体が不要かをこの後、判断します

なお、「データ前処理」は、学習データだけで無く、検証データや未知データに対しても同じ処理を行う必要があるため、count_missingsで関数化しておきます

  defmodule Pre do
-   def count_missings(datas) do
+   def count_missings(df) do
-     datas
+     DF.names(df)
-     |> Enum.flat_map(fn map -> Map.filter(map, &(elem(&1, 1) == "")) |> Map.keys end)
-     |> Enum.reject(&(&1 == []))
-     |> Enum.frequencies
+     |> Enum.reduce(%{}, fn name, acc -> 
+       count = DF.pull(df, name) |> S.is_nil() |> S.sum()
+       if (count > 0), do: Map.put(acc, name, count), else: acc
+     end)
    end
  end
end

下記のように呼び出します

- train_csv_maps
+ train_df
  |> Pre.count_missings

②ID/ラベル/学習データを分離

ID/ラベル/学習データの分離は、それぞれの列をマップから抜き出すseparateという関数で行います

うち、ラベルについては、この段階で行列化しておきますが、以下2つの操作が必要です

Nx.tensorでの行列化できる値は小数だが、ラベルは整数文字列なので、整数の後ろに「.0」を付加し、String.to_floatすることで小数化
モデルに入力できるよう、「2次元行列のリスト」に変換する必要があるが、ラベル群は単なるリストのため、2次元行> 列で包むために、2重リスト[[~]]で囲んだ上で、Nx.tensorに渡す

  defmodule Pre do
    …
-   def separate(datas, id, label) do
+   def separate(df, id, label) do
      {
-       datas |> Enum.map(& Map.get(&1, id)),
+       DF.pull(df, id) |> S.to_list(),
-       datas |> Enum.map(& [[String.to_float("#{Map.get(&1, label)}.0")]] |> Nx.tensor),
+       DF.pull(df, label)
+       |> S.cast(:float)
+       |> S.to_list
+       |> Enum.map(& [[&1]] |> Nx.tensor),
-       datas |> Enum.map(& Map.drop(&1, [id, label]))
+       df |> DF.discard([id, label]) |> DF.to_rows
      }
    end
end

ラベルの作成することについて、DF.pull(df, "label") |> S.cast(:float) |> S.to_tensorでできるはずと思いましたが、この行列のリストになってしまいました:

#Nx.Tensor<
  f64[891]
  [0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, ...]
>

トレーニングの関数に渡すフォーマットは以下の2次元行列のリストの形が必要です:

 [
   #Nx.Tensor<
     f32[1][1]
     [
       [0.0]
     ]
   >,
   #Nx.Tensor<
     f32[1][1]
     [
       [1.0]
     ]
   >,
   ...
]
  {trains_csv_ids, train_csv_labels, train_csv_maps} =
-   train_csv_all_maps
+   train_df
-   |> Pre.separate(:passengerid, :survived)
+   |> Pre.separate("passengerid", "survived")

ID/ラベル/学習データに分離され、ラベルは「2次元行列のリスト」に変換されたことが確認できます

{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
  28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ...],
 [
   #Nx.Tensor<
     f32[1][1]
     [
       [0.0]
     ]
   >,
   #Nx.Tensor<
     f32[1][1]
     [
       ...
     ]
   >,
   ...
 ],
 [
   %{
     "age" => 22.0,
     "cabin" => nil,
     "embarked" => "S",
     "fare" => 7.25,
     "name" => "Braund, Mr. Owen Harris",
     "parch" => 0,
     "pclass" => 3,
     "sex" => "male",
     "sibsp" => 1,
     "ticket" => "A/5 21171"
   },
   ...
]   

③特徴とならない列の削除

タイタニック問題で「特徴と言えないデータ」に該当すると思われるのは以下です

  • cabin:部屋番号
    • 部屋番号は、生存率にとても高い相関性を持っているはずですが、Cabinは891件中、687件と大量のデータが欠> 損しているため、使い物にならないと判断し、削除
  • name:乗客名
    • 乗客名は、全員が異なり、生存率にも無関係と思われる
    • 名字が同じで、チケット番号が近い or 部屋が近い等であれば、家族乗船の可能性があり、家族全員がボートに乗れるまで待ったとき生存率が低くなる、といった仮説は考えられるが、いったん削除
  • ticket:チケット番号
    • チケット番号そのものは、生存率に無関係と思われる
    • 近い番号の方が、生存率の高い/低い部屋番号にまとまって配置されたという可能性は考えられるが、憶測の域を出ないので、いったん削除

これら列の削除をdropという関数で行います

  defmodule Pre do
    …
-   def drop(datas, keys) do
+   def drop(df, keys) do
-     datas
-     |> Enum.map(& Map.drop(&1, keys))
+     keys
+     |> Enum.reduce(df, & DF.discard(&2, &1))
    end
  end

piacereさんはtrain_csv_mapを作成したあとに"Cabin", "Name", "Ticket"をマップから削除しています。私はその前にデータフレームから削除していますので、train_csv_dropped_mapsが要らなくなります。

- train_csv_dropped_maps = train_csv_maps
-   |> Pre.drop([:cabin, :name, :ticket])
+ train_df = train_df
+   |> Pre.drop(~w(cabin name ticket))

  {trains_csv_ids, train_csv_labels, train_csv_maps} =
    train_df
    |> Pre.separate("passengerid", "survived")

cabinなどの列が削除されているのが確認できます

 [
   %{
     "Age" => 22.0,
     "Embarked" => "S",
     "Fare" => 7.25,
     "Parch" => 0,
     "Pclass" => 3,
     "Sex" => "male",
     "SibSp" => 1
   },
   ...
]

④欠損値の補完

欠損値のうち、cabinは列ごと削除されたので、残るageemberkedが補完対象で、これをcount_missingsという関数で行います

  defmodule Pre do
    def count_missings(datas) do
    …
    def empty_replace(datas, replaces_map) do
      replaces_map
-     |> Enum.reduce(datas, fn {key, replace}, acc ->
+     |> Enum.reduce(df, fn {key, replace}, acc ->
-       acc
-       |> Enum.map(& Map.put(&1, key, String.replace(Map.get(&1, key), ~r/^$/, replace)))
+       DF.put(acc, key, DF.pull(acc, key) |> S.fill_missing(replace))
      end)
    end
  end

DF.pullDF.putでやりたくないですが、DF.mutateS.fill_missingを使う方法はマクロの中にあるので、keyをうまく使えませんでした。

ここではいったん、ageは「0」、emberkedは「S」で補完します(本来、どのような値で補完するかは精度に影響を与えますが、これは次回に)

- train_csv_replaced_maps = train_csv_dropped_maps
-   |> Pre.empty_replace(%{embarked: "S", age: "0"})
  train_df = train_df
    |> Pre.drop(~w(cabin name ticket))
+   |> Pre.empty_replace(%{embarked: "S", age: 0.0})

  {trains_csv_ids, train_csv_labels, train_csv_maps} =
    train_df
    |> Pre.separate("passengerid", "survived")

Pre.empty_replaceの代わりに、以下の方法の方が快適と思います:

  train_df = train_df
    |> Pre.drop(~w(cabin name ticket))
-   |> Pre.empty_replace(%{embarked: "S", age: 0.0})
+   |> DF.mutate(embarked: fill_missing(embarked, "S"))
+   |> DF.mutate(age: fill_missing(age, 0.0))

  {trains_csv_ids, train_csv_labels, train_csv_maps} =
    train_df
    |> Pre.separate("passengerid", "survived")

欠損値が無くなったことを確認できました

    ...
  ],
  [
    %{"age" => 22.0, "embarked" => "S", "fare" => 7.25, "parch" => 0, "pclass" => 3, "sex" => "male", "sibsp" => 1},
    %{"age" => 38.0, "embarked" => "C", "fare" => 71.2833, "parch" => 0, "pclass" => 1, "sex" => "female", "sibsp" => 1},
    %{"age" => 26.0, "embarked" => "S", "fare" => 7.925, "parch" => 0, "pclass" => 3, "sex" => "female", "sibsp" => 0},
    %{"age" => 35.0, "embarked" => "S", "fare" => 53.1, "parch" => 0, "pclass" => 1, "sex" => "female", "sibsp" => 1},
    %{"age" => 35.0, "embarked" => "S", "fare" => 8.05, "parch" => 0, "pclass" => 3, "sex" => "male", "sibsp" => 0},
-   %{"age" => nil, "embarked" => "Q", "fare" => 8.4583, "parch" => 0, "pclass" => 3, "sex" => "male", "sibsp" => 0},
+   %{"age" => 0.0, "embarked" => "Q", "fare" => 8.4583, "parch" => 0, "pclass" => 3, "sex" => "male", "sibsp" => 0},
    %{"age" => 54.0, "embarked" => "S", "fare" => 51.8625, "parch" => 0, "pclass" => 1, "sex" => "male", "sibsp" => 0},
    %{"age" => 2.0, "embarked" => "S", "fare" => 21.075, "parch" => 1, "pclass" => 3, "sex" => "male", "sibsp" => 3},
    ...
    %{"age" => 40.0, "embarked" => "S", "fare" => 9.475, "parch" => 0, "pclass" => 3, "sex" => "female", ...},
    %{"age" => 27.0, "embarked" => "S", "fare" => 21.0, "parch" => 0, "pclass" => 2, ...},
-   %{"age" => nil, "embarked" => "C", "fare" => 7.8958, "parch" => 0, ...},
+   %{"age" => 0.0, "embarked" => "C", "fare" => 7.8958, "parch" => 0, ...},
    %{"age" => 3.0, "embarked" => "C", "fare" => 41.5792, ...},
    %{"age" => 19.0, "embarked" => "Q", ...},
-   %{"age" => nil, ...},
+   %{"age" => 0.0, ...},
    %{...},
    ...
  ]}

⑤カテゴリ値(種別文字列)を数値に変換

sexは、「male」と「female」の2種類の文字列で男女を分類していますが、このような種別を表す文字列の数値化は、「male」を「0」、「female」を「1」のように、通番の数値に置換することで実現できます

このような種別を表す文字列を「カテゴリ値」と呼びます

また、通番数値はダミーの値であることから「ダミー変数」と呼ばれることもあります(Pythonのpandasではget_dummiesという関数でこの置換を行います)

こうした置換自体を「ワンホットエンコーディング」と呼ぶこともあります

さて蘊蓄はこのくらいにして、置換用コードを書いてみましょう

まずは、指定列をカテゴリ値とみなし、ダミー値を振ったマップを生成するmake_dummiesという関数を追加します

指定されたキーのみを抽出し、ユニーク化した後、Enum.with_indexでインデックスを振り出し、Enum.intoでマップ化することで実装します

このとき、Enum.with_indexで振り出すインデックスは、整数がデフォルトなので、separeteでラベルに行ったのと同じ方法で小数化します

  defmodule Pre do
    …
-   def make_dummies(datas, key) do
+   def make_dummies(df, key) do
+     series = DF.pull(df, key)
-     datas
-     |> Enum.map(& Map.get(&1, key))
+     map = S.to_list(series)
        |> Enum.uniq
        |> Enum.with_index(& {&1, String.to_float("#{&2}.0")})
        |> Enum.into(%{})
+     new_series = S.transform(series, & Map.get(map, &1))
+     DF.put(df, key, new_series)
    end

まだDF.pullDF.putでデータフレームを更新しています。でも、その間にマップを作成する部分がほぼ同じです。

データフレームはdummiesという関数があります。でも、このget_dummiesと関係ないです。DataFrame.dummies(df, "column")columnの列にランダムの01を入れます。

カテゴリ値であるsexとemberkedのダミー値入りマップを生成してみます

- Pre.make_dummies(train_csv_replaced_maps, :embarked)
+ Pre.make_dummies(train_df, "embarked")
- Pre.make_dummies(train_csv_replaced_maps, :sex)
+ Pre.make_dummies(train_df, "sex")

ダミー値入りマップが生成可能となりました

%{"C" => 1, "Q" => 2, "S" => 0}
%{"female" => 1, "male" => 0}

次に、make_dummiesを使って、指定列をダミー値に入れ替えるto_dummiesという関数を追加します

  defmodule Pre do
    …
-   def to_dummies(datas, train_maps, keys) do
+   def to_dummies(df, keys) do
      keys
      |> Enum.reduce(datas, fn key, acc ->
-       acc
-       |> Enum.map(& Map.put(&1, key, make_dummies(train_maps, key)[Map.get(&1, key)]))
+       map = make_dummies(acc, key)
+       DF.pull(acc, key)
+       |> S.transform(& Map.get(map, &1))
+       |> then(& DF.put(acc, key, &1))
      end)
    end

私のやり方では引き続いてデータフレームを編集しています。だから、分けたtrain_mapsが必要ないですね。直接keysの列をダミーマップを作成して、データを更新します。

カテゴリ値であるsexとemberkedを置換します

- train_csv_dummied_maps = train_csv_replaced_maps
-   |> Pre.to_dummies(train_csv_replaced_maps, [:embarked, :sex])
  train_df = train_df
    |> Pre.drop(~w(cabin name ticket))
    |> DF.mutate(embarked: fill_missing(embarked, "S"))
    |> DF.mutate(age: fill_missing(age, 0.0))
+   |> Pre.to_dummies(["embarked", "sex"])

  {trains_csv_ids, train_csv_labels, train_csv_maps} =
    train_df
    |> Pre.separate("passengerid", "survived")

カテゴリ値が数値に置換されました

{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
  28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ...],
 [
#Nx.Tensor<
     f32[1][1]
     [
       [0.0]
     ]
   >,
   ...
   #Nx.Tensor<
     f32[1][1]
     [
       ...
     ]
   >,
   ...
 ],
 [
   %{"age" => 22.0, "embarked" => 0, "fare" => 7.25, "parch" => 0, "pclass" => 3, "sex" => 0, "sibsp" => 1},
   %{"age" => 38.0, "embarked" => 1, "fare" => 71.2833, "parch" => 0, "pclass" => 1, "sex" => 1, "sibsp" => 1},
   %{"age" => 26.0, "embarked" => 0, "fare" => 7.925, "parch" => 0, "pclass" => 3, "sex" => 1, "sibsp" => 0},
   %{"age" => 40.0, "embarked" => 0, "fare" => 9.475, "parch" => 0, "pclass" => 3, "sex" => 1, ...},
   %{"age" => 27.0, "embarked" => 0, "fare" => 21.0, "parch" => 0, "pclass" => 2, ...},
   %{"age" => 0.0, "embarked" => 1, "fare" => 7.8958, "parch" => 0, ...},
   %{"age" => 3.0, "embarked" => 1, "fare" => 41.5792, ...},
   %{"age" => 19.0, "embarked" => 2, ...},
   %{"age" => 0.0, ...},
   %{...},
   ...
 ]}

⑥整数を小数に変換

数値文字列から数値への変換を行いますが、ここで、整数と小数が混在する列は、文字列から数値への変換をString.to_integerString.to_floatを使い分けしなければならなくて面倒なため、整数文字列を全て小数文字列に変換した上で、小数に変換します

まずは、整数値は小数表記の文字列に変換した上で、全て小数に置換するinteger_string_to_floatという関数を追加します

integer_string_to_floatでは、指定された全キーに対し、Enum.reduceを使って、小数化を行っていきます

整数文字列の小数文字列化は、小数点である「.」が存在しない値を整数とみなし、String.replaceの正規表現置換によって後ろに「.0」を付加します

これがデータフレームでは必要ないです。Axonも:integer, :boolean, :floatなどの数字がそのままで使えます。結果だけが小数点の形が必要らしいです。

⑦数値を行列に変換

最後に、数値をモデルに入力できるよう、行列に変換するためにmap_to_tensorという関数を追加します

ここで、「2次元行列のリスト」になるよう、Map.valuesで出力されるリストを、更にリスト[~]で囲んだ上で、

Nx.tensorにて行列化します

  defmodule Pre do
    …
    def map_to_tensor(datas) do
      datas
      |> Enum.map(& [Map.values(&1)] |> Nx.tensor)
    end
  end

そのままで使えます。

  {trains_csv_ids, train_csv_labels, train_csv_maps} =
    train_df
    |> Pre.separate("passengerid", "survived")

- train_csv_datas = train_csv_nimeric_maps
+ train_csv_datas = train_csv_maps
    |> Pre.map_to_tensor

データフレームを最後までに変更して、separateしましたので、train_csv_numeric_mapsがないですね。でも、マップのままで作成したから、関数を変わらずに同じ結果になります。

学習データが「2次元行列のリスト」に変換されました

[
  #Nx.Tensor<
    f32[1][7]
    [
      [22.0, 0.0, 7.25, 0.0, 3.0, 0.0, 1.0]
    ]
  >,
  #Nx.Tensor<
    f32[1][7]
    [
      [38.0, 1.0, 71.2833023071289, 0.0, 1.0, 1.0, 1.0]
    ]
  >,
  ...
]

⑧「データ前処理」全体の関数化

ここまでの「データ前処理」をprocessという関数で1発で完了するようにします

ここで、datasとtrain_datasの2つの引数を取っている理由ですが、これはto_dummiesの実施が、検証データや未知データではカテゴリ値が網羅されていないケースへの対策として、学習データからカテゴリ値を拾うためです

この処理は以下のことをPreモジュールでやることらしいです。でも、全部はseparateしたの後の処理ですので、順番が違います。このままで処理を続きます。

train_df = train_df
  |> Pre.drop(~w(cabin name ticket))
  |> DF.mutate(embarked: fill_missing(embarked, "S"))
  |> DF.mutate(age: fill_missing(age, 0.0))
  |> Pre.to_dummies(["embarked", "sex"])

{trains_csv_ids, train_csv_labels, train_csv_maps} =
  train_df
  |> Pre.separate("passengerid", "survived")

train_csv_datas = train_csv_maps
  |> Pre.map_to_tensor

更に、CSVファイルロード/マップ群変換/分離(ID/ラベル/学習データ)を処理する処理を、csv_file_to_datasとして関数化しておきます

ここで哲学的な意見の相違が生じているようです。 問題固有の関数が Pre のような汎用モジュールに属しているとは思えません。

しかし、データフレームの読み取りとその処理を統合したいので、その部分をここに移動しましょう。

- train_df = train_df
+ train_df = 
+   DF.from_csv!("data/titanic/train.csv")
+   |> DF.rename_with(& String.downcase(&1))
    |> Pre.drop(~w(cabin name ticket))
    |> DF.mutate(embarked: fill_missing(embarked, "S"))
    |> DF.mutate(age: fill_missing(age, 0.0))
    |> Pre.to_dummies(["embarked", "sex"])

  {trains_csv_ids, train_csv_labels, train_csv_maps} =
    train_df
    |> Pre.separate("passengerid", "survived")
  
  train_csv_datas = train_csv_maps
  |> Pre.map_to_tensor

データフレームを読んで、リデュースして、処理する。これがメインプログラムですので、モジュールに隠れたくないです。

6
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?