Як перетворити матрицю в список стовпців-векторів у R?


80

Скажімо, ви хочете перетворити матрицю в список, де кожен елемент списку містить один стовпець. list()або, as.list()очевидно, не буде працювати, і до цих пір я використовую хак, використовуючи поведінку tapply:

x <- matrix(1:10,ncol=2)

tapply(x,rep(1:ncol(x),each=nrow(x)),function(i)i)

Я не цілком задоволений цим. Хтось знає метод очищення, який я не помічаю?

(для створення списку, заповненого рядками, код, очевидно, можна змінити на:

tapply(x,rep(1:nrow(x),ncol(x)),function(i)i)

)


1
Цікаво, чи може оптимізоване рішення Rccp бути швидшим.
Марек

Відповіді:


68

В інтересах зняття шкури з кота розглядайте масив як вектор, ніби він не має атрибута затемнення:

 split(x, rep(1:ncol(x), each = nrow(x)))

9
Це ядро ​​того, що tapplyробити. Але це простіше :). Ймовірно, буде повільнішим, але приємним на вигляд рішенням split(x, col(x))split(x, row(x))відповідно).
Marek

Я це перевірив. Так само швидко буде split(x, c(col(x))). Але це виглядає гірше.
Marek

2
split (x, col (x)) виглядає краще - неявний примус до вектора чудовий. . .
mdsumner

2
Після багатого тестування це, здається, працює найшвидше, особливо з великою кількістю рядків або стовпців.
Джоріс Мейс

2
Зверніть увагу, що якщо xмає імена стовпців, то імена split(x, col(x, as.factor = TRUE))збережуться.
banbh

73

Відповідь Гевіна проста і елегантна. Але якщо стовпців багато, набагато швидшим рішенням буде:

lapply(seq_len(ncol(x)), function(i) x[,i])

Різниця швидкостей дорівнює 6x у прикладі нижче:

> x <- matrix(1:1e6, 10)
> system.time( as.list(data.frame(x)) )
   user  system elapsed 
   1.24    0.00    1.22 
> system.time( lapply(seq_len(ncol(x)), function(i) x[,i]) )
   user  system elapsed 
    0.2     0.0     0.2 

2
+1 Хороший момент щодо відносної ефективності різних рішень. Найкраща відповідь на даний момент.
Gavin Simpson

Але я думаю, що для отримання однакових результатів вам потрібно зробити lapply (seq_len (nrow (x)), function (i) x [i,]), а потім повільніше.
скан

26

я вважаю, що data.frames зберігаються як списки. Тому примус видається найкращим:

as.list(as.data.frame(x))
> as.list(as.data.frame(x))
$V1
[1] 1 2 3 4 5

$V2
[1]  6  7  8  9 10

Результати бенчмаркінгу цікаві. as.data.frame швидший, ніж data.frame, або тому, що data.frame повинен створити цілком новий об'єкт, або тому, що відстеження імен стовпців є якось дорогим (перегляньте порівняння c (unname ()) проти c () )? Просте рішення, яке надає @Tommy, швидше на порядок. Результати as.data.frame () можна дещо покращити, примушуючи вручну.

manual.coerce <- function(x) {
  x <- as.data.frame(x)
  class(x) <- "list"
  x
}

library(microbenchmark)
x <- matrix(1:10,ncol=2)

microbenchmark(
  tapply(x,rep(1:ncol(x),each=nrow(x)),function(i)i) ,
  as.list(data.frame(x)),
  as.list(as.data.frame(x)),
  lapply(seq_len(ncol(x)), function(i) x[,i]),
  c(unname(as.data.frame(x))),
  c(data.frame(x)),
  manual.coerce(x),
  times=1000
  )

                                                      expr     min      lq
1                                as.list(as.data.frame(x))  176221  183064
2                                   as.list(data.frame(x))  444827  454237
3                                         c(data.frame(x))  434562  443117
4                              c(unname(as.data.frame(x)))  257487  266897
5             lapply(seq_len(ncol(x)), function(i) x[, i])   28231   35929
6                                         manual.coerce(x)  160823  167667
7 tapply(x, rep(1:ncol(x), each = nrow(x)), function(i) i) 1020536 1036790
   median      uq     max
