dplyrパッケージを条件付き変異に使用できますか?


178

ミューテーションが条件付きの場合(特定の列の値に応じて)、ミューテートを使用できますか?

この例は、私が何を意味するかを示すのに役立ちます。

structure(list(a = c(1, 3, 4, 6, 3, 2, 5, 1), b = c(1, 3, 4, 
2, 6, 7, 2, 6), c = c(6, 3, 6, 5, 3, 6, 5, 3), d = c(6, 2, 4, 
5, 3, 7, 2, 6), e = c(1, 2, 4, 5, 6, 7, 6, 3), f = c(2, 3, 4, 
2, 2, 7, 5, 2)), .Names = c("a", "b", "c", "d", "e", "f"), row.names = c(NA, 
8L), class = "data.frame")

  a b c d e f
1 1 1 6 6 1 2
2 3 3 3 2 2 3
3 4 4 6 4 4 4
4 6 2 5 5 5 2
5 3 6 3 3 6 2
6 2 7 6 7 7 7
7 5 2 5 2 6 5
8 1 6 3 6 3 2

新しい列gを作成するために、dplyrパッケージを使用して問題の解決策を見つけることを望んでいました(そうです、これは機能するはずのコードではありませんが、目的を明確にしていると思います)。

 library(dplyr)
 df <- mutate(df,
         if (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)){g = 2},
         if (a == 0 | a == 1 | a == 4 | a == 3 |  c == 4) {g = 3})

私が探しているコードの結果は、この特定の例では次の結果になるはずです。

  a b c d e f  g
1 1 1 6 6 1 2  3
2 3 3 3 2 2 3  3
3 4 4 6 4 4 4  3
4 6 2 5 5 5 2 NA
5 3 6 3 3 6 2 NA
6 2 7 6 7 7 7  2
7 5 2 5 2 6 5  2
8 1 6 3 6 3 2  3

誰もがdplyrでこれを行う方法についてのアイデアを持っていますか?このデータフレームはほんの一例であり、私が扱っているデータフレームははるかに大きいです。その速度のために、dplyrを使用しようとしましたが、おそらくこの問題を処理する他のより良い方法があるでしょうか?


2
はいしかしdplyr::case_when()よりもはるかに明確であるifelse
SMCI

回答:


216

使用する ifelse

df %>%
  mutate(g = ifelse(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4), 2,
               ifelse(a == 0 | a == 1 | a == 4 | a == 3 |  c == 4, 3, NA)))

添加- if_else:注dplyr 0.5であることをif_else代替置換するであろうように定義された関数ifelseではif_else、ただし、if_elseifelse(条件の両方のレッグが同じタイプでなければならない)よりも厳密であるため、NAその場合はをに置き換える必要があることに注意してくださいNA_real_

df %>%
  mutate(g = if_else(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4), 2,
               if_else(a == 0 | a == 1 | a == 4 | a == 3 |  c == 4, 3, NA_real_)))

追加-case_whenこの質問が投稿されたcase_whenため、dplyrが追加したため、別の代替案は次のようになります。

df %>% mutate(g = case_when(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4) ~ 2,
                            a == 0 | a == 1 | a == 4 | a == 3 |  c == 4 ~ 3,
                            TRUE ~ NA_real_))

追加-算術/ na_if 値が数値であり、条件(最後のNAのデフォルト値を除く)が相互に排他的である場合、質問の場合のように、各項が乗算されるような算術式を使用できます。na_if最後に0をNAに置き換えるために使用する望ましい結果。

df %>%
  mutate(g = 2 * (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)) +
             3 * (a == 0 | a == 1 | a == 4 | a == 3 |  c == 4),
         g = na_if(g, 0))

3
の代わりにNA、条件を満たさない行をそのままにしたい場合のロジックは何ですか?
Nazer

