Как контролировать ход применения функции?

Мне нужно разработать корреляционную матрицу 2886 * 2886, проблема в том, что для создания промежуточного datatable (RESULT) требуется много времени, чтобы связать ее, чтобы я мог выполнять следующие действия, в то время как вызывая последнюю строку RESULT=rbindlist(apply(COMB, 1, append)) в коде ниже:

  • Оцените время, необходимое для завершения функции apply
  • Отслеживать его прогресс.
  • Возможность приостановки и продолжения в более позднее время

Вот код:

SOURCE=data.table(NAME=rep(paste0("NAME", as.character(1:2889)), each=600), VALUE=sample(c(TRUE,FALSE), 600, TRUE) )
> SOURCE
            NAME VALUE
      1:   NAME1  TRUE
      2:   NAME1  TRUE
      3:   NAME1  TRUE
      4:   NAME1  TRUE
      5:   NAME1  TRUE
     ---              
1733396: NAME999  TRUE
1733397: NAME999  TRUE
1733398: NAME999  TRUE
1733399: NAME999  TRUE
1733400: NAME999 FALSE

setkey(SOURCE,NAME)
a=SOURCE[,unique(NAME)]
COMB=data.table(expand.grid(a,a, stringsAsFactors=FALSE))
> COMB
             Var1    Var2
      1:    NAME1   NAME1
      2:   NAME10   NAME1
      3:  NAME100   NAME1
      4: NAME1000   NAME1
      5: NAME1001   NAME1
     ---                 
8346317:  NAME995 NAME999
8346318:  NAME996 NAME999
8346319:  NAME997 NAME999
8346320:  NAME998 NAME999
8346321:  NAME999 NAME999

append <- function(X) {
data.table(NAME1=X[1], VALUE1=SOURCE[X[1], VALUE], 
    NAME2=X[2], VALUE2=SOURCE[X[2], VALUE] )
}

RESULT=rbindlist(apply(COMB, 1, append))

Любая идея?

Также вы знаете, есть ли более быстрый способ генерации datatable RESULT из SOURCE? RESULT является промежуточным datatable для вычисления значений корреляции между VALUE1 и VALUE2 для каждой пары NAME.

С подмножеством SOURCE RESULT выглядит так:

SOURCE=SOURCE[sample(1:nrow(SOURCE), 3)]
setkey(SOURCE,NAME)
a=SOURCE[,unique(NAME)]
COMB=data.table(expand.grid(a,a, stringsAsFactors=FALSE))
RESULT=rbindlist(apply(COMB, 1, append))
> RESULT
      NAME1 VALUE1    NAME2 VALUE2
1: NAME1859   TRUE NAME1859   TRUE
2:  NAME768  FALSE NAME1859   TRUE
3:  NAME795   TRUE NAME1859   TRUE
4: NAME1859   TRUE  NAME768  FALSE
5:  NAME768  FALSE  NAME768  FALSE
6:  NAME795   TRUE  NAME768  FALSE
7: NAME1859   TRUE  NAME795   TRUE
8:  NAME768  FALSE  NAME795   TRUE
9:  NAME795   TRUE  NAME795   TRUE

Позже я сделаю RESULT[,VALUE3:=(VALUE1==VALUE2)], чтобы получить значения корреляции: RESULT[, mean(VALUE3), by=c("NAME1", "NAME2")] Поэтому, возможно, весь процесс можно сделать более эффективно, кто знает.

Ответ 1

Вы можете использовать библиотеку pbapply (git), которая показывает оценку времени и индикатор выполнения для любой функции из семейства '* apply'.

В случае вашего вопроса:

library(pbapply)      

result <- rbindlist( pbapply(COMB, 1, append) )

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

Ответ 2

Вы можете использовать txtProgressBar из пакета utils:

total <- 50
pb <- txtProgressBar(min = 0, max = total, style = 3)

lapply(1:total, function(i){
Sys.sleep(0.1)
setTxtProgressBar(pb, i)
})

ИЛИ используйте *ply plyr family из пакета plyr

library(plyr)
laply(1:100, function(i) {Sys.sleep(0.05); i}, .progress = "text")

Проверьте ?create_progress_bar() для более подробной информации

Ответ 3

Попробуйте это вместо:

setkey(SOURCE, NAME)

SOURCE[, CJ(NAME, NAME, unique = T)][
       , mean(SOURCE[V1, VALUE] == SOURCE[V2, VALUE]), by = .(V1, V2)]

Fwiw, имена all-caps - ужасный выбор imo - значительно затрудняет запись и чтение кода.

Ответ 4

Вы пытаетесь сделать перекрестное соединение? См. Этот пример:

#dummy data
set.seed(1)
SOURCE = data.frame(
  NAME = sample(paste0("Name", 1:4),20, replace = TRUE),
  VALUE = sample(c(TRUE,FALSE), 20, replace = TRUE)
)

#update colnames for join
d1 <- SOURCE
colnames(d1) <- c("NAME1", "VALUE1")
d2 <- SOURCE
colnames(d2) <- c("NAME2", "VALUE2")

