Эффективная выборка из вложенных списков - PullRequest
0 голосов
/ 03 июня 2018

У меня есть список списков , содержащий data.frames, из которых я хочу выбрать только несколько строк .Я могу достичь этого в цикле for, где я создаю последовательность на основе количества строк и выбираю только индексы строк в соответствии с этой последовательностью.

Но если у меня есть более глубокие вложенные списки, это больше не работает,Я также уверен, что есть лучший способ сделать это без цикла.

Какой будет эффективный и общий подход к выборке из вложенных списков, которые различаются по своим измерениям и содержат данные.кадры или матрицы?

## Dummy Data
n1=100;n2=300;n3=100
crdOrig <- list(
  list(data.frame(x = runif(n1,10,20), y = runif(n1,40,60))),
  list(data.frame(x = runif(n2,10,20), y = runif(n2,40,60))),
  list(data.frame(x = runif(n3,10,20), y = runif(n3,40,60)))
)

## Code to opimize
FiltRef <- list()
filterBy = 10
for (r in 1:length(crdOrig)) { 
  tmp <- do.call(rbind, crdOrig[[r]])
  filterInd <- seq(1,nrow(tmp), by = filterBy)
  FiltRef[[r]] <- tmp[filterInd,]
}
crdResult <- do.call(rbind, FiltRef)

# Plotting
crdOrigPl <- do.call(rbind, unlist(crdOrig, recursive = F))
plot(crdOrigPl[,1], crdOrigPl[,2], col="red", pch=20)
points(crdResult[,1], crdResult[,2], col="green", pch=20)

Приведенный выше код работает и в том случае, если список содержит несколько data.frames (данные ниже) .

## Dummy Data (Multiple DF)
crdOrig <- list(
  list(data.frame(x = runif(n1,10,20), y = runif(n1,40,60)),
       data.frame(x = runif(n1,10,20), y = runif(n1,40,60))),
  list(data.frame(x = runif(n2,10,20), y = runif(n2,40,60))),
  list(data.frame(x = runif(n3,10,20), y = runif(n3,40,60)))
)

Ноесли список содержит несколько списков, он выдает ошибку, пытаясь связать результат (FiltRef) вместе.

Результатом может быть data.frame с двумя столбцами (x, y) - как crdResult или одномерный список, например FiltRef (из первого примера)

## Dummy Data (Multiple Lists)
crdOrig <- list(
  list(list(data.frame(x = runif(n1,10,20), y = runif(n1,40,60))),
       list(data.frame(x = runif(n1,10,20), y = runif(n1,40,60)))),
  list(data.frame(x = runif(n2,10,20), y = runif(n2,40,60))),
  list(data.frame(x = runif(n3,10,20), y = runif(n3,40,60)))
)

+ 1 и спасибо всем за блестящие ответы!Все они работают, и у каждого из них есть чему поучиться.Я передам это @ Gwang-Jin Kim, поскольку его решение наиболее гибкое и обширное, хотя все они заслуживают проверки!

Ответы [ 5 ]

0 голосов
/ 03 июня 2018

Подготовка и реализация flatten

Ну, есть много других ответов, которые в принципе совпадают.

Я тем временем реализовал для забавы уплощениевложенные списки.

Так как я думаю на Лиспе:

Реализованы сначала car и cdr из lisp.

car <- function(l) {
  if(is.list(l)) {
    if (null(l)) {
      list()
    } else {
      l[[1]]
    }
  } else {
    error("Not a list.")
  }
}

cdr <- function(l) {
  if (is.list(l)) {
    if (null(l) || length(l) == 1) {
      list()
    } else {
      l[2:length(l)]
    }
  } else {
    error("Not a list.")
  }
}

Некоторые функции предикатов:

null <- function(l) length(l) == 0   
# this is Lisp's `null` checking whether list is empty (`length(l) == 0`)
# R's `is.null()` checks for the value NULL and not `length(obj) == 0`

# upon @Martin Morgan's comment removed other predicate functions
# thank you @Martin Morgan!
# instead using `is.data.frame()` and `is.list()`, since they are
# not only already there but also safer.

