R Преобразовать список в Data.Frame или таблицу - PullRequest
4 голосов
/ 05 апреля 2020
list1 = list(
  c(4,5,6,7,1,1,1,1,3,1,3,3),
  c(3,4,5,6,2,2,2,2,1,4,2,1),
  c(1,2,3,4,1,1,1,1,3,2,1,1),
  c(5,6,7,8,1,1,1,1,4,4,4,3),
  c(2,3,4,5,2,2,2,2,2,1,2,1)
)

data1=data.frame("ID"=c(1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5),
"Time"=c(1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4),
"Grade"=c(4,5,6,7,3,4,5,6,1,2,3,4,5,6,7,8,2,3,4,5),
"Class"=c(1,1,1,1,2,2,2,2,1,1,1,1,1,1,1,1,2,2,2,2),
"Score"=c(3,1,3,3,1,4,2,1,3,2,1,1,4,4,4,3,2,1,2,1))

У меня есть «список1». Каждый элемент в «списке1» соответствует баллу, классу и баллу одного человека за 4 года. Таким образом, «list1» имеет 5 учеников и 12 записей для каждого ученика (4 записи для каждой из трех переменных: класс, класс и балл). Я бы sh превратил 'list1' в 'data1', который является длинным файлом данных, где 'ID' равен номеру элемента списка в 'list1'. Время равно времени записи (у каждого учащегося есть 4 такта), Оценка равна первым 4 точкам данных во ВСЕХ элементах списка 1, Класс 4 и Оценка за последние 4.

Пример вывода показано превращение «list1» в желаемый вывод «data1».

Этот набор данных ОГРОМНЫЙ, поэтому я надеюсь на эффективный подход к выполнению этого преобразования.

Ответы [ 5 ]

2 голосов
/ 06 апреля 2020

Вот еще одно базовое решение, которое очень быстро. Это менее элегантно, но идея в том, что мы минимизируем использование памяти, заполняя матрицу al oop.

mat = matrix(0, nrow = length(list1) * 4L, ncol = 5L, dimnames = list(NULL, c("ID", "Time", "Grade", "Class", "Score")))

rw = 0L
times = 1:4

for (i in seq_along(list1)) {
  l = list1[[i]]
  new_rw = length(l) / 3
  inds = seq_len(new_rw) + rw

  mat[inds, 1L] = i
  mat[inds, 2L] = times
  mat[inds, 3:5] = matrix(l, ncol = 3L)

  rw = new_rw + rw
}

А вот более быстрый способ, который выводит из списка, а затем создает матрицу, выбирая наши незарегистрированные элементы в определенном порядке:

n = length(list1)
matrix(unlist(list1, use.names = FALSE)[rep(rep(1:4, n) + 12 * rep(0:(n-1L), each = 4), 3) + rep(c(0, 4, 8), each = n * 4L)], ncol = 3)

Затем, наконец, если вам все еще нужна скорость, Rcpp можно использовать:

Rcpp::cppFunction(
  " NumericMatrix rcpp_combo(List x) {
  NumericMatrix out(x.size() * 4, 5);
  int init = 0;

  for (int i = 0; i < x.size(); i++) {
    NumericVector tmp = x(i);
    int ID = i + 1;
    for (int j = 0; j < 4; j++) {
      int ind = j + init;

      out(ind, 0) = ID;
      out(ind, 1) = j + 1;
      out(ind, 2) = tmp(j);
      out(ind, 3) = tmp(4 + j);
      out(ind, 4) = tmp(8 + j);
    }
    init += 4;
  }
  return(out);
}"
)
rcpp_combo(list1)    

При использовании тестов @ Sathi sh эти методы находятся в диапазоне от 0,05 до 2 секунд.

big_list <- unlist(mget(x = rep('list1', 100000)), recursive = FALSE)

system.time(rcpp_combo(big_list))
##   user  system elapsed 
##   0.07    0.00    0.06 

system.time({
  n = length(big_list)
  mat2 = matrix(unlist(big_list, use.names = FALSE)[rep(rep(1:4, n) + 12 * rep(0:(n-1L), each = 4), 3) + rep(c(0, 4, 8), each = n * 4L)], ncol = 3)
})
##   user  system elapsed 
##   0.20    0.02    0.22 

big_list <- unlist(mget(x = rep('list1', 100000)), recursive = FALSE)
system.time({
mat = matrix(0, nrow = length(big_list) * 4L, ncol = 5L, dimnames = list(NULL, c("ID", "Time", "Grade", "Class", "Score")))
rw = 0L
times = 1:4
for (i in seq_along(big_list)) {
  l = big_list[[i]]
  new_rw = length(l) / 3
  inds = seq_len(new_rw) + rw
  mat[inds, 1L] = i
  mat[inds, 2L] = times
  mat[inds, 3:5] = matrix(l, ncol = 3L)
  rw = new_rw + rw
}
})
##   user  system elapsed 
##   2.08    0.03    2.21
2 голосов
/ 05 апреля 2020

Я не уверен, что это будет эффективно, но это сжато:

setDT(list1)
# could also do something like paste0('student', 1:5) for clarity,
#   and adjust patterns() below accordingly
setnames(list1, paste0(1:5))
# 4 = # of values of Time
list1[ , colid := rep(c('Grade', 'Class', 'Score'), each = 4L)]
# 3 = # of columns "stacked" in each student's column initially
list1[ , Time := rep(1:4, 3L)]
# first, reshape long
list1[ , melt(.SD, measure.vars = patterns('^[0-9]+'), variable.name = 'ID',
              variable.factor = FALSE)
       # now, reshape to the final format
       ][ , dcast(.SD, ID + Time ~ colid, value.var = 'value')]
