วิธีการป้องกัน ifelse () จากการเปลี่ยนวัตถุวันที่เป็นวัตถุที่เป็นตัวเลข


161

ฉันใช้ฟังก์ชันนี้ifelse()เพื่อจัดการกับเวกเตอร์วันที่ ฉันคาดหวังว่าผลการเรียนจะดีDateมากและรู้สึกประหลาดใจที่ได้รับnumericเวกเตอร์แทน นี่คือตัวอย่าง:

dates <- as.Date(c('2011-01-01', '2011-01-02', '2011-01-03', '2011-01-04', '2011-01-05'))
dates <- ifelse(dates == '2011-01-01', dates - 1, dates)
str(dates)

สิ่งนี้น่าประหลาดใจอย่างยิ่งเพราะการดำเนินการข้ามเวกเตอร์ทั้งหมดส่งคืนDateวัตถุ

dates <- as.Date(c('2011-01-01', '2011-01-02', '2011-01-03', '2011-01-04','2011-01-05'))
dates <- dates - 1
str(dates)

ฉันควรจะใช้ฟังก์ชั่นอื่นเพื่อทำงานกับDateเวกเตอร์หรือไม่? ถ้าเป็นเช่นนั้นฟังก์ชั่นอะไร? ถ้าไม่ฉันifelseจะบังคับให้ส่งคืนเวกเตอร์ประเภทเดียวกันกับอินพุตได้อย่างไร

หน้าความช่วยเหลือสำหรับifelseระบุว่านี่เป็นคุณลักษณะไม่ใช่ข้อผิดพลาด แต่ฉันยังคงดิ้นรนเพื่อหาคำอธิบายสำหรับสิ่งที่ฉันพบว่าเป็นพฤติกรรมที่น่าแปลกใจ


4
ขณะนี้มีฟังก์ชั่นif_else()ในแพ็คเกจ dplyr ที่สามารถใช้แทนifelseในขณะที่รักษาคลาสของวัตถุ Date ได้อย่างถูกต้องซึ่งโพสต์ด้านล่างนี้เป็นคำตอบล่าสุด ฉันให้ความสนใจกับมันที่นี่เพราะมันแก้ปัญหานี้ได้โดยจัดให้มีฟังก์ชั่นที่ผ่านการทดสอบหน่วยและจัดทำเอกสารในแพ็คเกจ CRAN ซึ่งแตกต่างจากคำตอบอื่น ๆ อีกมากมายที่อยู่ในอันดับต้น ๆ
Sam Firke

คำตอบ:


132

คุณอาจจะใช้data.table::fifelse( data.table >= 1.12.3) dplyr::if_elseหรือ


data.table::fifelse

ซึ่งแตกต่างจากifelse, fifelseแยมประเภทและระดับของปัจจัยการผลิต

library(data.table)
dates <- fifelse(dates == '2011-01-01', dates - 1, dates)
str(dates)
# Date[1:5], format: "2010-12-31" "2011-01-02" "2011-01-03" "2011-01-04" "2011-01-05"

dplyr::if_else

จากdplyr 0.5.0บันทึกประจำรุ่น :

[ if_else] มีความหมายที่เข้มงวดกว่าifelse(): trueและfalseอาร์กิวเมนต์ต้องเป็นประเภทเดียวกัน สิ่งนี้จะให้ผลตอบแทนที่น่าประหลาดใจน้อยกว่าและเก็บรักษาเวกเตอร์ S3 เช่นวันที่ "

library(dplyr)
dates <- if_else(dates == '2011-01-01', dates - 1, dates)
str(dates)
# Date[1:5], format: "2010-12-31" "2011-01-02" "2011-01-03" "2011-01-04" "2011-01-05" 

2
มีประโยชน์แน่นอนแม้ว่ามันจะทำให้ฉันหลุดเครื่องหมาย หน้าความช่วยเหลือรุ่นปัจจุบันไม่ได้กล่าวถึงสิ่งที่คาดหวังจากการขัดแย้งปัจจัย คะแนนของฉันจะเป็นวัตถุปัจจัยผลตอบแทนที่มีระดับที่มีสหภาพของระดับของtrue'และfalse' s ระดับ
IRTFM

3
มีวิธีที่จะมีหนึ่งในข้อโต้แย้งของการif_elseเป็น NA หรือไม่? ฉันได้ลองNA_ตัวเลือกเชิงตรรกะและไม่มีอะไรติดและฉันไม่เชื่อว่ามีNA_double_
roarkz

11
@Zak หนึ่งเป็นไปได้คือการห่อในNA as.Date
Henrik

นั่นคือNA_real_@roarkz และ @Henrik ความคิดเห็นของคุณที่นี่แก้ไขปัญหาของฉันได้
BLT

63

มันเกี่ยวข้องกับค่าเอกสารของifelse:

เวกเตอร์ของระยะเวลาเดียวกันและคุณลักษณะ (รวมทั้งมิติและ " class") ในฐานะtestและค่าข้อมูลจากค่าของหรือyes noโหมดของคำตอบที่จะได้รับการข่มขู่จากตรรกะที่จะรองรับค่าแรก ๆ ที่นำมาจากyesแล้วค่าใด ๆ noที่นำมาจาก

ต้มลงไปในความหมายของมันifelseทำให้ปัจจัยสูญเสียระดับของพวกเขาและวันที่สูญเสียระดับของพวกเขาและมีเพียงโหมดของพวกเขา ("ตัวเลข") จะถูกเรียกคืน ลองใช้สิ่งนี้แทน:

dates[dates == '2011-01-01'] <- dates[dates == '2011-01-01'] - 1
str(dates)
# Date[1:5], format: "2010-12-31" "2011-01-02" "2011-01-03" "2011-01-04" "2011-01-05"

คุณสามารถสร้างsafe.ifelse:

safe.ifelse <- function(cond, yes, no){ class.y <- class(yes)
                                  X <- ifelse(cond, yes, no)
                                  class(X) <- class.y; return(X)}

safe.ifelse(dates == '2011-01-01', dates - 1, dates)
# [1] "2010-12-31" "2011-01-02" "2011-01-03" "2011-01-04" "2011-01-05"

หมายเหตุในภายหลัง: ฉันเห็นว่า Hadley ได้สร้างif_elseสิ่งที่ซับซ้อน magrittr / dplyr / tidyr ที่ซับซ้อนของแพ็คเกจการสร้างข้อมูล


37
ค่อนข้างหรูหรากว่ารุ่นอื่น ๆ :safe.ifelse <- function(cond, yes, no) structure(ifelse(cond, yes, no), class = class(yes))
hadley

5
ดี คุณเห็นเหตุผลใด ๆ ที่ไม่ใช่พฤติกรรมเริ่มต้นหรือไม่
IRTFM

แค่ระวังสิ่งที่คุณใส่ใน "ใช่" เพราะฉันมี NA และมันใช้งานไม่ได้ น่าจะดีกว่าที่จะผ่านคลาสเป็นพารามิเตอร์มากกว่าสมมติว่าเป็นคลาสของเงื่อนไข "ใช่"
เดนิส

1
ฉันไม่แน่ใจว่าความคิดเห็นล่าสุดนี้หมายถึง เพียงเพราะบางสิ่งมีค่า NA ไม่ได้หมายความว่ามันไม่สามารถมีคลาสได้
IRTFM

8 ปีนับตั้งแต่ปัญหานี้ได้เกิดขึ้นและยังคงifelse()ไม่ได้"ปลอดภัย"
M--

16

คำอธิบายของ DWin นั้นตรงประเด็น ฉันเล่นซอและต่อสู้กับมันซักพักก่อนที่ฉันจะรู้ตัวว่าฉันสามารถบังคับคลาสได้หลังจากคำสั่ง ifelse:

dates <- as.Date(c('2011-01-01','2011-01-02','2011-01-03','2011-01-04','2011-01-05'))
dates <- ifelse(dates=='2011-01-01',dates-1,dates)
str(dates)
class(dates)<- "Date"
str(dates)

ตอนแรกรู้สึกว่า "แฮ็ค" เล็กน้อยสำหรับฉัน แต่ตอนนี้ฉันแค่คิดว่ามันเป็นราคาขนาดเล็กที่ต้องจ่ายสำหรับผลตอบแทนที่ฉันได้รับจาก ifelse () นอกจากนี้ยังมีความรัดกุมมากกว่าลูป


นี้ (ดีถ้าใช่ hackish) เทคนิคดูเหมือนว่าจะยังความช่วยเหลือเกี่ยวกับความจริงที่ว่าอาร์เอสforคำสั่งกำหนดค่าของรายการในVECTORการNAMEแต่ไม่ได้เป็นของชั้น
เกร็ก Minshall

6

วิธีการที่แนะนำไม่สามารถใช้ได้กับคอลัมน์ปัจจัย รหัสต้องการแนะนำการปรับปรุงนี้:

safe.ifelse <- function(cond, yes, no) {
  class.y <- class(yes)
  if (class.y == "factor") {
    levels.y = levels(yes)
  }
  X <- ifelse(cond,yes,no)
  if (class.y == "factor") {
    X = as.factor(X)
    levels(X) = levels.y
  } else {
    class(X) <- class.y
  }
  return(X)
}

