dplyr bermutasi dengan nilai bersyarat

88

Dalam kerangka data besar ("myfile") dengan empat kolom saya harus menambahkan kolom kelima dengan nilai bersyarat berdasarkan empat kolom pertama.

Lebih suka jawaban dengan dplyrdan mutate, terutama karena kecepatannya dalam kumpulan data besar.

Dataframe saya terlihat seperti ini:

  V1 V2 V3 V4
1  1  2  3  5
2  2  4  4  1
3  1  4  1  1
4  4  5  1  3
5  5  5  5  4
...

Nilai kolom kelima (V5) didasarkan pada beberapa aturan bersyarat:

if (V1==1 & V2!=4) {
  V5 <- 1
} else if (V2==4 & V3!=1) {
  V5 <- 2
} else {
  V5 <- 0
}

Sekarang saya ingin menggunakan mutatefungsi untuk menggunakan aturan ini di semua baris (untuk menghindari loop lambat). Sesuatu seperti ini (dan ya, saya tahu itu tidak berfungsi seperti ini!):

myfile <- mutate(myfile, if (V1==1 & V2!=4){V5 = 1}
    else if (V2==4 & V3!=1){V5 = 2}
    else {V5 = 0})

Hasilnya akan seperti ini:

  V1 V2 V3 V4 V5
1  1  2  3  5  1
2  2  4  4  1  2
3  1  4  1  1  0
4  4  5  1  3  0
5  5  5  5  4  0

Bagaimana melakukan ini dplyr?

rdatasculptor.dll
sumber
Berguna untuk menyatakan jika V1..4 semuanya integer (bukan faktor, logika, string atau float)? dan apakah Anda peduli tentang penanganan yang benar NA, ( NaN, +Inf, -Inf)?
smci
Jika kecepatan tampaknya menjadi masalah untuk disukai dplyr, maka saya akan lebih baik menggunakan data.table.
Valentin

Jawaban:

108

Coba ini:

myfile %>% mutate(V5 = (V1 == 1 & V2 != 4) + 2 * (V2 == 4 & V3 != 1))

memberi:

  V1 V2 V3 V4 V5
1  1  2  3  5  1
2  2  4  4  1  2
3  1  4  1  1  0
4  4  5  1  3  0
5  5  5  5  4  0

atau ini:

myfile %>% mutate(V5 = ifelse(V1 == 1 & V2 != 4, 1, ifelse(V2 == 4 & V3 != 1, 2, 0)))

memberi:

  V1 V2 V3 V4 V5
1  1  2  3  5  1
2  2  4  4  1  2
3  1  4  1  1  0
4  4  5  1  3  0
5  5  5  5  4  0

Catatan

Sarankan Anda mendapatkan nama yang lebih baik untuk bingkai data Anda. myfile membuatnya tampak seolah-olah menyimpan nama file.

Di atas digunakan masukan ini:

myfile <- 
structure(list(V1 = c(1L, 2L, 1L, 4L, 5L), V2 = c(2L, 4L, 4L, 
5L, 5L), V3 = c(3L, 4L, 1L, 1L, 5L), V4 = c(5L, 1L, 1L, 3L, 4L
)), .Names = c("V1", "V2", "V3", "V4"), class = "data.frame", row.names = c("1", 
"2", "3", "4", "5"))

Perbarui 1 Sejak awalnya diposting dplyr telah berubah %.%menjadi%>% jawaban sehingga telah diubah sesuai.

Perbarui 2 dplyr sekarang case_whenyang memberikan solusi lain:

myfile %>% 
       mutate(V5 = case_when(V1 == 1 & V2 != 4 ~ 1, 
                             V2 == 4 & V3 != 1 ~ 2,
                             TRUE ~ 0))
G. Grothendieck
sumber
Saya mencoba solusi kedua Anda. Saya mendapat kesalahan ini: Kesalahan dalam mutate_impl (.data, names_dots (...), environment ()): REAL () hanya dapat diterapkan ke 'numerik', bukan 'logis' Apakah Anda tahu apa yang salah?
rdatasculptor
5
Saya menemukan cara yang memungkinkan Anda untuk tidak mengumpulkan ifelsepernyataan:myfile %>% mutate(V5 = ifelse(V1 == 1 & V2 != 4, 1, 0), V5 = ifelse(V2 == 4 & V3 != 1, 2, V5))
Alex
32

