Для двух логических векторов x
и y
, длины > 1E8, что является самым быстрым способом вычисления кросс-таблиц 2x2?
Я подозреваю, что ответ заключается в том, чтобы написать его на C/С++, но мне интересно, есть ли что-то в R, которое уже довольно хорошо разбирается в этой проблеме, так как это не редкость.
Пример кода для записей 300M (не стесняйтесь N = 1E8, если 3E8 слишком велика, я выбрал общий размер чуть менее 2,5 ГБ (2,4 ГБ). Я нацелился на плотность 0,02, чтобы сделать ее более интересной (можно использовать разреженный вектор, если это помогает, но преобразование типа может занять время).
set.seed(0)
N = 3E8
p = 0.02
x = sample(c(TRUE, FALSE), N, prob = c(p, 1-p), replace = TRUE)
y = sample(c(TRUE, FALSE), N, prob = c(p, 1-p), replace = TRUE)
Некоторые очевидные методы:
-
table
-
bigtabulate
- Простые логические операции (например,
sum(x & y)
) - Векторное умножение (boo)
-
data.table
- Некоторые из вышеперечисленных, с
parallel
из пакетаmulticore
(или нового пакетаparallel
)
Я взял удар в первых трех вариантах (см. мой ответ), но я чувствую, что должно быть что-то лучше и быстрее.
Я нахожу, что table
работает очень медленно. bigtabulate
кажется излишним для пары логических векторов. Наконец, выполнение ванильных логических операций похоже на kludge, и он смотрит на каждый вектор слишком много раз (3X? 7X?), Не говоря уже о том, что он заполняет много дополнительной памяти во время обработки, что является массовым отпуском времени.
Векторное умножение обычно является плохой идеей, но когда вектор разрежен, можно получить преимущество от его хранения как такового, а затем использовать векторное умножение.
Не изменяйте N
и p
, если это продемонстрирует что-либо интересное поведение функций табуляции.:)
Обновление 1. Мой первый ответ дает тайминги по трем наивным методам, который является основой для веры table
медленным. Тем не менее, главное, чтобы понять, что "логический" метод крайне неэффективен. Посмотрите, что он делает:
- 4 операции логического вектора
- 4 преобразования типа (от логического до целого или FP - для
sum
) - 4 суммирования векторов
- 8 назначений (1 для логической операции, 1 для суммирования)
Не только это, но он даже не скомпилирован или не распараллелен. Тем не менее, он все еще превосходит штаны table
. Обратите внимание, что bigtabulate
, с дополнительным преобразованием типа (1 * cbind...
) по-прежнему бьет table
.
Обновление 2. Чтобы никто не указывал, что логические векторы в R поддерживают NA
, и что это будет ключ в системе для этих перекрестных таблиц (что в большинстве случаев верно), я должен указать, что мои векторы введите is.na()
или is.finite()
.:) Я отлаживал NA
и другие не конечные значения - они недавно были головной болью для меня. Если вы не знаете, есть ли все ваши записи NA
, вы можете протестировать с помощью any(is.na(yourVector))
- это было бы разумно, прежде чем вы примете некоторые идеи, возникающие в этом Q & A.
Обновление 3. Брэндон Бертельсен задал очень разумный вопрос в комментариях: зачем использовать так много данных, когда подвыбор (исходный набор, в конце концов, является образцом;-)) может быть адекватным для целей создания кросс-табуляция? Не слишком сильно заходить в статистику, но данные возникают из случаев, когда наблюдения TRUE
очень редки для обеих переменных. Один из них является результатом аномалии данных, другой - из-за возможной ошибки в коде (возможная ошибка, потому что мы видим только вычислительный результат - думаем о переменной x
как "Garbage In" и y
как "Garbage Out", В результате возникает вопрос, являются ли проблемы в выходе, вызванные кодом, исключительно теми случаями, когда данные являются аномальными, или есть некоторые другие случаи, когда хорошие данные ухудшаются? (Вот почему я задал вопрос о останавливается, когда встречаются NaN
, NA
или Inf
.)
Это также объясняет, почему мой пример имеет небольшую вероятность для значений TRUE
; они действительно происходят намного меньше, чем 0,1% времени.
Означает ли это другой путь решения? Да: это предполагает, что мы можем использовать два индекса (т.е. Местоположения TRUE
в каждом наборе) и подсчетные пересечения. Я избегал заданных перекрестков, потому что я был сожжен некоторое время назад Matlab (да, это R, но медведь со мной), который сначала будет сортировать элементы набора до того, как он пересечет. (Я смутно вспоминаю, что сложность была еще более смущающей: например, O(n^2)
вместо O(n log n)
.)