permutedims
はベクトルを1行のマトリックスに変換してくれるbuiltin関数です。juliaで前処理大全 9.カテゴリ型
juliaで前処理大全その8です。今回はカテゴリ型を取り扱います。
カテゴリ型への変換
Q カテゴリ型の変換
CategoricalArrays.jlを使うとカテゴリ型に変換できます。
using DataFrames,CSV,Chain,Downloads
using CategoricalArrays
customer_url = "https://raw.githubusercontent.com/hanafsky/awesomebook/master/data/customer.csv"
customer_df = @chain customer_url begin
Downloads.download
CSV.File
DataFrame
DataFrames.transform(:sex=>categorical=>:sex_c)
end
first(customer_df,10) |> println
10×6 DataFrame
Row │ customer_id age sex home_latitude home_longitude sex_c
│ String7 Int64 String7 Float64 Float64 Cat…
─────┼───────────────────────────────────────────────────────────────────
1 │ c_1 41 man 35.0922 136.512 man
2 │ c_2 38 man 35.3251 139.411 man
3 │ c_3 49 woman 35.1205 136.511 woman
4 │ c_4 43 man 43.0349 141.24 man
5 │ c_5 31 man 35.1027 136.524 man
6 │ c_6 52 man 34.4408 135.39 man
7 │ c_7 50 man 43.0158 141.231 man
8 │ c_8 65 woman 38.2013 140.466 woman
9 │ c_9 36 woman 33.3228 130.331 woman
10 │ c_10 34 woman 34.2904 132.303 woman
カテゴリ型のマスターデータには、Rの場合と同じく、levels関数でアクセスできます。
@show levels(customer_df.sex_c)
levels(customer_df.sex_c) = InlineStrings.String7["man", "woman"]
カテゴリ型のインデックスを取得するにはrefs
というフィールドを取得すれば、閲覧可能です。 ただし、UInt型なので、Int型に変換して見やすくしています。
@show customer_df.sex_c.refs .|> Int
customer_df.sex_c.refs .|> Int = [1, 1, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 1, 1, 2, 2, 1, 1, 1, 2, 1, 1, 2, 1, 2, 1, 2, 2, 2, 1, 1, 1, 2, 2, 1, 2, 2, 1, 1, 2, 2, 2, 1, 1, 2, 2, 1, 2, 2, 1, 2, 1, 2, 1, 2, 2, 2, 2, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 1, 1, 2, 2, 1, 1, 1, 1, 2, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 1, 2, 1, 2, 2, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2, 1, 1, 2, 1, 2, 1, 1, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 2, 2, 1, 1, 2, 1, 2, 2, 2, 2, 2, 1, 1, 2, 1, 2, 2, 1, 2, 1, 2, 2, 1, 2, 1, 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 2, 1, 2, 2, 1, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 2, 1, 2, 2, 1, 2, 1, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 2, 2, 1, 2, 1, 2, 2, 1, 1, 2, 2, 1, 2, 2, 1, 2, 1, 2, 1, 2, 1, 1, 2, 1, 1, 1, 2, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 2, 1, 2, 2, 2, 2, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 2, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 1, 2, 1, 1, 2, 1, 1, 2, 1, 2, 2, 1, 2, 2, 1, 2, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 2, 2, 2, 1, 1, 2, 1, 2, 2, 1, 1, 2, 2, 2, 1, 1, 2, 1, 1, 2, 1, 2, 1, 1, 2, 2, 1, 2, 2, 1, 2, 1, 2, 2, 1, 2, 1, 1, 1, 2, 2, 2, 1, 1, 2, 2, 1, 2, 1, 2, 1, 2, 2, 1, 2, 2, 2, 2, 2, 1, 2, 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 1, 2, 2, 2, 1, 1, 2, 2, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2, 1, 2, 1, 1, 1, 2, 1, 1, 2, 2, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 2, 2, 2, 1, 1, 2, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 1, 1, 2, 2, 2, 1, 2, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 2, 1, 2, 1, 1, 1, 1, 2, 2, 1, 2, 1, 1, 2, 2, 1, 1, 1, 2, 2, 2, 1, 2, 2, 1, 1, 2, 1, 2, 2, 1, 2, 2, 1, 1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 1, 2, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 2, 2, 1, 1, 2, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2, 1, 2, 1, 1, 2, 2, 2, 1, 2, 1, 1, 1, 1, 2, 1, 1, 2, 1, 2, 2, 1, 2, 1, 2, 2, 1, 2, 2, 1, 1, 1, 2, 2, 1, 2, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 1, 2, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 2, 2, 1, 2, 1, 1, 1, 2, 1, 2, 2, 1, 1, 2, 1, 2, 1, 2, 1, 2, 2, 1, 1, 1, 2, 1, 2, 1, 1, 2, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 1, 2, 2, 2, 2, 1, 2, 2, 1, 2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 2, 1, 1, 2, 1, 1, 2, 1, 2, 2, 1, 2, 2, 1, 1, 1, 2, 2, 1, 2, 1, 2, 1, 1, 2, 2, 1, 2, 1, 2, 2, 2, 2, 1, 2, 2, 1, 2, 2, 1, 1, 1, 1, 2, 1, 1, 2, 2, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 2, 2, 2, 1, 1, 2, 2, 1, 2, 2, 2, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1, 2, 1, 2, 1, 1, 2, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2, 2, 1, 2, 1, 2, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1]
インデックスの順番を自分好みに変えることもできます。
levels(customer_df.sex_c, ["woman","man"])
ダミー変数化
Q ダミー変数化
文字のカテゴリをダミー変数化します。いわゆるOne hot encoding
という処理です。 juliaではいくつかこの機能を備えたパッケージはありますが、 大して難しいコードでもないので、パイプライン処理の中に自分で書いてしまっても良さそうです。 こちらのdiscourseの議論を参考にしてみました。ski先生流の書き方ならこのようになります。
@chain customer_df begin
select(:sex, [:sex=>ByRow(isequal(v))=>Symbol("sex_is_"*v) for v in unique(customer_df.sex)])
first(10)
println
end
10×3 DataFrame
Row │ sex sex_is_man sex_is_woman
│ String7 Bool Bool
─────┼───────────────────────────────────
1 │ man true false
2 │ man true false
3 │ woman false true
4 │ man true false
5 │ man true false
6 │ man true false
7 │ man true false
8 │ woman false true
9 │ woman false true
10 │ woman false true
行列とブロードキャストを利用したエレガントな別解もあります。
customer_df.sex .== permutedims(unique(customer_df.sex))
カテゴリ値の集約
Q カテゴリ値の集約
60歳以上のカテゴリ値を1つに集約するお題です。 まず、数値型の例と同様にCategoricalArrays.jlを使ってカテゴリ型に変更します。 CategoricalArrayがAny型をサポートしていないため、replace!
関数で60歳以上のカテゴリを60に入れてしまうことにしました。
transform!(customer_df, :age=>ByRow(c->10floor(Int,c/10))=>:age_rank)
transform!(customer_df, :age_rank=>categorical=>:age_rank)
replace!(customer_df.age_rank,[70,80]=>60)
first(customer_df,20) |> println
20×7 DataFrame
Row │ customer_id age sex home_latitude home_longitude sex_c age_rank
│ String7 Int64 String7 Float64 Float64 Cat… Cat…
─────┼─────────────────────────────────────────────────────────────────────────────
1 │ c_1 41 man 35.0922 136.512 man 40
2 │ c_2 38 man 35.3251 139.411 man 30
3 │ c_3 49 woman 35.1205 136.511 woman 40
4 │ c_4 43 man 43.0349 141.24 man 40
5 │ c_5 31 man 35.1027 136.524 man 30
6 │ c_6 52 man 34.4408 135.39 man 50
7 │ c_7 50 man 43.0158 141.231 man 50
8 │ c_8 65 woman 38.2013 140.466 woman 60
9 │ c_9 36 woman 33.3228 130.331 woman 30
10 │ c_10 34 woman 34.2904 132.303 woman 30
11 │ c_11 73 man 33.3056 130.301 man 60
12 │ c_12 46 man 34.3033 132.304 man 40
13 │ c_13 47 woman 43.0126 141.242 woman 40
14 │ c_14 79 woman 43.0146 141.225 woman 60
15 │ c_15 25 woman 43.0219 141.242 woman 20
16 │ c_16 63 woman 34.3132 132.281 woman 60
17 │ c_17 48 woman 35.0855 136.555 woman 40
18 │ c_18 72 man 43.0131 141.27 man 60
19 │ c_19 38 man 34.4756 135.364 man 30
20 │ c_20 43 woman 34.4411 135.393 woman 40
StringとNumberが混在するAny型の配列をCategoricalArrayに変換しようとするとエラーがでます。
categorical([1,2,"3"])
ArgumentError: CategoricalArray only supports AbstractString, AbstractChar and Number element types (got element type Any)
カテゴリ値の組み合わせ
Q カテゴリ値の組み合わせ
年代と性別の組み合わせを使ってカテゴリ値を作るお題です。 string
関数を使って簡単に実装できます。
@chain customer_df begin
select(:age,:sex, [:age,:sex]=>ByRow((x,y)->string(floor(Int,x/10)*10, "_", y))=>:sex_and_age)
DataFrames.transform(:sex_and_age=>categorical=>:sex_and_age)
first(10)
println
end
10×3 DataFrame
Row │ age sex sex_and_age
│ Int64 String7 Cat…
─────┼─────────────────────────────
1 │ 41 man 40_man
2 │ 38 man 30_man
3 │ 49 woman 40_woman
4 │ 43 man 40_man
5 │ 31 man 30_man
6 │ 52 man 50_man
7 │ 50 man 50_man
8 │ 65 woman 60_woman
9 │ 36 woman 30_woman
10 │ 34 woman 30_woman
カテゴリ型の数値化
Q カテゴリ型の数値化
製造レコードから種別ごとに、平均障害率を計算するというお題です。 種別ごとに計算するのはgroupby
を使って実装できます。 自分自身のレコードを除かなければならないというのが少々曲者です。 今回は、ブロードキャスト演算子を使った無名関数で実装してみました。
production_url = "https://raw.githubusercontent.com/hanafsky/awesomebook/master/data/production.csv"
production_df =
@chain production_url begin
Downloads.download
CSV.File
DataFrame
groupby(:type)
transform(:fault_flg=>(c->(sum(c) .- c)/(length(c)-1))=>:fault_flg_per_type)
end
first(production_df,10) |> println
10×5 DataFrame
Row │ type length thickness fault_flg fault_flg_per_type
│ String1 Float64 Float64 Bool Float64
─────┼─────────────────────────────────────────────────────────────
1 │ E 274.027 40.2411 false 0.0612245
2 │ D 86.3193 16.9067 false 0.0327103
3 │ E 123.94 1.01846 false 0.0612245
4 │ B 175.555 16.4149 false 0.0344828
5 │ B 244.935 29.0611 false 0.0344828
6 │ B 226.427 39.7638 false 0.0344828
7 │ C 331.638 16.8356 false 0.0761905
8 │ A 200.865 12.1843 false 0.0547264
9 │ C 276.387 29.8996 false 0.0761905
10 │ E 168.441 1.26592 false 0.0612245
カテゴリ型の補完
Q KNNによる補完
まずデータを準備します。typeの列で欠損が発生しています。 欠損のないデータについてまずKNNで学習し、欠損のあるデータに適用するという流れです。 今回はjuliaにおけるScikitLearnのような存在であるMLJを利用してみたいと思います。[1]
いつも通り、データの読み込みを行いますが、KNNを適用する準備として、:type
の列はcategorical
型に変換します。 また、学習に利用するデータとして:length
、:thickness
を選んでおきます。
production_missc_url = "https://raw.githubusercontent.com/hanafsky/awesomebook/master/data/production_missing_category.csv"
production_missc_df =
@chain production_missc_url begin
Downloads.download
CSV.File
DataFrame
select(:type=>categorical=>:type,
:length,:thickness)
end
first(production_missc_df,10) |> println
10×3 DataFrame
Row │ type length thickness
│ Cat…? Float64 Float64
─────┼──────────────────────────────
1 │ E 274.027 40.2411
2 │ D 86.3193 16.9067
3 │ E 123.94 1.01846
4 │ B 175.555 16.4149
5 │ B 244.935 29.0611
6 │ B 226.427 39.7638
7 │ C 331.638 16.8356
8 │ A 200.865 12.1843
9 │ missing 276.387 29.8996
10 │ E 168.441 1.26592
さて、データを訓練データとテストデータに分け、KNNで学習するようにしてみます。 出力を補完値にするために``predict``関数ではなく、``predict_mode``関数を使いました。 それなりにAwesomeにかけたのではないでしょうか。
using MLJ
KNNClassifier = @load KNNClassifier pkg=NearestNeighborModels verbosity=0
train = dropmissing(production_missc_df)
test = filter(:type=>ismissing, production_missc_df)
y, X = unpack(train, ==(:type))
test[!,:type] = @chain machine(KNNClassifier(K=3), X, y) begin
fit!
predict_mode(test[!, Not(:type)])
end
first(test,10) |> println
10×3 DataFrame
Row │ type length thickness
│ Cat… Float64 Float64
─────┼───────────────────────────
1 │ E 276.387 29.8996
2 │ E 263.844 34.6643
3 │ E 129.365 21.3468
4 │ A 203.379 30.2865
5 │ E 157.463 11.1662
6 │ A 122.947 5.69413
7 │ D 123.976 11.1189
8 │ A 107.941 14.7408
9 │ A 189.611 1.41968
10 │ D 85.5865 16.9135
[1] | MLJについては、有名なパッケージではありますが、日本語での使い方をまとめた記事はあまりないので、そのうちにMLJのみを単独で取り上げてみたいと思います。 |
つづく