Dengan dplyr 0.7.2, Anda dapat menggunakan fungsi yang sangat berguna case_when:

x=read.table(
 text="V1 V2 V3 V4
 1  1  2  3  5
 2  2  4  4  1
 3  1  4  1  1
 4  4  5  1  3
 5  5  5  5  4")
x$V5 = case_when(x$V1==1 & x$V2!=4 ~ 1,
                 x$V2==4 & x$V3!=1 ~ 2,
                 TRUE ~ 0)

Dinyatakan dengan dplyr::mutate, itu memberi:

x = x %>% mutate(
     V5 = case_when(
         V1==1 & V2!=4 ~ 1,
         V2==4 & V3!=1 ~ 2,
         TRUE ~ 0
     )
)

Harap dicatat bahwa NAtidak diperlakukan secara khusus, karena dapat menyesatkan. Fungsi akan kembali NAhanya jika tidak ada kondisi yang cocok. Jika Anda meletakkan baris dengan TRUE ~ ..., seperti yang saya lakukan dalam contoh saya, nilai kembaliannya tidak akan pernahNA .

Oleh karena itu, Anda harus secara ekspresif memberi tahu case_whenuntuk meletakkannya NAdengan menambahkan pernyataan suka is.na(x$V1) | is.na(x$V3) ~ NA_integer_. Petunjuk: dplyr::coalesce()terkadang fungsi ini bisa sangat berguna di sini!

Selain itu, perlu diketahui bahwa NAsaja akan biasanya tidak bekerja, Anda harus meletakkan khusus NAnilai-nilai: NA_integer_, NA_character_atau NA_real_.

Dan Chaltiel
sumber
1
Ini secara signifikan lebih cepat daripada faktor turunan.
Fato39
12

Sepertinya derivedFactordari mosaicpaket dirancang untuk ini. Dalam contoh ini, akan terlihat seperti ini:

library(mosaic)
myfile <- mutate(myfile, V5 = derivedFactor(
    "1" = (V1==1 & V2!=4),
    "2" = (V2==4 & V3!=1),
    .method = "first",
    .default = 0
    ))

(Jika Anda ingin hasilnya berupa numerik, bukan faktor, bungkus derivedFactordengan as.numeric.)

Perhatikan bahwa .defaultopsi tersebut digabungkan dengan .method = "first"kumpulan kondisi "lain" - pendekatan ini dijelaskan dalam file bantuan untuk derivedFactor.

Jake Fisher
sumber
Anda juga dapat mencegah hasil menjadi faktor menggunakan .asFactor = Fopsi atau dengan menggunakan fungsi (serupa) derivedVariabledalam paket yang sama.
Jake Fisher
Sepertinya recodedari dplyr 0.5 akan melakukan ini. Saya belum menyelidikinya. Lihat blog.rstudio.org/2016/06/27/dplyr-0-5-0
Jake Fisher
Ini lambat untuk data saya dengan 1e6 baris.
Fato39
3
@ Fato39 Ya, fungsi mosaic::derivedFactorkeluarga sangat lambat. Jika Anda mengetahui alasannya, harap jawab pertanyaan SO saya tentang hal itu: stackoverflow.com/questions/33787691/… . Saya senang melihat dari komentar Anda yang lain yang dplyr::case_whenlebih cepat - saya harus beralih ke itu.
Jake Fisher
Saya mencoba perintah berikut, perpustakaan (mosaik) VENEZ.FINAL2 <- mutate (VENEZ, SEX = turunanFactor ("M" = (CATEGORY == "BULL" & CATEGORY! = "SIRE"), "F" = ( CATEGORY == "COW" & CATEGORY! = "HEIFER"), .method = "first", .default = "NA")) tetapi tidak berhasil, cukup selesaikan kondisi VENEZ.FINAL2 <- mutate (VENEZ, SEX = turunanFactor ("M" = (CATEGORY == "BULL Bisakah Anda membantu saya? Terima kasih banyak!
Johanna Ramirez