рядок 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))

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змішані типи ...
Calimo

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.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 (дійсно таблицею даних) з мільйонами спостережень та 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))

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
)

Результати:

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)
пан Хопко

І окрім того, що перебуває у пуррлірі, воно вже буде застарілим. Зараз існують інші методи, що поєднують в собі tidyr :: gnezdo, 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над цим функцію застосування списку :ldf <- lapply(as.matrix(myDF), function(x)x)


1

Ще одна альтернатива використання library(purrr)(що здається, трохи швидше для великих фреймів data.fram)

flatten(by_row(xy.df, ..f = function(x) flatten_chr(x), .labels = FALSE))

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кінці вищезазначеного твердження негайно вибирає цей стовпець, повертаючи список списків.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.