Wie konvertiere ich eine Matrix in eine Liste von Spaltenvektoren in R?

79

Angenommen, Sie möchten eine Matrix in eine Liste konvertieren, in der jedes Element der Liste eine Spalte enthält. list()oder as.list()offensichtlich nicht funktionieren, und bis jetzt benutze ich einen Hack mit dem Verhalten von tapply:

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

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

Ich bin damit nicht ganz zufrieden. Kennt jemand eine sauberere Methode, die ich übersehen habe?

(Um eine Liste mit den Zeilen zu erstellen, kann der Code natürlich geändert werden in:

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

)

Joris Meys
quelle
1
Ich frage mich, ob eine optimierte Rccp-Lösung schneller sein könnte.
Marek

Antworten:

67

Behandeln Sie das Array im Interesse des Enthäutens der Katze als Vektor, als hätte es kein dim-Attribut:

 split(x, rep(1:ncol(x), each = nrow(x)))
mdsumner
quelle
9
Dies ist der Kern dessen, was zu tapplytun ist. Aber es ist einfacher :). Wahrscheinlich wird eine langsamere, aber gut aussehende Lösung sein split(x, col(x))( split(x, row(x))bzw.).
Marek
Ich habe nachgeschaut. Genauso schnell wird es sein split(x, c(col(x))). Aber es sieht schlimmer aus.
Marek
2
split (x, col (x)) sieht besser aus - impliziter Zwang zum Vektor ist in Ordnung. . .
Mdsumner
2
Nach vielen Tests scheint dies am schnellsten zu funktionieren, insbesondere bei vielen Zeilen oder Spalten.
Joris Meys
2
Beachten Sie, dass bei xSpaltennamen die Namen split(x, col(x, as.factor = TRUE))beibehalten werden.
Banbh
73

Gavins Antwort ist einfach und elegant. Wenn es jedoch viele Spalten gibt, wäre eine viel schnellere Lösung:

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

Der Geschwindigkeitsunterschied beträgt im folgenden Beispiel 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 
Tommy
quelle
2
+1 Guter Punkt zur relativen Effizienz der verschiedenen Lösungen. Die bisher beste Antwort.
Gavin Simpson
Aber ich denke, um die gleichen Ergebnisse zu erzielen, müssen Sie lapply (seq_len (nrow (x)), function (i) x [i,]) ausführen und sind dann langsamer.
Skan
26

Ich glaube, data.frames werden als Listen gespeichert. Daher scheint Zwang am besten:

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

Benchmarking-Ergebnisse sind interessant. as.data.frame ist schneller als data.frame, entweder weil data.frame ein ganz neues Objekt erstellen muss oder weil das Verfolgen der Spaltennamen irgendwie kostspielig ist (Zeuge des Vergleichs von c (unname ()) gegen c () )? Die von @Tommy bereitgestellte Lösung ist um eine Größenordnung schneller. Die Ergebnisse von as.data.frame () können durch manuelles Erzwingen etwas verbessert werden.

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
Ari B. Friedman
quelle
Von Gavin um 5 Sekunden geschlagen. Verdammt, "Bist du ein Mensch?" :-)
Ari B. Friedman
1
Glück der Auslosung, denke ich, ich habe das gerade gesehen, nachdem sich @Joris vor mir eingeschlichen hat und Perter Floms Frage beantwortet hat. Außerdem as.data.frame()verliert er die Namen des Datenrahmens , data.frame()ist also etwas schöner.
Gavin Simpson
2
Äquivalent von manual.coerce(x)könnte sein unclass(as.data.frame(x)).
Marek
Danke Marek. Das ist ungefähr 6% schneller, vermutlich weil ich die Verwendung einer Funktionsdefinition / eines Funktionsaufrufs vermeiden kann.
Ari B. Friedman
16

Das Konvertieren in einen Datenrahmen von dort in eine Liste scheint zu funktionieren:

> 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
Gavin Simpson
quelle
12

Die Verwendung plyrkann für solche Dinge sehr nützlich sein:

library("plyr")

alply(x,2)

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

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

