この2つの関数はとくに違いが無さそうですが、負の数に適用する場合、注意が必要です。
round(Int, -2.1),floor(Int, -2.1)
(-2, -3)
juliaで前処理大全その7です。今回は数値型を取り扱います。
をさまざまな数値のデータ型に変換するというお題です。
typeof(40000/3)
Float64
デフォルトの型を確認するとFloat64
であることがわかります。
整数型に変換するにはround
関数、あるいはfloor
関数を用います。
round(Int, 40000/3)
13333
floor(Int, 40000/3)
13333
この2つの関数はとくに違いが無さそうですが、負の数に適用する場合、注意が必要です。
round(Int, -2.1),floor(Int, -2.1)
(-2, -3)
のような対数変換を行うと値が大きくなるほど値の差の意味をなくす効果があります。 また、回帰分析などを行うときに目的変数が負の値をとるとまずい場合に、対数変換を適用することがあります。 この場合、対数スケールで見たときに誤差の分散が等しい仮定をすることになりますが、細かく意識している人っているのか偶に疑問を抱きます。
ホテルの予約レコードのtotal_priceを1000で割って1を足して、底10で対数化するというお題です。
transform関数とByRowと無名関数を使って難なく実現できます。
using DataFrames,CSV,Chain,Downloads
reserve_url = "https://raw.githubusercontent.com/hanafsky/awesomebook/master/data/reserve.csv"
reserve_df = @chain reserve_url begin
Downloads.download
CSV.File
DataFrame
transform(:total_price=>ByRow(c->log10(c/1000 + 1))=>:total_price_log)
end
first(reserve_df,10) |> println
10×10 DataFrame
Row │ reserve_id hotel_id customer_id reserve_datetime checkin_date checkin_time checkout_date people_num total_price total_price_log
│ String7 String7 String7 String31 Dates.Date Dates.Time Dates.Date Int64 Int64 Float64
─────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ r1 h_75 c_1 2016-03-06 13:09:42 2016-03-26 10:00:00 2016-03-29 4 97200 1.99211
2 │ r2 h_219 c_1 2016-07-16 23:39:55 2016-07-20 11:30:00 2016-07-21 2 20600 1.33445
3 │ r3 h_179 c_1 2016-09-24 10:03:17 2016-10-19 09:00:00 2016-10-22 2 33600 1.53908
4 │ r4 h_214 c_1 2017-03-08 03:20:10 2017-03-29 11:00:00 2017-03-30 4 194400 2.29092
5 │ r5 h_16 c_1 2017-09-05 19:50:37 2017-09-22 10:30:00 2017-09-23 3 68100 1.83948
6 │ r6 h_241 c_1 2017-11-27 18:47:05 2017-12-04 12:00:00 2017-12-06 3 36000 1.5682
7 │ r7 h_256 c_1 2017-12-29 10:38:36 2018-01-25 10:30:00 2018-01-28 1 103500 2.01912
8 │ r8 h_241 c_1 2018-05-26 08:42:51 2018-06-08 10:00:00 2018-06-09 1 6000 0.845098
9 │ r9 h_217 c_2 2016-03-05 13:31:06 2016-03-25 09:30:00 2016-03-27 3 68400 1.84136
10 │ r10 h_240 c_2 2016-06-25 09:12:22 2016-07-14 11:00:00 2016-07-17 4 320400 2.50705
ホテルの予約レコードの顧客テーブルの年齢を10刻みのカテゴリー型として追加するというお題です。 行ごとの処理と列全体を分けて行っているので、本のpythonコードなどと比べるとやや冗長な書き方になっています。
using CategoricalArrays
customer_url = "https://raw.githubusercontent.com/hanafsky/awesomebook/master/data/customer.csv"
customer_df = @chain customer_url Downloads.download CSV.File DataFrame
transform!(customer_df, :age=>ByRow(c->10floor(Int,c/10))=>:age_rank)
transform!(customer_df, :age_rank=>categorical=>:age_rank)
first(customer_df,10) |> println
10×6 DataFrame
Row │ customer_id age sex home_latitude home_longitude age_rank
│ String7 Int64 String7 Float64 Float64 Cat…
─────┼──────────────────────────────────────────────────────────────────────
1 │ c_1 41 man 35.0922 136.512 40
2 │ c_2 38 man 35.3251 139.411 30
3 │ c_3 49 woman 35.1205 136.511 40
4 │ c_4 43 man 43.0349 141.24 40
5 │ c_5 31 man 35.1027 136.524 30
6 │ c_6 52 man 34.4408 135.39 50
7 │ c_7 50 man 43.0158 141.231 50
8 │ c_8 65 woman 38.2013 140.466 60
9 │ c_9 36 woman 33.3228 130.331 30
10 │ c_10 34 woman 34.2904 132.303 30
正規化処理にもいくつか種類があります。
中心化: 平均値が0になるようにする。
標準化: 平均値が0、かつ標準偏差が1になるようにする。
ホテルの予約レコードの予約人数と合計金額を平均0、標準偏差1になるように正規化するというお題です。 この正規化にはStatsBase.jl
のzscore
が使えます。
using StatsBase
transform!(reserve_df,:people_num=>zscore, :total_price=>zscore)
first(reserve_df,10) |> println
10×12 DataFrame
Row │ reserve_id hotel_id customer_id reserve_datetime checkin_date checkin_time checkout_date people_num total_price total_price_log people_num_zscore total_price_zscore
│ String7 String7 String7 String31 Dates.Date Dates.Time Dates.Date Int64 Int64 Float64 Float64 Float64
─────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ r1 h_75 c_1 2016-03-06 13:09:42 2016-03-26 10:00:00 2016-03-29 4 97200 1.99211 1.30055 -0.0531874
2 │ r2 h_219 c_1 2016-07-16 23:39:55 2016-07-20 11:30:00 2016-07-21 2 20600 1.33445 -0.483693 -0.74773
3 │ r3 h_179 c_1 2016-09-24 10:03:17 2016-10-19 09:00:00 2016-10-22 2 33600 1.53908 -0.483693 -0.629857
4 │ r4 h_214 c_1 2017-03-08 03:20:10 2017-03-29 11:00:00 2017-03-30 4 194400 2.29092 1.30055 0.828138
5 │ r5 h_16 c_1 2017-09-05 19:50:37 2017-09-22 10:30:00 2017-09-23 3 68100 1.83948 0.408427 -0.317041
6 │ r6 h_241 c_1 2017-11-27 18:47:05 2017-12-04 12:00:00 2017-12-06 3 36000 1.5682 0.408427 -0.608096
7 │ r7 h_256 c_1 2017-12-29 10:38:36 2018-01-25 10:30:00 2018-01-28 1 103500 2.01912 -1.37581 0.00393554
8 │ r8 h_241 c_1 2018-05-26 08:42:51 2018-06-08 10:00:00 2018-06-09 1 6000 0.845098 -1.37581 -0.88011
9 │ r9 h_217 c_2 2016-03-05 13:31:06 2016-03-25 09:30:00 2016-03-27 3 68400 1.84136 0.408427 -0.314321
10 │ r10 h_240 c_2 2016-06-25 09:12:22 2016-07-14 11:00:00 2016-07-17 4 320400 2.50705 1.30055 1.9706
なお、zscoreでは標本標準偏差が適用されることに意識しておく必要があります。 また、
全ての列に適用するときはmapcolsあるいはmapcols!が使えます。
mapcols(zscore,df)
また、最小値0、最大値1でスケーリングするにはStatsBase.jlにUnitRangeTransormが用意されています。
standardize(UnitRangeTransform, [0.0 -0.5 0.5; 0.0 1.0 2.0], dims=2)
2×3 Matrix{Float64}:
0.5 0.0 1.0
0.0 0.5 1.0
ただ、DataFrameには適用できないようなので、もしやりたいなら次のように適当な関数を作って適用したほうがいいでしょう。
unitrangetransform(v)=(v-minimum(v))/(maximum(v)-minimum(v))
transform(df, :colname=>unitrangetransform)
標準化後に絶対値が3を超えないデータだけを採用します。
zscoreを適用した列に対して、絶対値が3以下のデータをフィルターしています。
reserve_df2 = @chain reserve_df begin
transform(:total_price=>zscore)
filter(:total_price_zscore=>(c->abs(c)≤3),_)
end
first(reserve_df2,10) |> println
10×12 DataFrame
Row │ reserve_id hotel_id customer_id reserve_datetime checkin_date checkin_time checkout_date people_num total_price total_price_log people_num_zscore total_price_zscore
│ String7 String7 String7 String31 Dates.Date Dates.Time Dates.Date Int64 Int64 Float64 Float64 Float64
─────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ r1 h_75 c_1 2016-03-06 13:09:42 2016-03-26 10:00:00 2016-03-29 4 97200 1.99211 1.30055 -0.0531874
2 │ r2 h_219 c_1 2016-07-16 23:39:55 2016-07-20 11:30:00 2016-07-21 2 20600 1.33445 -0.483693 -0.74773
3 │ r3 h_179 c_1 2016-09-24 10:03:17 2016-10-19 09:00:00 2016-10-22 2 33600 1.53908 -0.483693 -0.629857
4 │ r4 h_214 c_1 2017-03-08 03:20:10 2017-03-29 11:00:00 2017-03-30 4 194400 2.29092 1.30055 0.828138
5 │ r5 h_16 c_1 2017-09-05 19:50:37 2017-09-22 10:30:00 2017-09-23 3 68100 1.83948 0.408427 -0.317041
6 │ r6 h_241 c_1 2017-11-27 18:47:05 2017-12-04 12:00:00 2017-12-06 3 36000 1.5682 0.408427 -0.608096
7 │ r7 h_256 c_1 2017-12-29 10:38:36 2018-01-25 10:30:00 2018-01-28 1 103500 2.01912 -1.37581 0.00393554
8 │ r8 h_241 c_1 2018-05-26 08:42:51 2018-06-08 10:00:00 2018-06-09 1 6000 0.845098 -1.37581 -0.88011
9 │ r9 h_217 c_2 2016-03-05 13:31:06 2016-03-25 09:30:00 2016-03-27 3 68400 1.84136 0.408427 -0.314321
10 │ r10 h_240 c_2 2016-06-25 09:12:22 2016-07-14 11:00:00 2016-07-17 4 320400 2.50705 1.30055 1.9706
主成分分析によって、データの次元を圧縮するお題です。
PCAはMultivariateStats.jlで実装されています。 注意点は、データ行列を転置して渡す必要があることです。
production_url = "https://raw.githubusercontent.com/hanafsky/awesomebook/master/data/production.csv"
production_df = @chain production_url Downloads.download CSV.File DataFrame
import MultivariateStats
X = @chain production_df begin
select(:length,:thickness)
Matrix
transpose
end
M = MultivariateStats.fit(MultivariateStats.PCA, X)
MultivariateStats.predict(M, X)
この節は欠損値の取り扱いに関してまとめています。 欠損にはいくつか種類があるようです。
MCAR: 完全にランダムな欠損
MAR: ほかの項目に依存した欠損
MNAR: 欠損したデータに依存した欠損
一番簡単な対処方法は欠損値を削除してしまうことです。 その他の対処方法としては、定数による補完、集計値(平均値・中央値など)による補完、欠損していないデータに基づく予測値によって補完、時系列の関係から補完などがあるようです。
対象レコードはthicknessに欠損が存在する製造レコードです。 まずデータを読み込む際に、欠損値を表す文字列をしているようにキーワード引数を指定します。 欠損値の除去はdropmissing
関数で簡単に行えます。
production_missing_url = "https://raw.githubusercontent.com/hanafsky/awesomebook/master/data/production_missing_num.csv"
production_missing_df = @chain production_missing_url begin
Downloads.download
CSV.File(;missingstring="None")
DataFrame
end
@chain dropmissing(production_missing_df) first(10) println
10×4 DataFrame
Row │ type length thickness fault_flg
│ String1 Float64 Float64 Bool
─────┼─────────────────────────────────────────
1 │ E 274.027 40.2411 false
2 │ D 86.3193 16.9067 false
3 │ E 123.94 1.01846 false
4 │ B 175.555 16.4149 false
5 │ B 244.935 29.0611 false
6 │ B 226.427 39.7638 false
7 │ C 331.638 16.8356 false
8 │ A 200.865 12.1843 false
9 │ C 276.387 29.8996 false
10 │ E 168.441 1.26592 false
欠損値を1に置き換えます。 missingを置き換えるには、SQLの例と同じくcoalesce
が利用可能です。
@chain production_missing_df begin
transform(:thickness=>ByRow(c->coalesce(c,1))=>:thickness)
first(30)
println
end
30×4 DataFrame
Row │ type length thickness fault_flg
│ String1 Float64 Real Bool
─────┼─────────────────────────────────────────
1 │ E 274.027 40.2411 false
2 │ D 86.3193 16.9067 false
3 │ E 123.94 1.01846 false
4 │ B 175.555 16.4149 false
5 │ B 244.935 29.0611 false
6 │ B 226.427 39.7638 false
7 │ C 331.638 16.8356 false
8 │ A 200.865 12.1843 false
9 │ C 276.387 29.8996 false
10 │ E 168.441 1.26592 false
11 │ D 218.139 33.1354 false
12 │ E 215.179 41.7599 false
13 │ D 218.038 11.8363 false
14 │ E 218.31 39.5786 false
15 │ D 207.823 38.7026 false
16 │ E 286.537 5.3755 false
17 │ C 239.284 18.9418 false
18 │ C 373.655 11.2548 false
19 │ E 200.112 17.9743 false
20 │ C 326.175 44.3079 false
21 │ B 157.573 0.409495 false
22 │ B 150.217 8.68243 false
23 │ C 358.605 25.7004 false
24 │ C 269.006 16.9575 false
25 │ B 202.549 1 false
26 │ D 223.806 19.6508 false
27 │ B 263.844 34.6643 false
28 │ B 169.691 1 false
29 │ A 131.849 4.7216 true
30 │ E 128.99 16.5232 false
欠損値に平均値を代入するお題です。 平均をmean
関数で計算するにはビルトインモジュールのStatistics.jl
をインポートする必要があります。 しかし、欠損値を含んだままで平均を計算すると、計算結果もmissing
になってしまいます。 各種統計量をまとめて計算可能なdescribe
関数を利用すれば、欠損値があっても平均値を計算可能なので、これを利用することにします。
平均値を計算した後は、定数補完と同様の処理が可能です。 今回はChain.jl
の@aside
マクロを使って、平均値の計算をパイプライン処理の中に入れてました。
@chain production_missing_df begin
@aside M = describe(_, :mean,cols=:thickness) # 平均値を計算
@aside thickness_mean = M[!,:mean][1] # データフレームから平均値を取り出す
transform(:thickness=>ByRow(c->coalesce(c,thickness_mean))=>:thickness) # 欠損値を平均値で置き換え
first(30)
println
end
30×4 DataFrame
Row │ type length thickness fault_flg
│ String1 Float64 Float64 Bool
─────┼─────────────────────────────────────────
1 │ E 274.027 40.2411 false
2 │ D 86.3193 16.9067 false
3 │ E 123.94 1.01846 false
4 │ B 175.555 16.4149 false
5 │ B 244.935 29.0611 false
6 │ B 226.427 39.7638 false
7 │ C 331.638 16.8356 false
8 │ A 200.865 12.1843 false
9 │ C 276.387 29.8996 false
10 │ E 168.441 1.26592 false
11 │ D 218.139 33.1354 false
12 │ E 215.179 41.7599 false
13 │ D 218.038 11.8363 false
14 │ E 218.31 39.5786 false
15 │ D 207.823 38.7026 false
16 │ E 286.537 5.3755 false
17 │ C 239.284 18.9418 false
18 │ C 373.655 11.2548 false
19 │ E 200.112 17.9743 false
20 │ C 326.175 44.3079 false
21 │ B 157.573 0.409495 false
22 │ B 150.217 8.68243 false
23 │ C 358.605 25.7004 false
24 │ C 269.006 16.9575 false
25 │ B 202.549 19.4704 false
26 │ D 223.806 19.6508 false
27 │ B 263.844 34.6643 false
28 │ B 169.691 19.4704 false
29 │ A 131.849 4.7216 true
30 │ E 128.99 16.5232 false
公式パッケージには次の多重代入法に関連するImpute.jl
というパッケージがあって、こちらを使うともう少しエレガントに処理が可能です。
using Impute
@chain production_missing_df begin
Impute.substitute(;statistic=Impute.mean)
end
PMM(Predictive Mean Matching)は、残念ながらImpute.jl
では実装されていないようです。[1] (逆に実装するチャンスともいえます。) おとなしく、RCallやPyCallで処理を呼び出すか、Impute.jl
の別の補完法を使うのが良さそうです。
[1] | 他のパッケージもあるにはあるのですが、RCallでwrapしたもののようです。 |
つづく