Зацикливание применяет функцию по списку данных - PullRequest
0 голосов
/ 19 мая 2018

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

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

Я написал пару строк для генерации списка всех данныхкадры и цикл for, который должен повторять оператор apply для каждого кадра данных;Однако у меня возникают проблемы при попытке выполнить все, что я надеюсь достичь (мой код и некоторые примеры данных приведены ниже):

1) Я хотел бы ограничить функцию rollmean всеми столбцамиза исключением 1-го (или первых нескольких), чтобы столбцы «информация» не усреднялись. Я также хотел бы добавить эти столбцы обратно во фрейм выходных данных.

2) Я хочу сохранить выходные данные как новый фрейм данных (с уникальным именем). Мне все равно, будет ли он сохранен в рабочей области или экспортирован как xlsx, так как у меня уже записаны коды пакетного импорта.

3) В идеале я хотел бы, чтобы результирующий кадр данныхбыть таким же количеством наблюдений, что и входные данные, где rollmean сокращает ваши данные.Я также не хочу, чтобы они стали NA, поэтому я не хочу использовать fill = NA Это может быть достигнуто путем написания новой функции, передавая type = "partial" в rollmean (хотя это все еще сжимает мои данные на1 в моих руках), или начав бросать среднее значение для nth + 2 терма и связав не усредненные nth и nth + 1 термы с результирующим фреймом данных.Любой способ в порядке. (подробности см. На рисунке, он иллюстрирует, как будет выглядеть последний)

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

Любой вклад очень важен, потому что у меня нет идей.

#reproducible data frames 
a = as.data.frame(cbind(info = 1:10, matrix(rexp(200), 10)))
b = as.data.frame(cbind(info = 1:10, matrix(rexp(200), 10)))
c = as.data.frame(cbind(info = 1:10, matrix(rexp(200), 10)))
colnames(a) = c("info", 1:20)
colnames(b) = c("info", 1:20)
colnames(c) = c("info", 1:20)