Что необходимо для построения сглаживания (для списков фреймов данных)

flatten <- function(nested.list.construct) {
  # Implemented Lisp's flatten tail call recursively. (`..flatten()`)
  # Instead of (atom l) (is.df l).
  ..flatten <- function(l, acc.l) { 
    if (null(l)) {
      acc.l
    } else if (is.data.frame(l)) {   # originally one checks here for is.atom(l)
      acc.l[[length(acc.l) + 1]] <- l
      acc.l # kind of (list* l acc.l)
    } else {
      ..flatten(car(l), ..flatten(cdr(l), acc.l))
    }
  }
  ..flatten(nested.list.construct, list())
}

# an atom is in the widest sence a non-list object

После этого фактическая функция определяется с помощью функции выборки.

Определениефункция выборки

# helper function
nrow <- function(df) dim(df)[1L]

# sampling function
sample.one.nth.of.rows <- function(df, fraction = 1/10) {
  # Randomly selects a fraction of the rows of a data frame
  nr <- nrow(df) 
  df[sample(nr, fraction * nr), , drop = FALSE]
}

Фактическая функция коллектора (из вложенных списков фреймов данных)

collect.df.samples <- function(df.list.construct, fraction = 1/10) {
  do.call(rbind, 
         lapply(flatten(df.list.construct), 
                function(df) sample.one.nth.of.rows(df, fraction)
               )
        )
}
# thanks for the improvement with `do.call(rbind, [list])` @Ryan!
# and the hint that `require(data.table)`
# `data.table::rbindlist([list])` would be even faster.

collect.df.samples сначала сглаживает вложенный списокпостроить фреймы данных df.list.construct в плоский список фреймов данных.Он применяет функцию sample.one.nth.of.rows к каждому элементу списка (lapply).Там он создает список выборочных фреймов данных (которые содержат дробь - здесь 1/10 от исходных строк фрейма данных).Эти выборочные кадры данных rbind редактируются по всему списку.Полученный фрейм данных возвращается.Он состоит из строк выборки каждого из фреймов данных.

Тестирование на примере

## Dummy Data (Multiple Lists)
n1=100;n2=300;n3=100
crdOrig <- list(
  list(list(data.frame(x = runif(n1,10,20), y = runif(n1,40,60))),
       list(data.frame(x = runif(n1,10,20), y = runif(n1,40,60)))),
  list(data.frame(x = runif(n2,10,20), y = runif(n2,40,60))),
  list(data.frame(x = runif(n3,10,20), y = runif(n3,40,60)))
)

collect.df.samples(crdOrig, fraction = 1/10)

Рефакторинг для последующих модификаций

Записав функцию collect.df.samples в:

# sampler function
sample.10th.fraction <- function(df) sample.one.nth.of.rows(df, fraction = 1/10)

# refactored:
collect.df.samples <- 
  function(df.list.construct, 
           df.sampler.fun = sample.10th.fraction) {
  do.call(rbind, 
          lapply(flatten(df.list.construct), df.sampler.fun))
}

Можно сделать функцию сэмплера сменной.(А если нет: изменяя параметр fraction, можно увеличить или уменьшить количество строк, собранных из каждого фрейма данных.)

Функция сэмплера в этом определении легко заменяется

Для выбора каждой n-й (например, каждой 10-й) строки в кадре данных вместо случайной выборки вы можете, например, использовать функцию сэмплера:

df[seq(from=1, to=nrow(df), by = nth), , drop = FALSE]

и ввести ее как df.sampler.fun =в collect.df.samples.Затем эта функция будет применена к каждому фрейму данных в объекте вложенного списка df и собрана в один фрейм данных.

every.10th.rows <- function(df, nth = 10) {
  df[seq(from=1, to=nrow(df), by = nth), , drop = FALSE]
}

a.10th.of.all.rows <- function(df, fraction = 1/10) {
  sample.one.nth.of.rows(df, fraction)
}

collect.df.samples(crdOrig, a.10th.of.all.rows)
collect.df.samples(crdOrig, every.10th.rows)
0 голосов
/ 03 июня 2018

