Структура данных, приводящая к ошибке
Error in if (schools[ii, 34] > schools[ii, 23]) { :
missing value where TRUE/FALSE needed
, возникает, когда одно или оба значения в сравнении имеют значение NA
, поскольку NA
распространяется через сравнение x > y
,например,
> test = 1 > NA
> test
[1] NA
и управление потоком if (test) {}
не может определить, является ли тест TRUE
(и поэтому код должен быть выполнен) или FALSE
> if (test) {}
Error in if (test) { : missing value where TRUE/FALSE needed
Простое векторизованное решение недостаточно хорошо
> set.seed(123)
> n = 10; x = sample(n); y = sample(n); y[5] = NA
> sum(x > y)
[1] NA
хотя «исправление» очевидно и недорого
> sum(x > y, na.rm = TRUE)
[1] 3
Цикл for
также не работает, но это невозможно(как в части исходного вопроса) просто добавить предложение na.rm = TRUE
в оператор if
s = 0
for (i in seq_along(x)) {
if (x[i] > y[i], na.rm = TRUE)
s <- s + 1
}
s
, потому что это синтаксически недопустимо
Error: unexpected ',' in:
"for (i in seq_along(x)) {
if (x[i] > y[i],"
, поэтому более креативное решениенеобходимо найти, например, проверить, действительно ли значение сравнения составляет TRUE
s <- 0
for (i in seq_along(x)) {
if (isTRUE(x[i] > y[i]))
s <- s + 1
}
s
Конечно, сравнивать производительность некорректного кода бесполезно;сначала нужно написать правильные решения
f1 <- function(x, y)
sum(x > y, na.rm = TRUE)
f2 <- function(x, y) {
s <- 0
for (i in seq_along(x))
if (isTRUE(x[i] > y[i]))
s <- s + 1
s
}
f1()
кажется гораздо более компактным и читаемым по сравнению с f2()
, но мы должны убедиться, что результаты разумны
> x > y
[1] FALSE TRUE FALSE FALSE NA TRUE FALSE FALSE FALSE TRUE
> f1(x, y)
[1] 3
и то же самое
> identical(f1(x, y), f2(x, y))
[1] FALSE
Эй, подожди, что происходит? Они выглядят одинаково?
> f2(x, y)
[1] 3
На самом деле результаты численно равны, но f1()
возвращает целочисленное значение, тогда как f2()
возвращает числовое значение
> all.equal(f1(x, y), f2(x, y))
[1] TRUE
> class(f1(x, y))
[1] "integer"
> class(f2(x, y))
[1] "numeric"
, и если мыДля сравнения производительности нам действительно нужно, чтобы результаты были идентичными - не имеет смысла сравнивать яблоки и апельсины. Мы можем обновить f2()
для получения целого числа, убедившись, что сумма s
всегда является целым числом - используйте суффикс L
, например, 0L
, для создания целочисленного значения
> class(0)
[1] "numeric"
> class(0L)
[1] "integer"
и убедитесь, что целое число 1L
добавляется к s
на каждой успешной итерации
f2a <- function(x, y) {
s <- 0L
for (i in seq_along(x))
if (isTRUE(x[i] > y[i]))
s <- s + 1L
s
}
Затем у нас есть
> f2a(x, y)
[1] 3
> identical(f1(x, y), f2a(x, y))
[1] TRUE
и теперь мы можем сравнить производительность
> microbenchmark(f1(x, y), f2a(x, y))
Unit: microseconds
expr min lq mean median uq max neval
f1(x, y) 1.740 1.8965 2.05500 2.023 2.0975 6.741 100
f2a(x, y) 17.505 18.2300 18.67314 18.487 18.7440 34.193 100
Конечно, f2a()
намного медленнее, но для этой проблемы размера, поскольку единица измерения составляет «микросекунды», возможно, это не имеет значения - как решения масштабируются с размером проблемы?
> set.seed(123)
> x = lapply(10^(3:7), sample)
> y = lapply(10^(3:7), sample)
> f = f1; microbenchmark(f(x[[1]], y[[1]]), f(x[[2]], y[[2]]), f(x[[3]], y[[3]]))
Unit: microseconds
expr min lq mean median uq max neval
f(x[[1]], y[[1]]) 9.655 9.976 10.63951 10.3250 11.1695 17.098 100
f(x[[2]], y[[2]]) 76.722 78.239 80.24091 78.9345 79.7495 125.589 100
f(x[[3]], y[[3]]) 764.034 895.075 914.83722 908.4700 922.9735 1106.027 100
> f = f2a; microbenchmark(f(x[[1]], y[[1]]), f(x[[2]], y[[2]]), f(x[[3]], y[[3]]))
Unit: milliseconds
expr min lq mean median uq
f(x[[1]], y[[1]]) 1.260307 1.296196 1.417762 1.338847 1.393495
f(x[[2]], y[[2]]) 12.686183 13.167982 14.067785 13.923531 14.666305
f(x[[3]], y[[3]]) 133.639508 138.845753 144.152542 143.349102 146.913338
max neval
3.345009 100
17.713220 100
165.990545 100
Они оба масштабируются линейно (не удивительно), но даже для длин 100000 f2a()
не выглядит слишком плохо - 1/6 секунды - и может быть кандидатом в ситуации, когда векторизация запутываеткод, а не разъяснил это. Стоимость извлечения отдельных элементов из столбцов data.frame меняет это исчисление, но также указывает на ценность работы с атомарными векторами, а не со сложными структурами данных.
Для чего стоит подумать о худших реализацияхособенно
f3 <- function(x, y) {
s <- logical(0)
for (i in seq_along(x))
s <- c(s, isTRUE(x[i] > y[i]))
sum(s)
}
, который масштабируется квадратично
> f = f3; microbenchmark(f(x[[1]], y[[1]]), f(x[[2]], y[[2]]), f(x[[3]], y[[3]]), times = 1)
Unit: milliseconds
expr min lq mean median
f(x[[1]], y[[1]]) 7.018899 7.018899 7.018899 7.018899
f(x[[2]], y[[2]]) 371.248504 371.248504 371.248504 371.248504
f(x[[3]], y[[3]]) 42528.280139 42528.280139 42528.280139 42528.280139
uq max neval
7.018899 7.018899 1
371.248504 371.248504 1
42528.280139 42528.280139 1
(потому что c(s, ...)
копирует все s
, чтобы добавить к нему один элемент), и это очень часто встречается в людяхcode.
Вторым распространенным замедлением является использование сложных структур данных (например, data.frame), а не простых структур данных (например, атомарных векторов), например, сравнение
f4 <- function(df) {
s <- 0L
x <- df[[1]]
y <- df[[2]]
for (i in seq_len(nrow(df))) {
if (isTRUE(x[i] > y[i]))
s <- s + 1L
}
s
}
f5 <- function(df) {
s <- 0L
for (i in seq_len(nrow(df))) {
if (isTRUE(df[i, 1] > df[i, 2]))
s <- s + 1L
}
s
}
с
> df <- Map(data.frame, x, y)
> identical(f1(x[[1]], y[[1]]), f4(df[[1]]))
[1] TRUE
> identical(f1(x[[1]], y[[1]]), f5(df[[1]]))
[1] TRUE
> microbenchmark(f1(x[[1]], y[[1]]), f2(x[[1]], y[[1]]), f4(df[[1]]), f5(df[[1]]), times = 10)
Unit: microseconds
expr min lq mean median uq
f1(x[[1]], y[[1]]) 10.042 10.324 13.3511 13.4425 14.690
f2a(x[[1]], y[[1]]) 1310.186 1316.869 1480.1526 1344.8795 1386.322
f4(df[[1]]) 1329.307 1336.869 1363.4238 1358.7080 1365.427
f5(df[[1]]) 37051.756 37106.026 38187.8278 37876.0940 38416.276
max neval
20.753 10
2676.030 10
1439.402 10
42292.588 10