Сложность времени% в% в R; есть ли способ сделать его O (1) похожим на множества в Python?

65
5

Оператор %in% в R проверяет, есть ли что-то в чем-то другом, очевидно. Но мне интересно о производительности. В Python, для поиска элемента, набора или ключей ключа является O (1), потому что набор является хэш-таблицей, я думаю. Но поиск элемента в списке в Python может быть O (n) с n-длинным списком, потому что он будет искать элемент за элементом. Итак, как %in% работает за кулисами для разных типов данных в R? Кажется, что в 5 раз больше времени для поиска чего-то в коэффициенте dtype в R в отличие от вектора, но, похоже, %in% ищет вектор линейно. Сначала я подумал, что тип данных факторов может быть как набор в Python, поскольку они оба сводят что-то к нему уникальные значения, но не на всех: https://www.tutorialspoint.com/r/r_data_types.htm. Вот пример кода, чтобы вы могли видеть, что я имею в виду во время выполнения:

library(microbenchmark)
s <- seq(5000)
microbenchmark(1 %in% s, times = 100000)
# searching for a term further in the list takes longer
microbenchmark(4999 %in% s, times = 100000)
s <- as.factor(s)
# searching for something in a factor takes way longer than a vector
# I think because everything is converted to a character dtype
microbenchmark(4999 %in% s, times = 100000)

Мой главный вопрос заключается в следующем: существует ли способ сделать% in% O (1) в R? Связанный с этим вопрос: существует ли эквивалент (в R) типа набора() в Python?

спросил(а) 2020-03-27T17:45:46+03:00 2 месяца назад
1
Решение
65

Как мы обсуждали в комментариях, в R есть свойственный набор-подобный механизм, хотя он, по общему признанию, немного хакерский и, возможно, не совсем то, что предназначалось. (Некоторые ограничения этого взлома задокументированы в пакете hashmap.)

Окружения в R внутренне хэшируются. Это может использоваться для хранения суровых объектов со случайным доступом (как для чтения, так и для записи). Чтобы проверить некоторые контрольные показатели, я создам несколько типов векторов, чтобы обосновать вашу первоначальную озабоченность и показать улучшение, используя среду.

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

library(microbenchmark)
set.seed(2)
s1 <- seq(5000)
s2 <- rev(s1) # to highlight the bias you highlighted, since the vector is sorted
s3 <- sample(s1) # to shake things up a little
s4 <- as.character(s3) # comparison with character-based named in 'l' and 'e'

l <- list()
e <- new.env(parent = emptyenv())
for (i in s4) {
assign(i, TRUE, envir = e)
l[[i]] <- TRUE
}
head(names(l)) # unordered
# [1] "925" "3512" "2866" "840" "4716" "4713"

list имеет ординальность внутри своих объектов, что подтверждает предположение, что его объекты не хешированы:

which(names(l) == "1")
# [1] 2291

В средах нет этого:

e[[1]]
# Error in e[[1]] : wrong arguments for subsetting an environment

Некоторое быстрое тестирование на членство: я использовал логическое значение для этой цели, хотя это абсолютно произвольно. Для наших нужд было бы достаточно ничего, кроме NULL. Мы будем использовать простой !is.null(e[[...]]) для проверки конкретного членства:

!is.null(e[["1"]])
# [1] TRUE
!is.null(e[["10000"]])
# [1] FALSE
!is.null(l[["1"]])
# [1] TRUE
!is.null(l[["10000"]])
# [1] FALSE

microbenchmark(
vec1 = 1 %in% s1,
vec2 = 1 %in% s2,
vec3 = 1 %in% s3,
vec4 = "1" %in% s4,
lst = is.null(l[["1"]]),
env = is.null(e[["1"]]),
times = 1000
)
# Warning in microbenchmark(vec1 = 1 %in% s1, vec2 = 1 %in% s2, vec3 = 1 %in% :
# Could not measure a positive execution time for 6 evaluations.
# Unit: nanoseconds
# expr min lq mean median uq max neval
# vec1 5835 6929 12493.25 7294 9482 3214588 1000
# vec2 9117 9847 16660.73 10212 12764 4081050 1000
# vec3 7294 8388 19983.63 8752 10576 3274759 1000
# vec4 11670 12400 15423.03 12764 14223 74394 1000
# lst 20787 21517 24561.72 21881 22975 143317 1000
# env 0 1 461.25 365 366 18235 1000

Неудивительно, что list не работает хорошо, хотя он, кажется, лучше, чем векторы (в max Случае, относительно бессмысленны). Также неудивительно, что, основываясь на нашем утверждении, что окружающая среда использует внутреннюю, она работает довольно хорошо. Это O (1)?

microbenchmark(
samp5 = sapply(as.character(sample(5000, size = 5)), function(a) is.null(e[[a]])),
samp50 = sapply(as.character(sample(5000, size = 50)), function(a) is.null(e[[a]])),
samp500 = sapply(as.character(sample(5000, size = 500)), function(a) is.null(e[[a]])),
samp5000 = sapply(as.character(sample(5000, size = 5000)), function(a) is.null(e[[a]]))
)
# Unit: microseconds
# expr min lq mean median uq max neval
# samp5 25.893 32.4565 49.58154 40.4795 58.3485 169.573 100
# samp50 108.309 119.4310 156.45244 135.8410 167.3850 681.938 100
# samp500 935.750 1023.2715 1265.29732 1073.9610 1172.6055 6841.985 100
# samp5000 9410.008 10337.5520 11137.82968 10650.0765 11280.0485 15455.548 100

Первый, samp5, кажется, займет немного больше времени. Это не слишком удивительно, потому что есть накладные расходы, связанные с sapply, выборочными и другими вещами. Тем не менее, оставшиеся строки, по-видимому, довольно хорошо масштабируются с количеством пробы. Это говорит о том, что это действительно O (1) для некоторых базовых операций.

NB: Мне пришлось использовать весь sapply(...) потому что, в отличие от векторов и списков, среды R не позволяют подмножество с вектором.

e[[c("1")]]
# [1] TRUE
e[[c("1","10")]]
# Error in e[[c("1", "10")]] :
# wrong arguments for subsetting an environment

Это одна из претензий, сделанных hashmap (и фиксированным).

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

newset <- function() new.env(parent = emptyenv())
setadd <- function(set, n) set[[n]] <- TRUE
setdel <- function(set, n) set[[n]] <- NULL
setcontains <- function(set, n) !is.null(set[[n]])
setmembers <- function(set) names(set)

e <- newset()
setcontains(e, "a")
# [1] FALSE
setadd(e, "a")
setcontains(e, "a")
# [1] TRUE
setmembers(e)
# [1] "a"
setdel(e, "a")
setcontains(e, "a")
# [1] FALSE

(Существует аналогичное, но гораздо более обширное сообщение в блоге об этом Джеффри Хорнером здесь: http://jeffreyhorner.tumblr.com/post/114524915928/hash-table-performance-in-r-part-i.)

ответил(а) 2020-03-27T18:01:02.019790+03:00 2 месяца назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема