R data.table 语法替换列中的多个值

elb*_*dor 4 r data.table

这可能很简单,但我很难找到一种方法来做到这一点。我有一个dt包含许多列的 data.table。如果其中一列sex包含值MaleFemale"",我想将这些值重新映射到Male= MFemale=F""=NA。我怎样才能做到这一点而不使用ifelse.

Lud*_*wig 7

有许多解决方案可用。在这里,我将重点关注base-R 或data.table解决方案。

数据:

library(data.table)
dt <- data.table(sex = c("Male", "Female", ""))
Run Code Online (Sandbox Code Playgroud)

选项1:

您可以使用fcase(基本上是嵌套的ifelse):

dt[, sex := fcase(sex == "Male", "M", sex == "Female", "F")]

    sex
1:    M
2:    F
3: <NA>
Run Code Online (Sandbox Code Playgroud)

请注意,默认情况下,所有不匹配的内容都fcase设置为NA。如果需要,您可以随时提供更多“案例”(如sex == "", NA, ...)。

选项2:

您可以将其设置为一个因素并重新标记级别:

dt[, sex := factor(sex, levels = c("Male", "Female"), labels = c("M", "F"))]

    sex
1:    M
2:    F
3: <NA>
Run Code Online (Sandbox Code Playgroud)

选项 3:(由 @thelatemail 提供)

dt[.(old = c("Male", "Female", ""), new = c("M", "F", NA)), on = .(sex = old), sex := new]

    sex
1:    M
2:    F
3: <NA>
Run Code Online (Sandbox Code Playgroud)

选项4:(由@jay.sf提供)

使用substring

dt[, sex := substring(replace(sex, sex == "", NA), 1, 1)]

    sex
1:    M
2:    F
3: <NA>
Run Code Online (Sandbox Code Playgroud)

选项 5:

一个data.table版本match(受到@jblood94的启发)。

dt[, sex := c("M", "F", NA)[chmatch(sex, c("Male", "Female", ""))]]

    sex
1:    M
2:    F
3: <NA>
Run Code Online (Sandbox Code Playgroud)

选项6:(由@s_baldur提供)

创建查找表。

LUT <- data.table(sex = c("Male", "Female", ""), to=c("M", "F", NA), key = "sex")
dt[, sex := LUT[.SD, to]]

    sex
1:    M
2:    F
3: <NA>
Run Code Online (Sandbox Code Playgroud)

如果您有其他解决方案,我可以在此处添加,请告诉我!

微基准测试:

我使用sample数据是因为有序值使选项 1选项 2比其他选项更有优势。*

library(microbenchmark)

#data
dt.bm <- data.table(sex = sample(rep(c("Male", "Female", ""), 100000)))

#benchmarking
microbenchmark(
        option_1 = copy(dt.bm)[, sex := fcase(sex == "Male", "M", sex == "Female", "F")],
        option_2 = copy(dt.bm)[, sex := factor(sex, levels = c("Male", "Female"), labels = c("M", "F"))],
        option_3 = copy(dt.bm)[.(old=c("Male","Female",""), new=c("M","F",NA)), on=.(sex=old), sex := new],
        option_4 = copy(dt.bm)[, sex := substring(replace(sex, sex == "", NA), 1, 1)],
        option_5 = copy(dt.bm)[, sex := c("M", "F", NA)[chmatch(sex, c("Male", "Female", ""))]],
        option_6 = {LUT <- data.table(sex = c("Male", "Female", ""), to=c("M", "F", NA), key = "sex"); copy(dt.bm)[, sex := LUT[.SD, to]]},
        times = 10L
        )
Run Code Online (Sandbox Code Playgroud)

结果:

就速度而言,选项 5是性能最好的选项。**

#results
Unit: milliseconds
     expr    min     lq     mean   median      uq     max neval
 option_1 6.7219 6.7816  7.76613  6.85335  9.7207 10.3584    10
 option_2 6.1356 6.1981  7.18201  6.29060  9.2294  9.4921    10
 option_3 8.6486 8.6935 10.61704 10.29420 12.5258 13.0865    10
 option_4 8.1740 8.2124  9.10800  8.31170  9.1615 12.3198    10
 option_5 3.3497 3.3843  3.84333  3.44340  3.4822  7.5465    10
 option_6 6.0769 6.2583  6.99274  6.51460  6.6087  9.6276    10
Run Code Online (Sandbox Code Playgroud)

*在有序情况下,选项 2 的平均计时时间约为 3.9 毫秒(数据未显示)。

**这只是考虑basedata.table解决方案。按照 @s_baldurs 答案的建议使用collapse::join,平均时间约为 3.1 毫秒,而fastmatch::fmatch(@jblood94) 是迄今为止最快的方法,为 3.0 毫秒(数据未显示)。

  • 这通常会更高效: `dt[, sex := c("M", "F", NA)[match(sex, c("Male", "Female", ""))]]` (和对于 `fastmatch::fmatch` 更是如此)。 (2认同)
  • @s_baldur 修复了它。我将在此处仅添加使用 `data.table` 或 `base` R 的版本。我将参考您的答案并添加有关基准的评论。 (2认同)