DOING THINGS RIGHT

彩りのある生産的な日々を。

【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さんは本当に痒い所に手が届く人です…