โดยวิธีการ: ifelse ดูด ... ด้วยพลังอันยิ่งใหญ่มาพร้อมความรับผิดชอบที่ดีเช่นการแปลงประเภทของการฝึกอบรม 1x1 และ / หรือตัวเลข [เมื่อพวกเขาควรจะเพิ่มตัวอย่าง] ตกลงกับฉัน แต่การแปลงประเภทนี้ใน ifelse ไม่พึงประสงค์อย่างชัดเจน ฉันชนกับ 'ข้อผิดพลาด' เดียวกันของ ifelse หลายครั้งแล้วและมันก็แค่ขโมยเวลาของฉัน :-(

FW


นี่เป็นทางออกเดียวที่เหมาะกับฉันสำหรับปัจจัย
bshor

ผมจะมีความคิดว่าระดับที่จะได้รับกลับมาจะเป็นสหภาพของระดับของyesและnoและที่คุณแรกจะตรวจสอบเพื่อดูว่าพวกเขาทั้งปัจจัย คุณอาจจะต้องแปลงเป็นตัวละครแล้วทำการ rebundle ด้วย "unionized" -levels
IRTFM

6

สาเหตุที่ใช้ไม่ได้เพราะ ifelse () ฟังก์ชันแปลงค่าเป็นปัจจัย วิธีแก้ปัญหาที่ดีคือการแปลงเป็นอักขระก่อนที่จะประเมิน

dates <- as.Date(c('2011-01-01','2011-01-02','2011-01-03','2011-01-04','2011-01-05'))
dates_new <- dates - 1
dates <- as.Date(ifelse(dates =='2011-01-01',as.character(dates_new),as.character(dates)))

สิ่งนี้ไม่ต้องการไลบรารีใด ๆ นอกเหนือจากฐาน R


5

คำตอบที่ได้รับจาก @ fabian-werner นั้นยอดเยี่ยม แต่วัตถุสามารถมีหลายคลาสได้และ "factor" อาจไม่จำเป็นต้องเป็นคำตอบแรกที่ส่งกลับมาclass(yes)ดังนั้นฉันขอแนะนำการปรับเปลี่ยนขนาดเล็กเพื่อตรวจสอบแอตทริบิวต์ของคลาสทั้งหมด:

safe.ifelse <- function(cond, yes, no) {
      class.y <- class(yes)
      if ("factor" %in% class.y) {  # Note the small condition change here
        levels.y = levels(yes)
      }
      X <- ifelse(cond,yes,no)
      if ("factor" %in% class.y) {  # Note the small condition change here
        X = as.factor(X)
        levels(X) = levels.y
      } else {
        class(X) <- class.y
      }
      return(X)
    }

ฉันได้ส่งคำขอไปยังทีม R Development เพื่อเพิ่มตัวเลือกที่เป็นเอกสารเพื่อให้ base :: ifelse () รักษาแอตทริบิวต์ตามการเลือกของผู้ใช้ที่ต้องการเก็บรักษาแอตทริบิวต์ คำขออยู่ที่นี่: https://bugs.r-project.org/bugzilla/show_bug.cgi?id=16609 - ได้รับการตั้งค่าสถานะเป็น "WONTFIX" โดยอ้างว่าเป็นวิธีที่เป็นอยู่ตอนนี้ แต่ฉันได้ให้เหตุผลในการติดตามว่าทำไมการเพิ่มอย่างง่ายอาจช่วยให้ผู้ใช้ปวดหัวจำนวนมาก บางที "+1" ของคุณในเธรดข้อผิดพลาดนั้นอาจกระตุ้นให้ทีม R Core พิจารณาอีกครั้ง

แก้ไข: นี่เป็นรุ่นที่ดีกว่าที่อนุญาตให้ผู้ใช้ระบุว่าจะเก็บรักษาแอตทริบิวต์ใดไว้ไม่ว่าจะเป็น "cond" (พฤติกรรมเริ่มต้น ifelse ()), "ใช่" พฤติกรรมตามรหัสด้านบนหรือ "ไม่" สำหรับกรณีที่ คุณลักษณะของค่า "ไม่" ดีกว่า:

safe_ifelse <- function(cond, yes, no, preserved_attributes = "yes") {
    # Capture the user's choice for which attributes to preserve in return value
    preserved           <- switch(EXPR = preserved_attributes, "cond" = cond,
                                                               "yes"  = yes,
                                                               "no"   = no);
    # Preserve the desired values and check if object is a factor
    preserved_class     <- class(preserved);
    preserved_levels    <- levels(preserved);
    preserved_is_factor <- "factor" %in% preserved_class;

    # We have to use base::ifelse() for its vectorized properties
    # If we do our own if() {} else {}, then it will only work on first variable in a list
    return_obj <- ifelse(cond, yes, no);

    # If the object whose attributes we want to retain is a factor
    # Typecast the return object as.factor()
    # Set its levels()
    # Then check to see if it's also one or more classes in addition to "factor"
    # If so, set the classes, which will preserve "factor" too
    if (preserved_is_factor) {
        return_obj          <- as.factor(return_obj);
        levels(return_obj)  <- preserved_levels;
        if (length(preserved_class) > 1) {
          class(return_obj) <- preserved_class;
        }
    }
    # In all cases we want to preserve the class of the chosen object, so set it here
    else {
        class(return_obj)   <- preserved_class;
    }
    return(return_obj);

} # End safe_ifelse function

1
inherits(y, "factor")อาจจะ "ถูกต้องมากกว่า" กว่า"factor" %in% class.y
IRTFM

จริง inheritsอาจจะดีที่สุด
Mekki MacAulay
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.