Итерация по столбцу и подсчет строк, удовлетворяющих условию в R - PullRequest
2 голосов
/ 12 ноября 2019

пытается написать функцию цикла for, чтобы определить количество школ с расходами на проживание в столбце 34, превышающими стоимость платы в столбце 23.

numrows <- dim(schools)[1]
for(ii in 1:numrows){ 
  if(schools[ii, 34] > schools[ii, 23], na.rm = TRUE){
    nrow(numrows)
  }
} 

Я получаю следующую ошибку

Error in if (schools[ii, 34] > schools[ii, 23]) { : 
  missing value where TRUE/FALSE needed

Я заметил, что некоторые расходы на плату отсутствуют, и я хотел бы пропустить их в сравнениях. Также я ожидаю только количество строк, которые удовлетворяют условию.

Ответы [ 2 ]

1 голос
/ 12 ноября 2019

Структура данных, приводящая к ошибке

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
1 голос
/ 12 ноября 2019

Чтобы еще раз продемонстрировать мою точку зрения, вот простой пример, основанный на выборке из 10000 строк data.frame

set.seed(2018)
df <- data.frame(one = runif(10^4), two = runif(10^4))

Выполнение анализа microbenchmark

library(microbenchmark)
res <- microbenchmark(
    vectorised = sum(df[, 1] > df[, 2]),
    for_loop = {
        ss <- 0
        for (i in seq_len(nrow(df))) if (df[i, 1] > df[i, 2]) ss <- ss + 1
        ss
    })

res
#    Unit: microseconds
#       expr        min        lq         mean      median          uq
# vectorised     59.681     65.13     78.33118     72.8305     77.9195
#   for_loop 346250.957 359535.08 398508.54996 379421.2305 426452.4265
#        max neval
#    152.172   100
# 608490.869   100

library(ggplot2)
autoplot(res)

enter image description here

Обратите внимание на разницу в четыре порядка (!!!) (это в 10 000 раз!) Между циклом for и векторизованной операцией. Ни удивительно, ни интересно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...