1  186486  190763 2768193
2  460225  471346 2854592
3  449960  460226 2895653
4  271174  277162 2827218
5   36784   37640 1165105
6  171088  176221  457659
7 1052188 1080417 3939286

is.list(manual.coerce(x))
[1] TRUE

Побитий Гавіном за 5 секунд. Проклятий ти, екран "Ти людина"? :-)
Арі Б. Фрідман,

1
На щастя жеребкування, я здогадуюсь, я просто переглядав це після того, як @Joris прокрався попереду мене, відповідаючи на запитання Пертера Флома. Крім того, as.data.frame()втрачає імена кадру даних, тому data.frame()стає трохи приємніше.
Gavin Simpson

2
Еквівалент manual.coerce(x)може бути unclass(as.data.frame(x)).
Marek

Дякую Марек. Це приблизно на 6% швидше, мабуть, тому, що я можу уникнути використання функції / виклику функції.
Арі Б. Фрідман,

16

Перетворення на фрейм даних звідти на список, здається, працює:

> as.list(data.frame(x))
$X1
[1] 1 2 3 4 5

$X2
[1]  6  7  8  9 10
> str(as.list(data.frame(x)))
List of 2
 $ X1: int [1:5] 1 2 3 4 5
 $ X2: int [1:5] 6 7 8 9 10

12

Використання plyrможе бути дуже корисним для таких речей:

library("plyr")

alply(x,2)

$`1`
[1] 1 2 3 4 5

$`2`
[1]  6  7  8  9 10

attr(,"class")
[1] "split" "list" 

6

Я знаю, що це анафема в R, і я насправді не маю великої репутації, щоб підтвердити це, але я знаходжу цикл for, щоб бути досить ефективним. Я використовую наступну функцію для перетворення матриці mat в список її стовпців:

mat2list <- function(mat)
{
    list_length <- ncol(mat)
    out_list <- vector("list", list_length)
    for(i in 1:list_length) out_list[[i]] <- mat[,i]
    out_list
}

Швидке порівняння порівняння з mdsummer's та оригінальним рішенням:

x <- matrix(1:1e7, ncol=1e6)

system.time(mat2list(x))
   user  system elapsed 
  2.728   0.023   2.720 

system.time(split(x, rep(1:ncol(x), each = nrow(x))))
   user  system elapsed 
  4.812   0.194   4.978 

system.time(tapply(x,rep(1:ncol(x),each=nrow(x)),function(i)i))
   user  system elapsed 
 11.471   0.413  11.817 

Звичайно, це знижує назви стовпців, але, схоже, вони не були важливими у вихідному питанні.
alfymbohm

2
Рішення Томмі швидше і компактніше:system.time( lapply(seq_len(ncol(x)), function(i) x[,i]) ) user: 1.668 system: 0.016 elapsed: 1.693
alfymbohm

Спроба зрозуміти це в іншому контексті, не працює: stackoverflow.com/questions/63801018 .... шукає це:vec2 = castMatrixToSequenceOfLists(vecs);
mshaffer

5

Нова функція asplit()перейде на базу R у версії 3.6. До тих пір, як і у відповіді @mdsumner, ми також можемо це робити

split(x, slice.index(x, MARGIN))

згідно з документами asplit(). Однак, як було показано раніше, всі split()засновані рішення набагато повільніші, ніж @ Tommy's lapply/`[`. Це також стосується нового asplit(), принаймні в його нинішньому вигляді.

split_1 <- function(x) asplit(x, 2L)
split_2 <- function(x) split(x, rep(seq_len(ncol(x)), each = nrow(x)))
split_3 <- function(x) split(x, col(x))
split_4 <- function(x) split(x, slice.index(x, 2L))
split_5 <- function(x) lapply(seq_len(ncol(x)), function(i) x[, i])

dat <- matrix(rnorm(n = 1e6), ncol = 100)

#> Unit: milliseconds
#>          expr       min        lq     mean   median        uq        max neval
#>  split_1(dat) 16.250842 17.271092 20.26428 18.18286 20.185513  55.851237   100
#>  split_2(dat) 52.975819 54.600901 60.94911 56.05520 60.249629 105.791117   100
#>  split_3(dat) 32.793112 33.665121 40.98491 34.97580 39.409883  74.406772   100
#>  split_4(dat) 37.998140 39.669480 46.85295 40.82559 45.342010  80.830705   100
#>  split_5(dat)  2.622944  2.841834  3.47998  2.88914  4.422262   8.286883   100

