ifelse()がDateオブジェクトを数値オブジェクトに変換しないようにする方法


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
Dateオブジェクトの正しいクラスを保持if_else()ifelseながら代替できるdplyrパッケージに関数が追加されました - 最近の回答として以下に掲載されています。(このコメントの時点で)他の多くの回答が上位にランク付けされた他の多くの回答とは異なり、ユニットテストおよびドキュメント化された機能をCRANパッケージに提供することでこの問題を解決するため、ここで注目します。
Sam Firke 16

回答:


132

data.table::fifelsedata.table >= 1.12.3)またはを使用できますdplyr::if_else


data.table::fifelse

とは異なりifelsefifelse入力のタイプとクラスを保持します。

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()ます。:truefalse引数は同じ型でなければなりません。これにより、予期しない戻り値の型が得られ、日付のような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
IRTFM 2016

3
if_elsebe NAの引数の1つを使用する方法はありますか?私は論理的なNA_オプションをNA_double_
試し

11
@Zak 1つの可能性は、ラップNAすることas.Dateです。
Henrik

NA_real_@roarkz があります。そして@ヘンリック、あなたのコメントはここで私の問題を解決しました。
BLT 2018年

63

これは、文書化に関連する価値ifelse

またはの値からのデータ値と同じ長さと属性(次元と " class" を含む)のベクトル。回答のモードは、最初にから取得したすべての値、次にから取得したすべての値に対応するために、論理から強制されます。testyesnoyesno

その意味するところまで要約すると、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_elsemagrittr / dplyr / tidyr複合データシェーピングパッケージに組み込みました。


37
ややエレガントなバージョン:safe.ifelse <- function(cond, yes, no) structure(ifelse(cond, yes, no), class = class(yes))
ハドリー、2011

5
いいね。それがデフォルトの動作ではない理由があると思いますか?
IRTFM '13 / 07/13

私がNAを持っていてそれが機能しなかったので、あなたが「はい」に何を入れたかに注意してください。「はい」条件のクラスであると想定するよりも、クラスをパラメータとして渡す方がおそらく良いでしょう。
Denis

1
最後のコメントがこれを意味するかどうかはわかりません。NA値を持つものがあるからといって、クラスを持つことができないわけではありません。
IRTFM 2019

この問題が発生してから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()から得られるパフォーマンスリターンを支払うための小さな代償と考えています。さらに、ループよりもはるかに簡潔です。


この(いいですが、はい、ハック)手法は、Rのforステートメントが項目のVECTORto NAMEに割り当てますが、それらのクラスには割り当てないという事実にも役立ちます。
グレッグミンシャル

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

私が返されるレベルはレベルの労働組合だろうと思っているだろうyesno、あなたが最初に彼らは両方の因子であったことを確認してくださいということ。おそらくキャラクターに変換してから、「unionized」レベルで再バンドルする必要があります。
IRTFM 2016

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)
    }

また、保持する属性のユーザー選択に基づいてbase :: ifelse()が属性を保持するように文書化されたオプションを追加するように、R開発チームにリクエストを送信しました。リクエストはこちら:https : //bugs.r-project.org/bugzilla/show_bug.cgi?id=16609 //bugs.r-project.org/bugzilla/show_bug.cgi?id=16609-それは常に現在の方法であったという理由ですでに「WONTFIX」としてフラグが付けられています、しかし、単純な追加で多くのRユーザーの頭痛を軽減できる理由について、フォローアップの引数を提供しました。おそらく、そのバグスレッドでの「+1」は、Rコアチームの再検討を促すでしょう。

編集:これは、ユーザーがどの属性を保持するかを指定できる、より優れたバージョンです。「cond」(デフォルトはifelse()の動作)、「yes」、上記のコードによる動作、または「no」の場合「いいえ」の値の属性の方が優れています。

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 '28

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