自从提出这个问题以来,出现了许多重复的问题(here 和 here)。我收集(并改进)了一些更惯用的答案,并将它们与我自己的 Rcpp 实现进行了基准测试。
为简单起见,我比较了将字符矩阵作为输入并返回作为输出的函数,不是仅包含字符变量的数据框。您始终可以使用as.matrix 和as.data.frame 从一个强制转换到另一个(例如,参见底部)。
Rcpp::sourceCpp(code = '
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
void shift_na_in_place(CharacterMatrix x)
{
int m = x.nrow();
int n = x.ncol();
for (int i = 0, k = 0, k0 = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (x[k] != NA_STRING) {
x[k0] = x[k];
k0 += m;
}
k += m;
}
while (k0 < k) {
x[k0] = NA_STRING;
k0 += m;
}
k = (k % m) + 1;
k0 = k;
}
if (x.attr("dimnames") != R_NilValue) {
List dn = x.attr("dimnames");
dn[1] = R_NilValue;
if (dn.attr("names") != R_NilValue) {
CharacterVector ndn = dn.attr("names");
ndn[1] = "";
}
}
}
// [[Rcpp::export]]
CharacterMatrix shift_na(CharacterMatrix x)
{
CharacterMatrix y = clone(x);
shift_na_in_place(y);
return y;
}
')
f1 <- function(x) {
t(apply(x, 1L, function(y) {r <- is.na(y); c(y[!r], y[r])}))
}
f2 <- function(x) {
t(apply(x, 1L, function(y) y[order(is.na(y), method = "radix")]))
}
f3 <- function(x) {
d <- dim(x)
dn <- dimnames(x)
matrix(x[order(row(x), is.na(x), method = "radix")],
nrow = d[1L], ncol = d[2L], byrow = TRUE,
dimnames = if (!is.null(dn)) c(dn[1L], list(NULL)))
}
f4 <- function(x) {
d <- dim(x)
dn <- dimnames(x)
matrix(x[order(is.na(x) + (row(x) - 1L) * 2L + 1L, method = "radix")],
nrow = d[1L], ncol = d[2L], byrow = TRUE,
dimnames = if (!is.null(dn)) c(dn[1L], list(NULL)))
}
set.seed(1L)
m <- 1e+05L
n <- 10L
x <- sample(c(letters, NA), size = m * n, replace = TRUE, prob = c(rep(1, 26), 13))
dim(x) <- c(m, n)
microbenchmark::microbenchmark(shift_na(x), f1(x), f2(x), f3(x), f4(x), check = "identical")
Unit: milliseconds
expr min lq mean median uq max neval
shift_na(x) 10.04959 10.32019 10.82935 10.41968 10.60104 22.69412 100
f1(x) 141.95959 150.83875 180.49025 167.01266 211.52478 248.07587 100
f2(x) 722.27211 759.75710 780.69368 773.26920 797.01253 857.07905 100
f3(x) 18.45201 19.15436 22.47760 21.59577 22.40543 66.47121 100
f4(x) 30.03168 31.62765 35.22960 33.92801 35.06384 85.92661 100
如您所料,专用的Rcpp 实现shift_na 最快,但f3 和f4 并没有慢很多。一些更好的点:
-
f1 和 f2 调用 apply,它建立在 R for 循环之上,所以它们很慢也就不足为奇了。
-
f3 和f4 必须为is.na(x) 和row(x) 分配内存,这对于足够大的x 来说可能是一个障碍。
-
f3 比 f4 快,因为当被排序的整数向量的范围(最大值减去最小值)小于 100000 时,"radix" 排序使用更快的算法(请参阅?sort)。这里,范围是:
is.na(x): 1
row(x): 99999
is.na(x) + (row(x) - 1L) * 2L + 1L: 199999
-
shift_na(x) 创建x 的副本并就地修改副本。如果您因为x 非常大而无法或不想为副本分配内存,则可以通过shift_na_in_place(x) 将x 修改到位。
-
shift_na_in_place 应该优先于 shift_na,如果您有一个包含字符变量的数据框 data,而不是字符矩阵。在这种情况下,没有必要保留中间的as.matrix(data);可以就地修改:
x <- as.matrix(data)
shift_na_in_place(x)
newdata <- as.data.frame(x)