#cross join
merge(d1, d2, all = TRUE)

Ответ 5

Я просто написал свою собственную реализацию строки прогресса текста. Я не знал о txtProgressBar(), поэтому благодаря @JavK для этого! Но я по-прежнему буду использовать мою реализацию здесь.


Я изучил что-то очень полезное, работая над этой проблемой. Первоначально я планировал в зависимости от terminfo для управления курсором. В частности, я собирался прекомпилировать текущий код терминала, чтобы переместить курсор влево, используя tput:

tc_left <- system2('tput','cub1',stdout=T);

И затем я собирался повторно распечатать этот код до reset курсора до начала строки выполнения после каждого обновления. Это решение работает, но только на терминалах Unix, на которых установлена ​​надлежащая база данных terminfo; он не будет работать на других платформах, в первую очередь RStudio в Windows.

Затем, когда я просмотрел код txtProgressBar() (после чтения ответа @JavK), я обнаружил, что они используют гораздо более простое и надежное решение для reset позиции курсора: они просто печатают возврат каретки! Это так же просто, как cat('\r');, что я теперь использую в своей реализации.


Вот мое решение. Он включает в себя одну функцию инициализации, называемую progInit(), которую вы должны вызывать один раз до цикла с интенсивным вычислением, и которому вы должны передать общее количество итераций цикла (которое вы, следовательно, должны знать заранее), и одну функцию обновления, называемую prog(), который увеличивает счетчик циклов и обновляет линию выполнения. Переменные состояния просто сбрасываются в глобальную среду под именами, начинающимися с prog.

progInit <- function(N,dec=3L) {
    progStart <<- Sys.time();
    progI <<- 1L;
    progN <<- N;
    progDec <<- dec;
}; ## end progInit()

prog <- function() {
    rem <- unclass(difftime(Sys.time(),progStart,units='secs'))*(progN/progI-1);
    days <- as.integer(rem/86400); rem <- rem-days*86400;
    hours <- as.integer(rem/3600); rem <- rem-hours*3600;
    minutes <- as.integer(rem/60); rem <- rem-minutes*60;
    seconds <- as.integer(rem); rem <- rem-seconds;
    millis <- as.integer(rem*1000);
    over <- paste(collapse='',rep(' ',20L));
    pct <- progI/progN*100;
    if (days!=0L) {
        msg <- sprintf(' %.*f%% %dd/%02d:%02d:%02d.%03d%s',
            progDec,pct,days,hours,minutes,seconds,millis,over);
    } else {
        msg <- sprintf(' %.*f%% %02d:%02d:%02d.%03d%s',
            progDec,pct,hours,minutes,seconds,millis,over);
    }; ## end if
    cat('\r');
    cat(msg);
    cat('\r');
    progI <<- progI+1L;
}; ## end prog()

library(data.table);
SOURCE <- data.table(NAME=rep(paste0("NAME", as.character(1:2889)), each=600), VALUE=sample(c(TRUE,FALSE), 600, TRUE) );
setkey(SOURCE,NAME);
a <- SOURCE[,unique(NAME)];
COMB <- data.table(expand.grid(a,a, stringsAsFactors=FALSE));
append <- function(X) {
    prog();
    data.table(NAME1=X[1],VALUE1=SOURCE[X[1],VALUE],NAME2=X[2],VALUE2=SOURCE[X[2],VALUE]);
}; ## end append()
##x <- COMB; progInit(nrow(x)); rbindlist(apply(x,1,append)); ## full object
x <- COMB[1:1e4,]; progInit(nrow(x)); rbindlist(apply(x,1,append)); ## ~30s

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

К сожалению, когда я запускаю код на вашем полном COMB объекте, оценка ведет себя беспорядочно; сначала он быстро падает, затем он неуклонно растет. Это, по-видимому, вызвано замедлением скорости обработки, что я не могу объяснить, и я не уверен, что вы видите то же самое. В любом случае, теоретически, если вы дождитесь, когда цикл приблизится к завершению, увеличение оценочного оставшегося времени должно измениться, и в конечном итоге оценка должна упасть до нуля по мере завершения вычисления. Но, несмотря на эту причуду, я вполне уверен, что код правильный, поскольку он работает так, как ожидалось, для более быстрых (т.е. Менее интенсивных вычислительных) тестовых случаев.

Ответ 6

Для модных индикаторов выполнения (не в базовой/стандартной библиотеке) также есть progress:

pb <- progress_bar$new(
  format = "  downloading [:bar] :percent eta: :eta",
  total = 100, clear = FALSE, width= 60)
for (i in 1:100) {
  pb$tick()
  Sys.sleep(1 / 100)
}

#> downloading [========----------------------]  28% eta:  1s

Таким образом, это соответствует требованиям (1) и (2), но не (3). Для кэширования промежуточных результатов, вероятно, проще всего время от времени записывать данные на диск. Для быстрой сериализации вы можете попробовать

  • fst: удобно для сериализации столбчатых структур данных, таких как data.tables
  • qs для более общей сериализации объекта

Надеюсь, это поможет.