data.frame строки в список

123

У меня есть data.frame, который я хотел бы преобразовать в список по строкам, то есть каждая строка будет соответствовать своим собственным элементам списка. Другими словами, мне нужен список, который будет содержать строки в data.frame.

До сих пор я решал эту проблему следующим образом, но мне было интересно, есть ли лучший способ подойти к этому.

xy.df <- data.frame(x = runif(10),  y = runif(10))

# pre-allocate a list and fill it with a loop
xy.list <- vector("list", nrow(xy.df))
for (i in 1:nrow(xy.df)) {
    xy.list[[i]] <- xy.df[i,]
}
Роман Луштрик
источник

Ответы:

164

Как это:

xy.list <- split(xy.df, seq(nrow(xy.df)))

И если вы хотите, чтобы имена строк xy.dfбыли именами выходного списка, вы можете сделать:

xy.list <- setNames(split(xy.df, seq(nrow(xy.df))), rownames(xy.df))
flodel
источник
4
Обратите внимание, что после использования splitкаждого элемента data.frame with 1 rows and N columnsвместоlist of length N
Кароль Данилюк
Я бы только добавил, что если вы используете, splitвам, вероятно, следует поступить drop=Tиначе, ваши исходные уровни факторов не упадут
Денис
51

Эврика!

xy.list <- as.list(as.data.frame(t(xy.df)))
Роман Луштрик
источник
1
Хотите продемонстрировать, как использовать приложение?
Роман Луштрик
3
unlist(apply(xy.df, 1, list), recursive = FALSE), Однако решение Флоделя более эффективно, чем использование applyили t.
Арун
11
Проблема здесь заключается в том , что tпреобразует data.fameв matrixтак, чтобы элементы в списке являются атомными векторами, а не список , как просили ОП. Обычно это не проблема, пока у вас не будут xy.dfсмешанные типы ...
Калимо
2
Если вы хотите перебирать значения, я не рекомендую apply. На самом деле это просто цикл for, реализованный в R. Он lapplyвыполняет цикл в C, что значительно быстрее. Этот формат списка строк на самом деле предпочтительнее, если вы делаете много циклов.
Лиз Сандер
1
Добавляю еще один комментарий из будущего, applyверсия.mapply(data.frame, xy.df, NULL)
alexis_laz
15

Если вы хотите полностью злоупотребить data.frame (как это делаю я) и хотите сохранить функциональность $, один из способов - разбить data.frame на однострочные data.frames, собранные в список:

> df = data.frame(x=c('a','b','c'), y=3:1)
> df
  x y
1 a 3
2 b 2
3 c 1

# 'convert' into a list of data.frames
ldf = lapply(as.list(1:dim(df)[1]), function(x) df[x[1],])

> ldf
[[1]]
x y
1 a 3    
[[2]]
x y
2 b 2
[[3]]
x y
3 c 1

# and the 'coolest'
> ldf[[2]]$y
[1] 2

Это не только интеллектуальная мастурбация, но и позволяет «преобразовать» data.frame в список его строк, сохраняя $ indexation, что может быть полезно для дальнейшего использования с lapply (при условии, что функция, которую вы передаете lapply, использует эту $ indexation)

Цио Би
источник
Как нам снова собрать их? Превратить список data.frames в единый data.frame?
Аарон МакДэйд
4
@AaronMcDaid Вы можете использовать do.call и rbind: df == do.call ("rbind", ldf)
random_forest_fanatic
@AaronMcDaid Или data.table :: rbindlist (). Если исходный фрейм данных был большим, прирост скорости будет значительным.
Empiromancer
8

Более современное решение использует только purrr::transpose:

library(purrr)
iris[1:2,] %>% purrr::transpose()
#> [[1]]
#> [[1]]$Sepal.Length
#> [1] 5.1
#> 
#> [[1]]$Sepal.Width
#> [1] 3.5
#> 
#> [[1]]$Petal.Length
#> [1] 1.4
#> 
#> [[1]]$Petal.Width
#> [1] 0.2
#> 
#> [[1]]$Species
#> [1] 1
#> 
#> 
#> [[2]]
#> [[2]]$Sepal.Length
#> [1] 4.9
#> 
#> [[2]]$Sepal.Width
#> [1] 3
#> 
#> [[2]]$Petal.Length
#> [1] 1.4
#> 
#> [[2]]$Petal.Width
#> [1] 0.2
#> 
#> [[2]]$Species
#> [1] 1
Майк Стэнли
источник
8

