【R】複数列で1セットになっているデータをtidyrで整然(縦長)にする~pivot_longerの.valueを学ぶ
「割とよく遭遇するものの、知らないと意外に苦戦するデータ」の整形Tipsです。pivot_longer()
で値を複数列に入れたい時に使えます。
データは以下のようなもの。場面としては、「バーガー,サイドメニュー,ドリンクそれぞれ選べるセットメニューの注文レコード」を想定しています。
見てわかるように横長で気持ち悪いので整然データにしたいところですが、注文された「バーガーの種類と価格」「ポテトの種類と価格」「ドリンクの種類と価格」がそれぞれ2列で1セットになっているので、少し工夫が必要です。
data <- data.frame(number=c(1:3), order_burger=c("cheese","fish","chicken"),price_burger=c(500,450,300), order_side=c("potato","onion","potato"),price_side=c(100,150,100), order_drink=c("ginger","orange","apple"),price_drink=c(100,120,120)) kable(data)
number | order_burger | price_burger | order_side | price_side | order_drink | price_drink |
---|---|---|---|---|---|---|
1 | cheese | 500 | potato | 100 | ginger | 100 |
2 | fish | 450 | onion | 150 | orange | 120 |
3 | chicken | 300 | potato | 100 | apple | 120 |
方法0:そのままやると…?
data %>% mutate_if(is.numeric, as.character) %>% ##そのままだとエラーが出るのでデータ型統一 pivot_longer(-number, names_to = "setmenu", values_to = "order") %>% kable()
number | setmenu | order |
---|---|---|
1 | order_burger | cheese |
1 | price_burger | 500 |
1 | order_side | potato |
1 | price_side | 100 |
1 | order_drink | ginger |
1 | price_drink | 100 |
2 | order_burger | fish |
2 | price_burger | 450 |
2 | order_side | onion |
2 | price_side | 150 |
2 | order_drink | orange |
2 | price_drink | 120 |
3 | order_burger | chicken |
3 | price_burger | 300 |
3 | order_side | potato |
3 | price_side | 100 |
3 | order_drink | apple |
3 | price_drink | 120 |
違います!orderとpriceを別々の列にしたいんです!といってもR君はわかってくれません。
方法1:pivot_longe()の.valueを駆使
pivot_longer()
のnames_to
は複数の引数を取ることができ、さらに.value
という特殊な変数を渡すことで、セットになっている複数列をまとめることができます。実際に見てもらった方がわかりやすいでしょう。
以下のコードで、「"_"で切り分けた列名の前半部分(order,prise)を列名にした新たな列を作って値の格納先とし、後半部分(burger,side,drink)をsetmenuという列に格納せよ。」という指示になります。
data1<-data %>% pivot_longer(-number,names_to=c(".value","setmenu"),names_sep = "_") kable(data1)
number | setmenu | order | price |
---|---|---|---|
1 | burger | cheese | 500 |
1 | side | potato | 100 |
1 | drink | ginger | 100 |
2 | burger | fish | 450 |
2 | side | onion | 150 |
2 | drink | orange | 120 |
3 | burger | chicken | 300 |
3 | side | potato | 100 |
3 | drink | apple | 120 |
求めていたのはこれだよ、これ。
方法2:unite()とseparate()を使う
「.value
の使い方いまいちわかんねえ!」という人には少し冗長にはなりますがこんな方法もあります。
- 手順1:
unite()
で一列にまとめる
unite()
は列指定の方法としてselect()
と同じ方法が使えて便利です。
data2<-data %>% unite(col="burger",ends_with("burger")) %>% unite(col="side",ends_with("side")) %>% unite(col="drink",ends_with("drink")) kable(data2)
number | burger | side | drink |
---|---|---|---|
1 | cheese_500 | potato_100 | ginger_100 |
2 | fish_450 | onion_150 | orange_120 |
3 | chicken_300 | potato_100 | apple_120 |
- 手順2:
pivot_longer()
で縦長にする
先ほどと違い、1列にまとめているので簡単です。
names_to
で列名を入れる列の名前を指定し、values_to
で値を入れる列の名前を指定します。
data3<-data2 %>% pivot_longer(-number,names_to="setmenu",values_to="order_price") kable(data3)
number | setmenu | order_price |
---|---|---|
1 | burger | cheese_500 |
1 | side | potato_100 |
1 | drink | ginger_100 |
2 | burger | fish_450 |
2 | side | onion_150 |
2 | drink | orange_120 |
3 | burger | chicken_300 |
3 | side | potato_100 |
3 | drink | apple_120 |
- 手順3:
separate()
で戻す
data4<-data3 %>% separate(col=order_price,into=c("order","price")) kable(data4)
number | setmenu | order | price |
---|---|---|---|
1 | burger | cheese | 500 |
1 | side | potato | 100 |
1 | drink | ginger | 100 |
2 | burger | fish | 450 |
2 | side | onion | 150 |
2 | drink | orange | 120 |
3 | burger | chicken | 300 |
3 | side | potato | 100 |
3 | drink | apple | 120 |
まとめ
pivot_longer()
をうまく使えばunite()
やseparate()
などを使わずに1つの関数で完結させることができます。Hadleyさんは本当に痒い所に手が届く人です…