#         ID  Time Class Grade Score
#     <char> <int> <num> <num> <num>
#  1:      1     1     1     4     3
#  2:      1     2     1     5     1
#  3:      1     3     1     6     3
#  4:      1     4     1     7     3
#  5:      2     1     2     3     1
#  6:      2     2     2     4     4
#  7:      2     3     2     5     2
#  8:      2     4     2     6     1
#  9:      3     1     1     1     3
# 10:      3     2     1     2     2
# 11:      3     3     1     3     1
# 12:      3     4     1     4     1
# 13:      4     1     1     5     4
# 14:      4     2     1     6     4
# 15:      4     3     1     7     4
# 16:      4     4     1     8     3
# 17:      5     1     2     2     2
# 18:      5     2     2     3     1
# 19:      5     3     2     4     2
# 20:      5     4     2     5     1
#         ID  Time Class Grade Score

Неэффективность может возникнуть из-за двух операций здесь.

Подход к построению скелета таблицы первым затем его заполнение может быть быстрее, например так:

# 4 = # of Times per ID&Column (assuming your table is rectangular)
out = CJ(ID = 1:length(list1), Time = 1:4)
# relies on ID being an integer, so that ID = 1 --> list1[[1]]
#   gives ID=1's data
out[ , by = ID, c('Grade', 'Class', 'Score') := {
  as.data.table(matrix(list1[[ .BY$ID ]], ncol = 3L))
}]

Возможно, as.data.table также неэффективен, но этот код более читабелен, чем альтернатива:

out = CJ(ID = 1:length(list1), Time = 1:4)
out[ , by = ID, c('Grade', 'Class', 'Score') := {
  student_data = list1[[.BY$ID]]
  lapply(1:3, function(j) student_data[4L*(j-1) + 1:4])
}]
1 голос
/ 05 апреля 2020

Используя базу R, мы можем перебрать индекс list1 и создать кадр данных для каждого списка.

do.call(rbind, lapply(seq_along(list1), function(i) 
        data.frame(ID = i, Time = 1:4, Grade = list1[[i]][1:4], 
                    Class = list1[[i]][5:8], Score = list1[[i]][9:12])))

#   ID Time Grade Class Score
#1   1    1     4     1     3
#2   1    2     5     1     1
#3   1    3     6     1     3
#4   1    4     7     1     3
#5   2    1     3     2     1
#6   2    2     4     2     4
#7   2    3     5     2     2
#8   2    4     6     2     1
#9   3    1     1     1     3
#10  3    2     2     1     2
#11  3    3     3     1     1
#12  3    4     4     1     1
#13  4    1     5     1     4
#14  4    2     6     1     4
#15  4    3     7     1     4
#16  4    4     8     1     3
#17  5    1     2     2     2
#18  5    2     3     2     1
#19  5    3     4     2     2
#20  5    4     5     2     1
1 голос
/ 05 апреля 2020

Использование 10 миллионов точек данных

Данные:

list1 = list(
  c(4,5,6,7,1,1,1,1,3,1,3,3),
  c(3,4,5,6,2,2,2,2,1,4,2,1),
  c(1,2,3,4,1,1,1,1,3,2,1,1),
  c(5,6,7,8,1,1,1,1,4,4,4,3),
  c(2,3,4,5,2,2,2,2,2,1,2,1))

big_list <- unlist(mget(x = rep('list1', 100000)), recursive = FALSE)

Код: - Использование Base-R: split()

system.time({
  col_levels <- rep(c('Grade', 'Class', 'Score'), each = 4)

  for(x in seq_along(big_list)){
    big_list[[x]] <- do.call('cbind', list(ID = x, Time = 1:4, 
                                        do.call('cbind', split(big_list[[x]], col_levels))))
  }

  final_df <- do.call('rbind', big_list)      
})

# user  system elapsed 
# 82.86    0.31   83.78

Сравнение: использование data.table

@ MichaelChirico

library('data.table')
system.time({
  # 4 = # of Times per ID&Column (assuming your table is rectangular)
  out = CJ(ID = 1:length(big_list), Time = 1:4)
  # relies on ID being an integer, so that ID = 1 --> list1[[1]]
  #   gives ID=1's data
  out[ , by = ID, c('Grade', 'Class', 'Score') := {
    as.data.table(matrix(big_list[[ .BY$ID ]], ncol = 3L))
  }]
})

# user  system elapsed 
# 76.22    0.25   76.80

Вывод

dim(final_df)
# [1] 2000000      5

head(final_df)
#      ID Time Class Grade Score
# [1,]  1    1     1     4     3
# [2,]  1    2     1     5     1
# [3,]  1    3     1     6     3
# [4,]  1    4     1     7     3
# [5,]  2    1     2     3     1
# [6,]  2    2     2     4     4
1 голос
/ 05 апреля 2020

Одно purrr и dplyr решение может быть:

map_dfr(.x = list1, 
        ~ as.data.frame(matrix(.x, 4, 3)) %>%
         setNames(c("Grade", "Class", "Score")), .id = "ID") %>%
 group_by(ID) %>%
 mutate(Time = 1:n())

   ID    Grade Class Score  Time
   <chr> <dbl> <dbl> <dbl> <int>
 1 1         4     1     3     1
 2 1         5     1     1     2
 3 1         6     1     3     3
 4 1         7     1     3     4
 5 2         3     2     1     1
 6 2         4     2     4     2
 7 2         5     2     2     3
 8 2         6     2     1     4
 9 3         1     1     3     1
10 3         2     1     2     2
11 3         3     1     1     3
12 3         4     1     1     4
13 4         5     1     4     1
14 4         6     1     4     2
15 4         7     1     4     3
16 4         8     1     3     4
17 5         2     2     2     1
18 5         3     2     1     2
19 5         4     2     2     3
20 5         5     2     1     4
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...