データフレームの操作(1).R
要約
・複数列に分割した値を一列にまとめる tidyr::pivot_longer()
・一列にまとめられた値を複数列に分散させる tidyr::pivot_wider()
使うデータフレーム
実際は上記で生成したデータを作り直しているので、ガチャ結果部分の乱数がは異なる。
separate()で複数列に分割された値を1列にまとめる
各試行の結果であるrarityを1つの列にまとめたい。
例えばstr_replace()で数値で表したrarityを文字列(N~UR)で表して見やすくする際に、行列の状態で適用すると各要素についての結果がリスト化されたベクタの形で返されてしまう。1列つまりベクタであればベクタで返してくれるので、そのままデータフレームにmutate()できて楽だ。
table()を使って各値の出現回数を出すときも、思った通りの2行の出力になってくれる。
このような形式を縦長(long)というらしいが、データフレームをlongにする関数が、tidyr::pivot_longer()だ。
tidyr::pivot_longer(df, cols, names_to = "(列名)", ..., values_to = "列名")
cols: 集められる値が入っていた列
names_to : 元の列がどこだったかを格納する列
values_to: 値が格納される列
names_prefix = "prefix": prefixをnameから除去する ex. "FY20" -> "20"
names_transform = list(names_toで指定した列名 = as.numeric)
values_drop_na = T: 値がNAの時はdtarop(除去)する
namesで指定された列に格納する際にstr_remove()みたいなことをしてみたり、変数型をas.なんとかで変化したり、NAは落としてみたりと、結構便利なオプションがある。
tidyr::pivot_longer()で縦長にする
動作の確認のため、まずは10連ガチャ結果データセット(gacha)を縦長にしてみる。
> gacha
set V1 V2 V3 V4 V5 V6 V7 V8 V9 V10
1 1 3 3 1 4 3 2 4 3 4 4
2 2 1 5 5 4 1 5 4 1 5 1
3 3 2 4 4 4 1 3 5 2 5 4
4 4 5 2 3 3 3 5 2 2 5 1
5 5 4 4 5 1 3 3 5 4 5 5
6 6 5 3 1 1 2 5 5 4 5 5
7 7 2 3 1 2 3 2 3 3 5 5
8 8 1 4 4 2 3 5 1 3 4 1
9 9 2 4 4 1 5 4 5 2 4 3
10 10 4 3 5 4 4 2 2 5 4 1
> gacha.l <- gacha %>% pivot_longer(cols = c(paste0("V", 1:10)), names_to = "time", values_to = "rarity")
> gacha.l
# A tibble: 100 x 3
set time rarity
<int> <chr> <int>
1 1 V1 3
2 1 V2 3
3 1 V3 1
4 1 V4 4
5 1 V5 3
6 1 V6 2
7 1 V7 4
8 1 V8 3
9 1 V9 4
10 1 V10 4
# ... with 90 more rows
colsはV1~V10まで手打ちするのがめんどくさかったので、paste0()で表現してみた。でも、よく考えたら「cols = -set」とすればset以外の列を指定できたんだった。しまった。
V1~V10にあった値がrarity列に格納され、どの列にあったかはtime列に格納されている。
ついでにデータ形式がtibbleになっているので、各列の変数型が明らかだし、最初の10行までしか表示されないようになっている。
縦長データを簡易集計してみる(table())
table()を使うことで、rarityの5段階数値(1~5)のそれぞれが何回ずつ出てきたかが集計できる。
これを、縦長にしたあとのgacha.l$rarityを入力した結果と、参考として元のgachaの値部分(gacha %>% select(-set))を入力した結果とで比べてみる。
> table(gacha.l$rarity)
1 2 3 4 5
16 15 19 25 25
> gacha %>% select(-set) %>% table()
, , V3 = 1, V4 = 1, V5 = 1, V6 = 2, V7 = 1, V8 = 1, V9 = 4, V10 = 1
V2
V1 2 3 4 5
1 0 0 0 0
2 0 0 0 0
3 0 0 0 0
4 0 0 0 0
5 0 0 0 0
(以下繰り返しのため省略)
そういうわけで、table()はベクタを入力しないと狙った結果は得られないことがよく分かった。
…のだが、行列を入れた場合の挙動はいったい何が起きているのかよくわからない。
tidyr::pivot_wider()で横長に戻す
元のデータセットgachaが横長(wide)の形式に相当する。
つまり、pivot_wider()とpivot_longer()で相互に変換可能ということだ。
tidyr::pivot_wider(data, names_from = name, ..., values_from = value,
id_cols = NULL, values_fill = NULL, values_fn = NULL)
names_from :指定列の値を使ってwideにしたときの格納先の列名を作る
values_from :wideにする値を指定
id_cols :names/values_fromの2つを指定していれば不要
values_fill :存在しない組み合わせのセルを埋める値を指定、空白だったり0だったり
values_fn :wideにした後のセルに複数の値が集まってしまう場合の数値処理、平均、etc.
id_colsの使い道はいまいちわからない。
values_fillは元のデータに欠損がある場合の処理だ。
キャラと装備の混在で出てくるガチャでキャラのレアリティだけをみたい場合は、装備を引いてしまったところは間引いたデータを作るだろうけれども、wideにした際にはその部分が欠損する。その部分の値を特定の値で埋めるかどうかがこの引数だ。
values_fnは元のデータに重複がある場合の処理だ。
そんなガチャはないと思うが、ある確率で1度に2枚引けることがあるとして、例えば3セット目の5回目の試行はSSRとRの2枚が出たとする(しない)。
えーっと、サンプルAの測定Bをn=5でやったときに、サンプル列と測定列までは作って何回目かの測定かは区別せずに測定値列に値をぶち込んである場合かなあ。A, B, xがxを変えながら5行並ぶので、names_from = 測定にすると、横に測定値がズラズラっと並んで、そこに格納されているのは平均値にしたい、みたいな時だがまずない。
そこはもう一列追加してn1~n5と書いておいてほしいのだけど、上から順に並んでいれば暗黙的に1~5回目の測定を示すものだろうし、測定順に意味はないからそもそも何回目かなんか関係ないと思っている技術者はいっぱいいるし、いわんや総務(略)……というのはさておき、測定値に測定順依存性があるケースはそれなりに見かけるから、みんな気をつけようね。
> gacha.l %>% pivot_wider(names_from = time, values_from = rarity)
# A tibble: 10 x 11
set V1 V2 V3 V4 V5 V6 V7 V8 V9 V10
<int> <int> <int> <int> <int> <int> <int> <int> <int> <int> <int>
1 1 3 3 1 4 3 2 4 3 4 4
2 2 1 5 5 4 1 5 4 1 5 1
3 3 2 4 4 4 1 3 5 2 5 4
4 4 5 2 3 3 3 5 2 2 5 1
5 5 4 4 5 1 3 3 5 4 5 5
6 6 5 3 1 1 2 5 5 4 5 5
7 7 2 3 1 2 3 2 3 3 5 5
8 8 1 4 4 2 3 5 1 3 4 1
9 9 2 4 4 1 5 4 5 2 4 3
10 10 4 3 5 4 4 2 2 5 4 1
ということで適用してみると、当然ながら元のデータであるgachaに戻る。ここでもデータ形式はtibbleになる。だからどうってことは(そんなに)ないのだが。
このようにして、縦長(long) <--> 横長(wide) の変換は相互に可能だし、2つの関数は引数がわかりやすく作られているのでセットで覚えると便利そうだ。
参考(いつもお世話になっております)
今日はここまで。