Рассмотрим рекурсивный вызов, условно проверяющий, является ли первый элемент data.frame или list class.

stack_process <- function(lst){
  if(class(lst[[1]]) == "data.frame") {
    tmp <- lst[[1]]
  } 

  if(class(lst[[1]]) == "list") {
    inner <- lapply(lst, stack_process)        
    tmp <- do.call(rbind, inner)
  }

  return(tmp)
}

new_crdOrig <- lapply(crdOrig, function(x) {
  df <- stack_process(x)

  filterInd <- seq(1, nrow(df), by = filterBy)
  return(df[filterInd,])
})

final_df <- do.call(rbind, new_crdOrig)
0 голосов
/ 03 июня 2018

Вот ответ в базовом заимствовании из упомянутой здесь пользовательской функции "rapply" rapply к вложенному списку фреймов данных в R

df_samples<-list()
i=1

f<-function(x) {
  i<<-i+1
  df_samples[[i]]<<-x[sample(rownames(x),10),]
}

recurse <- function (L, f) {
  if (inherits(L, "data.frame")) {
  f(L)  }
  else lapply(L, recurse, f)
}

recurse(crdOrig, f)

res<-do.call("rbind", df_samples)
0 голосов
/ 03 июня 2018

Я бы тоже выровнял списки списков в стандартное представление (и сделал бы весь анализ на выровненном представлении, а не только в подразделах), но следил бы за соответствующей информацией индексации, например,

flatten_recursive = function(x) {
    i <- 0L
    .f = function(x, depth) {
        if (is.data.frame(x)) {
            i <<- i + 1L
            cbind(i, depth, x)
        } else {
            x = lapply(x, .f, depth + 1L)
            do.call(rbind, x)
        }
    }
    .f(x, 0L)
}

Внутренняя функция .f() посещает каждый элемент списка.Если элемент является data.frame, он добавляет уникальный идентификатор для его индексации.Если это список, то он вызывает себя для каждого элемента списка (увеличивая счетчик глубины, в случае, если это полезно, можно также добавить счетчик «группы»), а затем связывает элементы по строкам.Я использую внутреннюю функцию, чтобы у меня была переменная i для увеличения при вызове функций.Конечным результатом является отдельный фрейм данных с индексом, который используется для ссылки на исходные результаты.

> tbl <- flatten_recursive(crdOrig) %>% as_tibble()
> tbl %>% group_by(i, depth) %>% summarize(n())
# A tibble: 4 x 3
# Groups:   i [?]
      i depth `n()`
  <int> <int> <int>
1     1     3   100
2     2     3   100
3     3     2   300
4     4     2   100
> tbl %>% group_by(i) %>% slice(seq(1, n(), by = 10)) %>% summarize(n())
# A tibble: 4 x 2
      i `n()`
  <int> <int>
1     1    10
2     2    10
3     3    30
4     4    10

Общий шаблон .f() может быть скорректирован для дополнительных типов данных, например (некоторые детали опущены)

.f <- function(x) {
    if (is.data.frame(x)) {
        x
    } else if (is.matrix(x)) {
        x <- as.data.frame(x)
        setNames(x, c("x", "y"))
    } else {
        do.call(rbind, lapply(x, .f))
    }
}
0 голосов
/ 03 июня 2018

Я бы просто сгладил всю чертову штуку и работал бы над чистым списком.

library(rlist)
out <- list.flatten(y)

# prepare a vector for which columns belong together
vc <- rep(1:(length(out)/2), each = 2)
vc <- split(1:length(vc), vc)

# prepare the final list
ll <- vector("list", length(unique(vc)))
for (i in 1:length(vc)) {
  ll[[i]] <- as.data.frame(out[vc[[i]]])
}

result <- lapply(ll, FUN = function(x) {
  x[sample(1:nrow(x), size = 10, replace = FALSE), ]
})

do.call(rbind, result)

           x        y
98  10.32912 52.87113
52  16.42912 46.07026
92  18.85397 46.26403
90  12.04884 57.79290
23  18.20997 40.57904
27  18.98340 52.55919
...
...