attr(,"class")
[1] "split" "list" 
Sacha Epskamp
quelle
6

Ich weiß, dass dies ein Anathema in R ist, und ich habe nicht wirklich den Ruf, dies zu belegen, aber ich finde, dass eine for-Schleife effizienter ist. Ich verwende die folgende Funktion, um die Matrixmatte in eine Liste ihrer Spalten zu konvertieren:

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
}

Schneller Benchmark im Vergleich zu mdsummer's und der ursprünglichen Lösung:

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
quelle
Natürlich werden dadurch Spaltennamen gelöscht, aber es scheint nicht, dass sie in der ursprünglichen Frage wichtig waren.
Alfymbohm
2
Tommys Lösung ist schneller und kompakter:system.time( lapply(seq_len(ncol(x)), function(i) x[,i]) ) user: 1.668 system: 0.016 elapsed: 1.693
Alfymbohm
Der Versuch, dies in einem anderen Kontext herauszufinden, funktioniert nicht: stackoverflow.com/questions/63801018 .... auf der Suche nach:vec2 = castMatrixToSequenceOfLists(vecs);
mshaffer
5

Die neue Funktion asplit()wird in Version 3.6 auf Basis R kommen. Bis dahin und in ähnlicher Weise wie die Antwort von @mdsumner können wir dies auch tun

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

gemäß den Dokumenten von asplit(). Wie bereits gezeigt, sind alle split()basierten Lösungen viel langsamer als die von @ Tommy lapply/`[`. Dies gilt auch für das Neue asplit(), zumindest in seiner jetzigen Form.

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
nbenn
quelle
4

Verwenden Sie asplitdiese Option , um eine Matrix in eine Liste von Vektoren zu konvertieren

asplit(x, 1) # split into list of row vectors
asplit(x, 2) # split into list of column vectors
Daniel Freeman
quelle
3

array_tree()Das purrrPaket von tidyverse enthält eine Funktion , die dies mit minimalem Aufwand erledigt:

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

Verwenden Sie margin=1diese Option, um stattdessen nach Zeilen aufzulisten. Funktioniert für n-dimensionale Arrays. Standardmäßig werden Namen beibehalten:

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

(Dies ist eine fast wörtliche Kopie meiner Antwort auf eine ähnliche Frage hier )

wjchulme
quelle
2

Unter Some R Help Site, auf die über nabble.com zugegriffen werden kann, finde ich:

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

Als gültige Lösung und in meiner R v2.13.0-Installation sieht dies in Ordnung aus:

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

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

Ich kann nichts über Leistungsvergleiche sagen oder wie sauber es ist ;-)

Dilettant
quelle
2
Interessant. Ich denke, das funktioniert auch durch Zwang. c(as.data.frame(x))erzeugt identisches Verhalten wieas.list(as.data.frame(x)
Ari B. Friedman
Ich denke, dass dies so ist, weil die Mitglieder der Beispiellisten / Matrix vom gleichen Typ sind, aber ich bin kein Experte.
Dilettant
2

Sie könnten verwenden applyund dann cmitdo.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

Und es sieht so aus, als würden die Spaltennamen beibehalten, wenn sie der Matrix hinzugefügt werden.

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
Rich Scriven
quelle
5
oderunlist(apply(x, 2, list), recursive = FALSE)
Taufe
Ja. Sie sollten das als Antwort @baptiste hinzufügen.
Rich Scriven
1
Dafür müsste man jedoch zum Ende der Seite scrollen! Dafür bin ich viel zu faul
Baptiste
Es gibt eine "END" -Taste auf meinem Computer ... :-)
Rich Scriven
Ich denke, dies kann wahrscheinlich auch dadurch erreicht werden, dass eine leere Liste erstellt und aufgefüllt wird. y <- vector("list", ncol(x))und dann etwas in der Art von y[1:2] <- x[,1:2], obwohl es nicht genau so funktioniert.
Rich Scriven
1

In dem trivialen Fall, in dem die Anzahl der Spalten klein und konstant ist, habe ich festgestellt, dass die schnellste Option darin besteht, die Konvertierung einfach hart zu codieren:

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
ms609
quelle