dat <- matrix(rnorm(n = 1e6), ncol = 1e5)

#> Unit: milliseconds
#>          expr       min       lq     mean   median       uq      max neval
#>  split_1(dat) 204.69803 231.3023 261.6907 246.4927 289.5218 413.5386   100
#>  split_2(dat) 229.38132 235.3153 253.3027 242.0433 259.2280 339.0016   100
#>  split_3(dat) 208.29162 216.5506 234.2354 221.7152 235.3539 342.5918   100
#>  split_4(dat) 214.43064 221.9247 240.7921 231.0895 246.2457 323.3709   100
#>  split_5(dat)  89.83764 105.8272 127.1187 114.3563 143.8771 209.0670   100

4

Використовується asplitдля перетворення матриці у список векторів

asplit(x, 1) # split into list of row vectors
asplit(x, 2) # split into list of column vectors

3

У пакеті array_tree()tidyverse є функція, purrrяка робить це з мінімумом суєти:

x <- matrix(1:10,ncol=2)
xlist <- purrr::array_tree(x, margin=2)
xlist

#> [[1]]
#> [1] 1 2 3 4 5
#>  
#> [[2]]
#> [1]  6  7  8  9 10

margin=1Натомість використовуйте для списку за рядками. Працює для n-мірних масивів. Він зберігає імена за замовчуванням:

x <- matrix(1:10,ncol=2)
colnames(x) <- letters[1:2]
xlist <- purrr::array_tree(x, margin=2)
xlist

#> $a
#> [1] 1 2 3 4 5
#>
#> $b
#> [1]  6  7  8  9 10

(це майже дословна копія моєї відповіді на подібне запитання тут )


2

Під веб-сайтом Some R Help, доступним через nabble.com, я знаходжу:

c(unname(as.data.frame(x))) 

як дійсне рішення і в моїй установці R v2.13.0 це виглядає нормально:

> y <- c(unname(as.data.frame(x)))
> y
[[1]]
[1] 1 2 3 4 5

[[2]]
[1]  6  7  8  9 10

Не можу сказати нічого про порівняння продуктивності чи наскільки це чисто ;-)


2
Цікаво. Я думаю, це також працює за допомогою примусу. c(as.data.frame(x))поводиться ідентично поведінціas.list(as.data.frame(x)
Арі Б. Фрідман

Я думаю, що це так, оскільки члени зразків списків / матриць є однотипними, але я не є експертом.
Дилетант,

2

Ви можете використовувати, applyа потім cіdo.call

x <- matrix(1:10,ncol=2)
do.call(c, apply(x, 2, list))
#[[1]]
#[1] 1 2 3 4 5
#
#[[2]]
#[1]  6  7  8  9 10

І схоже, він збереже імена стовпців при додаванні до матриці.

colnames(x) <- c("a", "b")
do.call(c, apply(x, 2, list))
#$a
#[1] 1 2 3 4 5
#
#$b
#[1]  6  7  8  9 10

5
абоunlist(apply(x, 2, list), recursive = FALSE)
баптист

Так. Ви повинні додати це як відповідь @baptiste.
Rich Scriven

1
але для цього знадобиться прокрутка до кінця сторінки! я занадто лінивий для цього
баптист

На моїй машині є кнопка "END" ... :-)
Річ Скривен

Думаю, це, мабуть, також можна зробити, створивши порожній список та заповнивши його. y <- vector("list", ncol(x))а потім щось на зразок y[1:2] <- x[,1:2], хоча це не працює точно так.
Rich Scriven


1

У тривіальному випадку, коли кількість стовпців невелика і постійна, то я виявив, що найшвидшим варіантом є просто жорстке кодування перетворення:

mat2list  <- function (mat) lapply(1:2, function (i) mat[, i])
mat2list2 <- function (mat) list(mat[, 1], mat[, 2])


## Microbenchmark results; unit: microseconds
#          expr   min    lq    mean median    uq    max neval
##  mat2list(x) 7.464 7.932 8.77091  8.398 8.864 29.390   100
## mat2list2(x) 1.400 1.867 2.48702  2.333 2.333 27.525   100
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.