Я работал над этим сегодня для data.frame (на самом деле data.table) с миллионами наблюдений и 35 столбцами. Моя цель состояла в том, чтобы вернуть список data.frames (data.tables), каждый с одной строкой. То есть я хотел разделить каждую строку на отдельный data.frame и сохранить их в списке.

Вот два метода, которые я придумал, которые были примерно в 3 раза быстрее, чем split(dat, seq_len(nrow(dat)))для этого набора данных. Ниже я сравниваю эти три метода с набором данных из 7500 строк и 5 столбцов ( диафрагма повторяется 50 раз).

library(data.table)
library(microbenchmark)

microbenchmark(
split={dat1 <- split(dat, seq_len(nrow(dat)))},
setDF={dat2 <- lapply(seq_len(nrow(dat)),
                  function(i) setDF(lapply(dat, "[", i)))},
attrDT={dat3 <- lapply(seq_len(nrow(dat)),
           function(i) {
             tmp <- lapply(dat, "[", i)
             attr(tmp, "class") <- c("data.table", "data.frame")
             setDF(tmp)
           })},
datList = {datL <- lapply(seq_len(nrow(dat)),
                          function(i) lapply(dat, "[", i))},
times=20
) 

Это возвращает

Unit: milliseconds
       expr      min       lq     mean   median        uq       max neval
      split 861.8126 889.1849 973.5294 943.2288 1041.7206 1250.6150    20
      setDF 459.0577 466.3432 511.2656 482.1943  500.6958  750.6635    20
     attrDT 399.1999 409.6316 461.6454 422.5436  490.5620  717.6355    20
    datList 192.1175 201.9896 241.4726 208.4535  246.4299  411.2097    20

Хотя различия не так велики, как в моем предыдущем тесте, прямой setDFметод значительно быстрее на всех уровнях распределения прогонов с max (setDF) <min (split), а attrметод обычно более чем в два раза быстрее.

Четвертый метод - это крайний чемпион, который представляет собой простой вложенный lapply, возвращающий вложенный список. Этот метод иллюстрирует стоимость создания data.frame из списка. Более того, все методы, которые я пробовал с этой data.frameфункцией, были примерно на порядок медленнее, чем data.tableметоды.

данные

dat <- vector("list", 50)
for(i in 1:50) dat[[i]] <- iris
dat <- setDF(rbindlist(dat))
LMO
источник
6

Кажется, текущая версия пакета purrr(0.2.2) - самое быстрое решение:

by_row(x, function(v) list(v)[[1L]], .collate = "list")$.out

Сравним самые интересные решения:

data("Batting", package = "Lahman")
x <- Batting[1:10000, 1:10]
library(benchr)
library(purrr)
benchmark(
    split = split(x, seq_len(.row_names_info(x, 2L))),
    mapply = .mapply(function(...) structure(list(...), class = "data.frame", row.names = 1L), x, NULL),
    purrr = by_row(x, function(v) list(v)[[1L]], .collate = "list")$.out
)

Rsults:

Benchmark summary:
Time units : milliseconds 
  expr n.eval   min  lw.qu median   mean  up.qu  max  total relative
 split    100 983.0 1060.0 1130.0 1130.0 1180.0 1450 113000     34.3
mapply    100 826.0  894.0  963.0  972.0 1030.0 1320  97200     29.3
 purrr    100  24.1   28.6   32.9   44.9   40.5  183   4490      1.0

Также мы можем получить тот же результат с Rcpp:

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
List df2list(const DataFrame& x) {
    std::size_t nrows = x.rows();
    std::size_t ncols = x.cols();
    CharacterVector nms = x.names();
    List res(no_init(nrows));
    for (std::size_t i = 0; i < nrows; ++i) {
        List tmp(no_init(ncols));
        for (std::size_t j = 0; j < ncols; ++j) {
            switch(TYPEOF(x[j])) {
                case INTSXP: {
                    if (Rf_isFactor(x[j])) {
                        IntegerVector t = as<IntegerVector>(x[j]);
                        RObject t2 = wrap(t[i]);
                        t2.attr("class") = "factor";
                        t2.attr("levels") = t.attr("levels");
                        tmp[j] = t2;
                    } else {
                        tmp[j] = as<IntegerVector>(x[j])[i];
                    }
                    break;
                }
                case LGLSXP: {
                    tmp[j] = as<LogicalVector>(x[j])[i];
                    break;
                }
                case CPLXSXP: {
                    tmp[j] = as<ComplexVector>(x[j])[i];
                    break;
                }
                case REALSXP: {
                    tmp[j] = as<NumericVector>(x[j])[i];
                    break;
                }
                case STRSXP: {
                    tmp[j] = as<std::string>(as<CharacterVector>(x[j])[i]);
                    break;
                }
                default: stop("Unsupported type '%s'.", type2name(x));
            }
        }
        tmp.attr("class") = "data.frame";
        tmp.attr("row.names") = 1;
        tmp.attr("names") = nms;
        res[i] = tmp;
    }
    res.attr("names") = x.attr("row.names");
    return res;
}

