関数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
のヘルプページは、これがバグではなく機能であることを示していますが、驚くべき動作であることがわかったものの説明を見つけるのに苦労しています。
_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"
_
ベンチマークを含むfifelse
の詳細については、 開発バージョン1.12.3のニュース項目#21 を参照してください。開発バージョンのインストールについては、 here を参照してください。
dplyr::if_else
__dplyr 0.5.0
_リリースノート : "[_if_else
_]は、ifelse()
:true
およびfalse
引数は同じ型でなければなりません。これはそれほど驚くべき戻り値型を与えず、dates "のような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"
_
文書化されたValue of 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複合体に組み込んだことがわかります。
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()から得られるパフォーマンスリターンを支払うための小さな価格だと考えています。さらに、ループよりもはるかに簡潔です。
推奨される方法は、因子列では機能しません。この改善を提案したいと思います:
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
これが機能しない理由は、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以外のライブラリは必要ありません。
@ 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開発チームに、保存する属性のユーザー選択に基づいて属性を保存するbase :: ifelse()を持つ文書化されたオプションを追加するリクエストを送信しました。リクエストはここにあります: https://bugs.r-project.org/bugzilla/show_bug.cgi?id=16609 -常に持っているという理由で既に「WONTFIX」としてフラグが付けられています今はそうでしたが、単純な追加でRユーザーの多くの頭痛の種を救うことができる理由についての補足説明を提供しました。おそらく、そのバグスレッドの「+1」は、R Coreチームにもう一度見てもらうことを奨励するでしょう。
編集:これは、「cond」(デフォルトのifelse()の振る舞い)、「yes」、上記のコードによる動作、または「no」のいずれかを保持する属性をユーザーが指定できるようにするより良いバージョンです。 「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