Как преобразовать матрицу в список векторов-столбцов в R?
Скажем, вы хотите преобразовать матрицу в список, где каждый элемент списка содержит один столбец. 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)
)
16 ответов
В интересах снятия шкур с кошки, рассматривайте массив как вектор, как если бы он не имел атрибута dim:
split(x, rep(1:ncol(x), each = nrow(x)))
Ответ Гэвина прост и элегантен. Но если столбцов много, гораздо более быстрое решение:
lapply(seq_len(ncol(x)), function(i) x[,i])
Разница в скорости в 6 раз в следующем примере:
> 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
Я считаю, что 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
Использовать asplit
преобразовать матрицу в список векторов
asplit(x, 1) # split into list of row vectors
asplit(x, 2) # split into list of column vectors
Преобразование во фрейм данных, следовательно, в список, кажется, работает:
> 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
С помощью 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"
Я знаю, что это анафема в 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 и оригинальным решением:
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
Новая функция asplit()
будет приходить на базу R в v3.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
Есть функция array_tree()
в Тидиверс 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
(это почти дословная копия моего ответа на подобный вопрос здесь)
convertRowsToList {BBmisc}
Преобразуйте строки (столбцы) data.frame или матрицы в списки.
BBmisc::convertColsToList(x)
ссылка: http://berndbischl.github.io/BBmisc/man/convertRowsToList.html
Под некоторым сайтом справки R, доступным через 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
Ничего не могу сказать о сравнениях производительности или о том, насколько она чиста;-)
Вы могли бы использовать 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
А также естьcollapse::mrtl
(списки строк) иcollapse::mctl
(списки столбцов).collapse
происходит значительно быстрее.
collapse::mrtl(x)
collapse::mctl(x)
Тест:
x <- matrix(1:2e4, ncol = 100)
microbenchmark::microbenchmark(
asplit = asplit(x, MARGIN = 2),
collapse = mctl(x),
split = split(x, rep(1:ncol(x), each = nrow(x))),
lapply = lapply(seq_len(ncol(x)), function(i) x[,i])
)
# Unit: microseconds
# expr min lq mean median uq max neval
# asplit 281.301 318.8005 376.88597 370.4515 421.051 659.300 100
# collapse 20.600 25.3510 46.51694 31.3510 41.351 157.401 100
# split 748.001 935.1515 1144.62000 991.4010 1061.601 14922.301 100
# lapply 139.401 152.0005 205.74196 165.3005 236.901 1319.701 100
В простейшем случае, когда количество столбцов невелико и постоянно, я обнаружил, что самый быстрый вариант - просто жестко закодировать преобразование:
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
Самый простой способ создать список, содержащий столбцы матрицы в качестве элементов, - это использовать тот факт, что объект data.frame в R внутренне представлен как список столбцов. Таким образом, все, что нужно, это следующая строка
mat.list <- as.data.frame(mat)
Читаемый dplyr обновленный подход для того же самого:
x <- matrix(1:10,ncol=2)
library(dplyr)
x %>% as_tibble() %>%
as.list()
$V1
[1] 1 2 3 4 5
$V2
[1] 6 7 8 9 10