Теперь сравните с purrr:

benchmark(
    purrr = by_row(x, function(v) list(v)[[1L]], .collate = "list")$.out,
    rcpp = df2list(x)
)

Полученные результаты:

Benchmark summary:
Time units : milliseconds 
 expr n.eval  min lw.qu median mean up.qu   max total relative
purrr    100 25.2  29.8   37.5 43.4  44.2 159.0  4340      1.1
 rcpp    100 19.0  27.9   34.3 35.8  37.2  93.8  3580      1.0
Артем Клевцов
источник
Тестирование крошечного набора данных из 150 строк не имеет особого смысла, поскольку никто не заметит никакой разницы в микросекундах, и он не масштабируется
Дэвид Аренбург,
4
by_row()перемещен вlibrary(purrrlyr)
MrHopko
И помимо того, что он находится в мурлыканье, он скоро станет устаревшим. Теперь есть другие методы, объединяющие tidyr :: nest, dplyr :: mutate purrr :: map для достижения того же результата
Майк Стэнли
3

Еще пара вариантов:

С участием asplit

asplit(xy.df, 1)
#[[1]]
#     x      y 
#0.1137 0.6936 

#[[2]]
#     x      y 
#0.6223 0.5450 

#[[3]]
#     x      y 
#0.6093 0.2827 
#....

С splitиrow

split(xy.df, row(xy.df)[, 1])

#$`1`
#       x      y
#1 0.1137 0.6936

#$`2`
#       x     y
#2 0.6223 0.545

#$`3`
#       x      y
#3 0.6093 0.2827
#....

данные

set.seed(1234)
xy.df <- data.frame(x = runif(10),  y = runif(10))
Ронак Шах
источник
2

Лучшим способом для меня было:

Пример данных:

Var1<-c("X1",X2","X3")
Var2<-c("X1",X2","X3")
Var3<-c("X1",X2","X3")

Data<-cbind(Var1,Var2,Var3)

ID    Var1   Var2  Var3 
1      X1     X2    X3
2      X4     X5    X6
3      X7     X8    X9

Мы называем BBmiscбиблиотеку

library(BBmisc)

data$lists<-convertRowsToList(data[,2:4])

И результат будет:

ID    Var1   Var2  Var3  lists
1      X1     X2    X3   list("X1", "X2", X3") 
2      X4     X5    X6   list("X4","X5", "X6") 
3      X7     X8    X9   list("X7,"X8,"X9) 
Кроманьонец
источник
1

Альтернативный способ - преобразовать df в матрицу, а затем применить к ней lappyфункцию list apply :ldf <- lapply(as.matrix(myDF), function(x)x)

user3553260
источник
1

Другой вариант использования library(purrr)(который, кажется, немного быстрее на больших data.frames)

flatten(by_row(xy.df, ..f = function(x) flatten_chr(x), .labels = FALSE))
MrHopko
источник
3
`by_row ()` теперь перемещен в `library (purrrlyr)`
MrHopko
1

Как писал @flodel: Это преобразует ваш фрейм данных в список, который имеет то же количество элементов, что и количество строк в фрейме данных:

NewList <- split(df, f = seq(nrow(df)))

Вы можете дополнительно добавить функцию для выбора только тех столбцов, которые не являются NA в каждом элементе списка:

NewList2 <- lapply(NewList, function(x) x[,!is.na(x)])
Михал
источник
0

by_rowФункция из purrrlyrпакета будет делать это для вас.

Этот пример демонстрирует

myfn <- function(row) {
  #row is a tibble with one row, and the same number of columns as the original df
  l <- as.list(row)
  return(l)
}

list_of_lists <- purrrlyr::by_row(df, myfn, .labels=FALSE)$.out

По умолчанию возвращаемое значение из myfnпомещается в новый столбец списка в вызываемой df .out. В $.outконце приведенного выше оператора немедленно выбирается этот столбец, возвращая список списков.

RobinL
источник