R внешнее произведение факторов - пользовательское отображение - слишком медленное

С учетом двух факторов (каждый с одинаковым набором уровней), скажем,

lev <- c("alpha", "bravo", "charlie", "echo", "delta", "foxtrot")
A <- factor(sample(lev, 6000, TRUE))
B <- factor(sample(lev, 6000, TRUE))

Я хочу взять их внешний продукт с пользовательской функцией продукта, определенной следующим образом:

mapping <- matrix(c(
    "green", "blue",  "blue",  "red",    "red",    "red",
    "blue",  "green", "blue",  "red",    "red",    "red",
    "blue",  "blue",  "green", "red",    "red",    "red",
    "red",   "red",   "red",   "green",  "yellow", "red",
    "red",   "red",   "red",   "yellow", "green",  "red",
    "red",   "red",   "red",   "red",    "red",    "green"),
    nrow=6, ncol=6,
    dimnames=list(lev, lev))
mapper <- function (X, Y) mapping[matrix(c(levels(X)[X], levels(Y)[Y]),
                                         ncol=2, byrow=TRUE)]
A.B <- outer(A, B, FUN=mapper)

Ожидаемый результат (для значительно сокращенного тестового примера) должен быть чем-то вроде

> A
[1] alpha   foxtrot echo    charlie echo    foxtrot bravo   delta   charlie
Levels: alpha bravo charlie delta echo foxtrot
> B
[1] alpha   foxtrot delta   bravo   bravo   alpha   alpha   bravo   alpha  
Levels: alpha bravo delta foxtrot
> outer(A, B, mapper)
      [,1]   [,2]   [,3]   [,4]   [,5]    [,6]    [,7]    [,8]    [,9]   
 [1,] "red"  "red"  "red"  "red"  "red"   "green" "green" "green" "green"
 [2,] "red"  "red"  "red"  "red"  "red"   "green" "green" "green" "green"
 [3,] "red"  "red"  "red"  "red"  "red"   "green" "green" "green" "green"
 [4,] "red"  "red"  "red"  "red"  "red"   "green" "green" "green" "green"
 [5,] "blue" "blue" "blue" "blue" "blue"  "red"   "green" "green" "blue" 
 [6,] "red"  "red"  "red"  "red"  "green" "green" "green" "green" "green"
 [7,] "red"  "red"  "red"  "red"  "green" "green" "green" "green" "green"
 [8,] "red"  "red"  "red"  "red"  "green" "green" "green" "green" "green"
 [9,] "red"  "red"  "red"  "red"  "green" "green" "green" "green" "green"

Это работает, но в полном объеме это неприятно медленно:

> system.time(outer(A, B, mapper))
   user  system elapsed 
 11.381   5.015  17.653 

Кто-нибудь может порекомендовать более быстрый способ? В случае, если это помогает, матрица отображения гарантированно будет треугольной (т.е. mapping[a,b] == mapping[b,a] ∀ а, б.)

3 ответа

Решение

РЕДАКТИРОВАТЬ: Кажется, вопрос резко изменился, когда я отвечал, но оставить это здесь в любом случае.


Я предполагаю, что комментарий @joran верен, и вы имели в виду (и исправляете порядок в lev)

lev <- c("alpha", "bravo", "charlie", "delta", "echo", "foxtrot")
A <- factor(sample(lev, 6000, TRUE), levels=lev)
B <- factor(sample(lev, 6000, TRUE), levels=lev)

Также, mapping не является двумерным массивом (матрицей) или вложенной структурой данных (списком списков), как вы, кажется, думаете

> mapping
    alpha.alpha     alpha.bravo   alpha.charlie     alpha.delta      alpha.echo 
        "green"          "blue"          "blue"           "red"           "red" 
  alpha.foxtrot     bravo.alpha     bravo.bravo   bravo.charlie     bravo.delta 
          "red"          "blue"         "green"          "blue"           "red" 
     bravo.echo   bravo.foxtrot   charlie.alpha   charlie.bravo charlie.charlie 
          "red"           "red"          "blue"          "blue"         "green" 
  charlie.delta    charlie.echo charlie.foxtrot     delta.alpha     delta.bravo 
          "red"           "red"           "red"           "red"           "red" 
  delta.charlie     delta.delta      delta.echo   delta.foxtrot      echo.alpha 
          "red"         "green"        "yellow"           "red"           "red" 
     echo.bravo    echo.charlie      echo.delta       echo.echo    echo.foxtrot 
          "red"           "red"        "yellow"           "red"           "red" 
  foxtrot.alpha   foxtrot.bravo foxtrot.charlie   foxtrot.delta    foxtrot.echo 
          "red"           "red"           "red"           "red"           "red" 
foxtrot.foxtrot 
        "green" 

Теперь, если вы хотите сохранить это как список списков:

mapping <- list(
    "alpha"   = list("alpha"="green", "bravo"="blue", "charlie"="blue",
                     "delta"="red", "echo"="red", "foxtrot"="red"),
    "bravo"   = list("alpha"="blue", "bravo"="green", "charlie"="blue",
                     "delta"="red", "echo"="red", "foxtrot"="red"),
    "charlie" = list("alpha"="blue", "bravo"="blue", "charlie"="green",
                     "delta"="red", "echo"="red", "foxtrot"="red"),
    "delta"   = list("alpha"="red", "bravo"="red", "charlie"="red",
                     "delta"="green", "echo"="yellow", "foxtrot"="red"),
    "echo"    = list("alpha"="red", "bravo"="red", "charlie"="red",
                     "delta"="yellow", "echo"="red", "foxtrot"="red"),
    "foxtrot" = list("alpha"="red", "bravo"="red", "charlie"="red",
                     "delta"="red", "echo"="red", "foxtrot"="green")
)
mapper = function(X, Y) mapping[[levels(X)[X]]][[levels(Y)[Y]]]

Обратите внимание, что я использую list вместо c в создании mapping и это mapper использует экстрактор ([[) не подмножество ([) обозначение.

Проверка это работает для одного значения:

> mapper(A[1], B[1])
[1] "red"

И только для нескольких значений:

> mapper(A[1:2], B[1:2])
Error in mapping[[levels(X)[X]]][[levels(Y)[Y]]] : 
  attempt to select more than one element

Итак, мы видим mapper не векторизовано (как и должно быть). Со страницы помощи outer:

FUN вызывается с этими двумя расширенными векторами в качестве аргументов. Следовательно, это должна быть векторизованная функция (или имя единицы), ожидающая не менее двух аргументов.

Простой, но не обязательно эффективный способ векторизации:

> Vectorize(mapper)(A[1:2], B[1:2])
[1] "red"   "green"

Теперь это работает на подмножестве:

> outer(A[1:6], B[1:6], FUN=Vectorize(mapper))
     [,1]    [,2]     [,3]    [,4]    [,5]    [,6]    
[1,] "red"   "yellow" "red"   "red"   "red"   "red"   
[2,] "red"   "green"  "red"   "red"   "red"   "yellow"
[3,] "red"   "green"  "red"   "red"   "red"   "yellow"
[4,] "blue"  "red"    "blue"  "red"   "blue"  "red"   
[5,] "green" "red"    "green" "red"   "green" "red"   
[6,] "red"   "red"    "red"   "green" "red"   "red"   

Давайте проверим время:

> system.time(outer(A[1:6], B[1:6], FUN=Vectorize(mapper)))
   user  system elapsed 
      0       0       0 
> system.time(outer(A[1:60], B[1:60], FUN=Vectorize(mapper)))
   user  system elapsed 
   0.22    0.00    0.22 
> system.time(outer(A[1:600], B[1:600], FUN=Vectorize(mapper)))
   user  system elapsed 
  23.97    0.01   24.01 

Выглядит примерно линейно по длине внешнего произведения или квадратично по длине A или B. Я не ждал 40 минут, чтобы посмотреть, сработает ли 6000x6000.

Можем ли мы сделать это намного эффективнее? Двойная индексация в рекурсивную структуру (а затем необходимость использования Vectorize к тому же) это не так эффективно. Давайте использовать другую структуру данных: двумерный массив (матрица) и использовать индексирование на основе матрицы.

mapping <- matrix(c("green", "blue", "blue", "red", "red", "red", 
                    "blue", "green", "blue", "red", "red", "red", 
                    "blue", "blue", "green", "red", "red", "red",
                    "red", "red", "red", "green", "yellow", "red", 
                    "red", "red", "red", "yellow", "red", "red", 
                    "red", "red", "red", "red", "red", "green"),
                  nrow = 6, ncol = 6,
                  dimnames = list(lev, lev))
mapper <- function(X, Y) mapping[cbind(as.character(X), as.character(Y))]

И тестирование это

> A[1:6]
[1] echo    delta   delta   charlie alpha   foxtrot
Levels: alpha bravo charlie echo delta foxtrot
> B[1:6]
[1] alpha   delta   alpha   foxtrot alpha   echo   
Levels: alpha bravo charlie echo delta foxtrot
> mapper(A[1], B[1])
[1] "red"
> mapper(A[1:2], B[1:2])
[1] "red"   "green"
> outer(A[1:6], B[1:6], FUN=mapper)
     [,1]    [,2]     [,3]    [,4]    [,5]    [,6]    
[1,] "red"   "yellow" "red"   "red"   "red"   "red"   
[2,] "red"   "green"  "red"   "red"   "red"   "yellow"
[3,] "red"   "green"  "red"   "red"   "red"   "yellow"
[4,] "blue"  "red"    "blue"  "red"   "blue"  "red"   
[5,] "green" "red"    "green" "red"   "green" "red"   
[6,] "red"   "red"    "red"   "green" "red"   "red"   

Выглядит хорошо. Проверьте сроки:

> system.time(outer(A[1:6], B[1:6], FUN=mapper))
   user  system elapsed 
      0       0       0 
> system.time(outer(A[1:60], B[1:60], FUN=mapper))
   user  system elapsed 
      0       0       0 
> system.time(outer(A[1:600], B[1:600], FUN=mapper))
   user  system elapsed 
   0.22    0.00    0.22 
> system.time(outer(A, B, FUN=mapper))
   user  system elapsed 
   7.80    1.48    9.30 

Чуть более 9 секунд вместо ~40 минут для ускорения в 250 раз.

Так что ваши mapping переменная не совсем правильная. Если вы посмотрите на

str(mapping)
#  Named chr [1:36] "green" "blue" "blue" "red" "red" "red" ...
#  - attr(*, "names")= chr [1:36] "alpha.alpha" "alpha.bravo" "alpha.charlie" ...

Вы увидите, что это одномерный символьный вектор. Там имена элементов вставлены вместе с "." Я предполагаю, что это не то, что вы хотели. Возможно, вы использовали list() скорее, чем c()? Но если вы можете контролировать формат, почему бы не использовать простую матрицу

mapping <- structure(c("green", "blue", "blue", "red", "red", "red", "blue", 
"green", "blue", "red", "red", "red", "blue", "blue", "green", 
"red", "red", "red", "red", "red", "red", "green", "yellow", 
"red", "red", "red", "red", "yellow", "red", "red", "red", "red", 
"red", "red", "red", "green"), .Dim = c(6L, 6L), .Dimnames = list(
    c("alpha", "bravo", "charlie", "echo", "delta", "foxtrot"
    ), c("alpha", "bravo", "charlie", "echo", "delta", "foxtrot"
    )))

Так что есть строка и столбец для каждого значения lev и цвет ячейки - это цвет комбинации.

Тогда если вы делаете

#sample data
lev <- c("alpha", "bravo", "charlie", "echo", "delta", "foxtrot")
A <- factor(sample(lev, 6000, TRUE), levels=lev)
B <- factor(sample(lev, 6000, TRUE), levels=lev)

#run mapping
out <- outer(A, B, FUN=function(a,b) mapping[cbind(a,b)])

Теперь out будет иметь значения A вдоль строк и значения B вдоль столбцов и правильный цвет для взаимодействия между ними в качестве значения ячейки. Это работает довольно быстро

system.time(outer(A, B, FUN=function(a,b) mapping[cbind(a,b)]))

#   user  system elapsed 
#   0.90    0.25    1.15 

Я считаю, что это делает то, что вы хотите, примерно за 4 секунды (работает примерно в 4 раза быстрее, если вы не добавляете имена, но результат выглядит не так хорошо). Обратите внимание, очень важно: это работает, только если уровни одинаковы для A а также B и уровни такие же, как имена mapping.mx, Это потому что cbind приводит факторы к числовому, поэтому отображение является позиционным. Если не так, вы можете принуждать A а также B к характеру, и это будет работать, но будет еще медленнее.

names(A) <- A
names(B) <- B

mapping.mx <- do.call(rbind, mapping.lst)  # see below for mapping.lst
system.time(res <- outer(A, B, function(x, y) mapping.mx[cbind(x, y)]))

# user  system elapsed 
# 3.33    0.62    3.95 

str(res)

# chr [1:6000, 1:6000] "red" "green" "green" "blue" "green" "blue" ...
# - attr(*, "dimnames")=List of 2
#  ..$ : chr [1:6000] "delta" "alpha" "alpha" "bravo" ...
#  ..$ : chr [1:6000] "alpha" "alpha" "echo" "delta" ...

res[1:5, 1:5]

#       alpha   alpha   echo     delta charlie
# delta "red"   "red"   "yellow" "red" "red"  
# alpha "green" "green" "red"    "red" "blue" 
# alpha "green" "green" "red"    "red" "blue" 
# bravo "blue"  "blue"  "red"    "red" "blue" 
# alpha "green" "green" "red"    "red" "blue"     

А также mapping.lst (в принципе, так же как и у вас, но поменял первый c к list):

mapping.lst <- list(
  "alpha"   = c("alpha"="green", "bravo"="blue", "charlie"="blue",
                "delta"="red", "echo"="red", "foxtrot"="red"),
  "bravo"   = c("alpha"="blue", "bravo"="green", "charlie"="blue",
                "delta"="red", "echo"="red", "foxtrot"="red"),
  "charlie" = c("alpha"="blue", "bravo"="blue", "charlie"="green",
                "delta"="red", "echo"="red", "foxtrot"="red"),
  "delta"   = c("alpha"="red", "bravo"="red", "charlie"="red",
                "delta"="green", "echo"="yellow", "foxtrot"="red"),
  "echo"    = c("alpha"="red", "bravo"="red", "charlie"="red",
                "delta"="yellow", "echo"="red", "foxtrot"="red"),
  "foxtrot" = c("alpha"="red", "bravo"="red", "charlie"="red",
                "delta"="red", "echo"="red", "foxtrot"="green")
)
Другие вопросы по тегам