10
mutate(g = ifelse(condition1, 2, ifelse(condition2, 3, g))
G.グロタンディーク2017年

11
case_whenはすごく美しいです。実際にそこにあることを理解するには、すごく時間がかかりました。これは最も単純なdplyrチュートリアルにあると思います。データのサブセットのデータを計算する必要があることは非常に一般的ですが、それでもデータを完全に保ちたいと思っています。
ハビエルファハルド

55

あなたが問題を処理する他のより良い方法を求めるので、ここに使用する別の方法がありdata.tableます:

require(data.table) ## 1.9.2+
setDT(df)
df[a %in% c(0,1,3,4) | c == 4, g := 3L]
df[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]

条件文の順序がg正しくなるように逆になっていることに注意してください。g2番目の割り当て中であっても、作成されたコピーはありません- インプレースで置き換えられます。

大規模なデータでは、「はい」と「いいえ」の両方のケースを評価できるためネストされた を使用するよりもパフォーマンスが向上し、ネストによって IMHOの読み取り/保守が難しくなる場合があります。if-else


比較的大きなデータのベンチマークは次のとおりです。

# R version 3.1.0
require(data.table) ## 1.9.2
require(dplyr)
DT <- setDT(lapply(1:6, function(x) sample(7, 1e7, TRUE)))
setnames(DT, letters[1:6])
# > dim(DT) 
# [1] 10000000        6
DF <- as.data.frame(DT)

DT_fun <- function(DT) {
    DT[(a %in% c(0,1,3,4) | c == 4), g := 3L]
    DT[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]
}

DPLYR_fun <- function(DF) {
    mutate(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L, 
            ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}

BASE_fun <- function(DF) { # R v3.1.0
    transform(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L, 
            ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}

system.time(ans1 <- DT_fun(DT))
#   user  system elapsed 
#  2.659   0.420   3.107 

system.time(ans2 <- DPLYR_fun(DF))
#   user  system elapsed 
# 11.822   1.075  12.976 

system.time(ans3 <- BASE_fun(DF))
#   user  system elapsed 
# 11.676   1.530  13.319 

identical(as.data.frame(ans1), as.data.frame(ans2))
# [1] TRUE

identical(as.data.frame(ans1), as.data.frame(ans3))
# [1] TRUE

これがあなたが求めた代替案であるかどうかはわかりませんが、それが役に立てば幸いです。


4
素敵なコード!G. Grotendieckの答えは有効で短いので、それを私の質問の答えとして選びましたが、解決策をありがとうございます。きっとこうやってみます。
rdatasculptor 2014年

DT_funはその入力をインプレースで変更しているため、ベンチマークはかなり公平ではない可能性があります。2回目の反復から同じ入力を受信しないことに加えて(DT$gすでに割り当てられているため、タイミングに影響する可能性がありますか?)、結果も伝搬されてans1( Rのオプティマイザは、必要があると認める場合はどうなりますか?わからないこの上...)避けるため、別のことをコピーするDPLYR_funと、BASE_fun作成する必要がありますか?
ケンウィリアムズ

ただ明確data.tableにするために、このソリューションは素晴らしいと思います。data.tableテーブルの操作に本当にスピードが必要なところならどこでも使用し、C ++に行きたくありません。ただし、実際の変更には十分注意する必要があります。
ケンウィリアムズ

私はdata.tableからより整然としたものに慣れるようにしています。これは、data.tableが読みやすく、効率的であるというかなり一般的な使用例の1つです。私の語彙をもっと整頓したいと思う私の主な理由は、私自身や他の人にとっての読みやすさですが、この場合はdata.tableの方が勝っているようです。
ポールマクマーディ

38

dplyrにcase_when、ベクトル化されたifを提供する関数が追加されました。mosaic:::derivedFactor標準のdplyrの方法では変数にアクセスできず、NAのモードを宣言する必要があるため、構文は少し奇妙ですが、よりもかなり高速ですmosaic:::derivedFactor

df %>%
mutate(g = case_when(a %in% c(2,5,7) | (a==1 & b==4) ~ 2L, 
                     a %in% c(0,1,3,4) | c == 4 ~ 3L, 
                     TRUE~as.integer(NA)))

編集:dplyr::case_when()バージョン0.7.0より前のパッケージを使用している場合は、変数名の前に ' .$'を付ける必要があります(たとえば、.$a == 1内部に書き込むcase_when)。

ベンチマーク:ベンチマーク(Arunの投稿の関数を再利用)およびサンプルサイズの削減:

require(data.table) 
require(mosaic) 
require(dplyr)
require(microbenchmark)

set.seed(42) # To recreate the dataframe
DT <- setDT(lapply(1:6, function(x) sample(7, 10000, TRUE)))
setnames(DT, letters[1:6])
DF <- as.data.frame(DT)

DPLYR_case_when <- function(DF) {
  DF %>%
  mutate(g = case_when(a %in% c(2,5,7) | (a==1 & b==4) ~ 2L, 
                       a %in% c(0,1,3,4) | c==4 ~ 3L, 
                       TRUE~as.integer(NA)))
}

DT_fun <- function(DT) {
  DT[(a %in% c(0,1,3,4) | c == 4), g := 3L]
  DT[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]
}

DPLYR_fun <- function(DF) {
  mutate(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L, 
                    ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}

mosa_fun <- function(DF) {
  mutate(DF, g = derivedFactor(
    "2" = (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)),
    "3" = (a == 0 | a == 1 | a == 4 | a == 3 |  c == 4),
    .method = "first",
    .default = NA
  ))
}

perf_results <- microbenchmark(
  dt_fun <- DT_fun(copy(DT)),
  dplyr_ifelse <- DPLYR_fun(copy(DF)),
  dplyr_case_when <- DPLYR_case_when(copy(DF)),
  mosa <- mosa_fun(copy(DF)),
  times = 100L
)

これは与える:

print(perf_results)
Unit: milliseconds
           expr        min         lq       mean     median         uq        max neval
         dt_fun   1.391402    1.560751   1.658337   1.651201   1.716851   2.383801   100
   dplyr_ifelse   1.172601    1.230351   1.331538   1.294851   1.390351   1.995701   100
dplyr_case_when   1.648201    1.768002   1.860968   1.844101   1.958801   2.207001   100
           mosa 255.591301  281.158350 291.391586 286.549802 292.101601 545.880702   100

case_when:のように書くこともできたdf %>% mutate(g = with(., case_when(a %in% c(2,5,7) | (a==1 & b==4) ~ 2L, a %in% c(0,1,3,4) | c==4 ~ 3L, TRUE ~ NA_integer_)))
G.グロタンディーク

3
このベンチマークはマイクロ秒/ミリ秒/日ですか?このベンチマークは、測定ユニットが提供されないと意味がありません。また、1e6より小さいデータセットでのベンチマークは、スケーリングしないため、あまり意味がありません。
デビッドアレンブルク2017

3
Plsが答えを変更します.$。dplyrの新しいバージョンではもう必要ありません
Amit Kohli

14

パッケージのderivedFactor関数はmosaicこれを処理するように設計されているようです。この例を使用すると、次のようになります。

library(dplyr)
library(mosaic)
df <- mutate(df, g = derivedFactor(
     "2" = (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)),
     "3" = (a == 0 | a == 1 | a == 4 | a == 3 |  c == 4),
     .method = "first",
     .default = NA
     ))

(結果を因子ではなく数値にしたい場合derivedFactorは、as.numeric呼び出しをラップすることができます。)

derivedFactor 任意の数の条件文にも使用できます。


4
@hadleyは、これをdplyrのデフォルトの構文にする必要があります。ネストされた "ifelse"ステートメントが必要なのは、パッケージの他の部分で最も悪い部分です。これは主に、他の関数が非常に優れているためです
rsoren

.asFactor = Fオプションを使用するかderivedVariable、同じパッケージの(類似した)関数を使用して、結果が要因になるのを防ぐこともできます。
ジェイクフィッシャー

以下のように見えますrecodedplyr 0.5からこれを行います。私はまだそれを調査していません。blog.rstudio.org/2016/06/27/dplyr-0-5-0を
ジェイクフィッシャー

12

case_when 次の場合、SQLスタイルのケースのかなりクリーンな実装になります。

structure(list(a = c(1, 3, 4, 6, 3, 2, 5, 1), b = c(1, 3, 4, 
2, 6, 7, 2, 6), c = c(6, 3, 6, 5, 3, 6, 5, 3), d = c(6, 2, 4, 
5, 3, 7, 2, 6), e = c(1, 2, 4, 5, 6, 7, 6, 3), f = c(2, 3, 4, 
2, 2, 7, 5, 2)), .Names = c("a", "b", "c", "d", "e", "f"), row.names = c(NA, 
8L), class = "data.frame") -> df


df %>% 
    mutate( g = case_when(
                a == 2 | a == 5 | a == 7 | (a == 1 & b == 4 )     ~   2,
                a == 0 | a == 1 | a == 4 |  a == 3 | c == 4       ~   3
))

dplyr 0.7.4の使用

マニュアル:http : //dplyr.tidyverse.org/reference/case_when.html

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.