#identify all dataframes for looping rollmean
dflist = as.list(ls()[sapply(mget(ls(), .GlobalEnv), is.data.frame)]

#for loop to create rolling average and save as new dataframe
for (j in 1:length(dflist)){
  list = as.list(ls()[sapply(mget(ls(), .GlobalEnv), is.data.frame)])
  new.names = as.character(unique(list))
  smoothed = as.data.frame(
     apply(
        X = names(list), MARGIN = 1, FUN = rollmean, k = 3, align = 'right'))
  assign(new.names[i], smoothed)
}

Я также попробовал использовать вложенный подход, но не смог заставить его вызватьФункция rollmean / rollapply аналогична проблеме здесь , поэтому я вернулся к циклам, но если кто-то может сделать эту работу с вложенными приложениями, я не в сети!

Рисунок - идеальный выход: Top - один входной фрейм данных с цветными прямоугольниками, показывающими скользящее среднее по всем столбцам, для повторения по каждому столбцу;нижний является идеальным выходом с цветами, отражающими местоположение вывода для каждого цветного окна выше Top is single input dataframe with colored boxes demonstrating a rolling average across all columns, to be iterated over each column; bottom is ideal output with colors reflecting the location of output for each colored window above

Ответы [ 2 ]

0 голосов
/ 19 мая 2018

Ниже dfnames - имена фреймов данных в env, глобальной среде - мы назвали ее env на случай, если вы захотите позже изменить место их расположения.Обратите внимание, что ls имеет аргумент pattern=, и если имена фреймов данных имеют различный шаблон, тогда вместо него можно использовать dfnames <- ls(pattern=whatever), где любое подходящее регулярное выражение.

Теперь определим make_new, который вызывает rollapplyr с новой средней функцией mean3, которая возвращает последнее значение своего ввода, если входной вектор имеет длину меньше 3, и означает иначе.Затем зациклите имена, используя rollappyr с FUN=mean3 и partial=TRUE.

library(zoo)

env <- .GlobalEnv
dfnames <- Filter(function(x) is.data.frame(get(x, env)), ls(env))

# make_new - first version
mean3 <- function(x, k = 3) if (length(x) < k) tail(x, 1) else mean(x)
make_new <- function(df) replace(df, -1, rollapplyr(df[-1], 3, mean3, partial = TRUE))

for(nm in dfnames) env[[paste(nm, "new", sep = "_")]] <- make_new(get(nm, env))

Альтернативная версия make_new

Альтернативой первой версии make_new, показанной выше, является следующеевторая версия.Во второй версии вместо определения mean3 мы используем просто mean, но задаем вектор ширины w в rollapplyr, такой что w равно c (1, 1, 3,3, ..., 3).Таким образом, он принимает среднее значение только последнего элемента для первых двух входных компонентов и среднее значение 3 последних элементов для остальных.Обратите внимание, что теперь, когда мы указываем ширину явно, нам больше не нужно указывать partial=.

# make_new -- second version
make_new <- function(df) {
  w <- replace(rep(3, nrow(df)), 1:2, 1)
  replace(df, -1, rollapplyr(df[-1], w, mean))
}

Примечание

Обычно при записи R и манипулировании набором объектов сохраняются объекты всписок, а не оставлять их свободными в глобальной среде.Мы могли бы создать такой список L, как этот, и затем использовать lapply, чтобы создать второй список L2, содержащий новые версии.Любая версия make_new будет работать здесь.

L <- mget(dfnames, env)
L2 <- lapply(L, make_new)
0 голосов
/ 19 мая 2018

Чтобы приблизиться к этому, подумайте об одном столбце, затем одном кадре (который является просто списком столбцов), а затем о списке кадров.

(Мои данные используются в нижней части ответа.)

Один столбец

Если вам не нравится сокращение zoo::rollmean, напишите свое собственное:

myrollmean <- function(x, k, ..., type=c("normal","rollin","keep"), na.rm=FALSE) {
  type <- match.arg(type)
  out <- zoo::rollmean(x, k, ...)
  aug <- c()
  if (type == "rollin") {
    # effectively:
    #   c(mean(x[1]), mean(x[1:2]), ..., mean(x[1:j]))
    # for the j=k-1 elements that precede the first from rollmean,
    # when it'll become something like:
    # c(mean(x[3:5]), mean(x[4:6]), ...)
    aug <- sapply(seq_len(k-1), function(i) mean(x[seq_len(i)], na.rm=na.rm))
  } else if (type == "keep") {
    aug <- x[seq_len(k-1)]
  }
  out <- c(aug, out)
  out
}

myrollmean(1:8, k=3) # "normal", default behavior
# [1] 2 3 4 5 6 7
myrollmean(1:8, k=3, type="rollin")
# [1] 1.0 1.5 2.0 3.0 4.0 5.0 6.0 7.0
myrollmean(1:8, k=3, type="keep")
# [1] 1 2 2 3 4 5 6 7

Я предупреждаю, что эта реализация немного наивнав лучшем случае и должно быть исправлено.Убедитесь, что вы понимаете, что он делает, когда выбираете что-то, отличное от "normal" (что вам не подойдет, я просто по умолчанию использую zoo::rollmean).Эта функция может быть легко применена к другим zoo::roll* функциям.

В одном столбце данных:

rbind(
  dflist[[1]][,2],  # for comparison
  myrollmean(dflist[[1]][,2], k=3, type="keep")
)
#          [,1]      [,2]      [,3]      [,4]       [,5]      [,6]      [,7]     [,8]     [,9]     [,10]
# [1,] 1.865352 0.4047481 0.1466527 1.7307097 0.08952618 0.6668976 1.0743669 1.511629 1.314276 0.1565303
# [2,] 1.865352 0.4047481 0.8055844 0.7607035 0.65562952 0.8290445 0.6102636 1.084298 1.300091 0.9941452

Один «кадр»

Простое использование lapply, опуская первый столбец:

str(dflist[[1]][1:4, 1:3])
# 'data.frame': 4 obs. of  3 variables:
#  $ info: num  1 2 3 4
#  $ 1   : num  1.865 0.405 0.147 1.731
#  $ 2   : num  0.745 1.243 0.674 1.59
dflist[[1]][-1] <- lapply(dflist[[1]][-1], myrollmean, k=3, type="keep")
str(dflist[[1]][1:4, 1:3])
# 'data.frame': 4 obs. of  3 variables:
#  $ info: num  1 2 3 4
#  $ 1   : num  1.865 0.405 0.806 0.761
#  $ 2   : num  0.745 1.243 0.887 1.169

(Для проверки столбец $ 1 соответствует второй строке в приведенном выше примере с «одним столбцом».)

Список «кадров»

(Я сбрасываю данные до того, что было, прежде чем изменить их выше ... см. Код «данных» внизу ответа.)

Мы вложили предыдущую технику в другую lapply:

dflist2 <- lapply(dflist, function(ldf) {
  ldf[-1] <- lapply(ldf[-1], myrollmean, k=3, type="keep")
  ldf
})
str(lapply(dflist2, function(a) a[1:4, 1:3]))
# List of 3
#  $ :'data.frame': 4 obs. of  3 variables:
#   ..$ info: num [1:4] 1 2 3 4
#   ..$ 1   : num [1:4] 1.865 0.405 0.806 0.761
#   ..$ 2   : num [1:4] 0.745 1.243 0.887 1.169
#  $ :'data.frame': 4 obs. of  3 variables:
#   ..$ info: num [1:4] 1 2 3 4
#   ..$ 1   : num [1:4] 0.271 3.611 2.36 3.095
#   ..$ 2   : num [1:4] 0.127 0.722 0.346 0.73
#  $ :'data.frame': 4 obs. of  3 variables:
#   ..$ info: num [1:4] 1 2 3 4
#   ..$ 1   : num [1:4] 1.278 0.346 1.202 0.822
#   ..$ 2   : num [1:4] 0.341 1.296 1.244 1.528

(Опять же, для простой проверки, посмотрите, что строка $ 1 первого кадра показывает то же самое свернутое значение, что и вторая строка примера "один столбец" выше.)

PS:

  • если вам нужно пропустить больше, чем только первый столбец, то внутри внешнего lapply используйте вместо ldf[-(1:n)] <- lapply(ldf[-(1:n)], myrollmean, k=3, type="keep") пропустить первые n столбцы
  • Чтобы использовать оконную функцию, отличную от zoo::rollmean, вам нужно изменить особые случаи myrollmean, хотя это должно быть прямым- достаточно вперед, учитывая этот пример
  • Я использую придуманный str(...), чтобы сократить вывод для отображения здесь.Вы должны проверить все ваши данные, что они делают то, что вы ожидаете для всего каждого кадра.

Воспроизводимые данные

set.seed(2)
a = as.data.frame(cbind(info = 1:10, matrix(rexp(200), 10)))
b = as.data.frame(cbind(info = 1:10, matrix(rexp(200), 10)))
c = as.data.frame(cbind(info = 1:10, matrix(rexp(200), 10)))
colnames(a) = c("info", 1:20)
colnames(b) = c("info", 1:20)
colnames(c) = c("info", 1:20)
dflist <- list(a,b,c)

str(lapply(dflist, function(a) a[1:3, 1:4]))
# List of 3
#  $ :'data.frame': 3 obs. of  4 variables:
#   ..$ info: num [1:3] 1 2 3
#   ..$ 1   : num [1:3] 1.865 0.405 0.147
#   ..$ 2   : num [1:3] 0.745 1.243 0.674
#   ..$ 3   : num [1:3] 0.356 0.689 0.833
#  $ :'data.frame': 3 obs. of  4 variables:
#   ..$ info: num [1:3] 1 2 3
#   ..$ 1   : num [1:3] 0.271 3.611 3.198
#   ..$ 2   : num [1:3] 0.127 0.722 0.188
#   ..$ 3   : num [1:3] 1.99 2.74 4.78
#  $ :'data.frame': 3 obs. of  4 variables:
#   ..$ info: num [1:3] 1 2 3
#   ..$ 1   : num [1:3] 1.278 0.346 1.981
#   ..$ 2   : num [1:3] 0.341 1.296 2.094
#   ..$ 3   : num [1:3] 1.1159 3.05877 0.00506
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...