Работа с длительностями, определенными днями, часами, минутами и секундами, такими как «1d 3h 2m 28s» в R - PullRequest
6 голосов
/ 18 октября 2011

У меня есть фрейм данных с символьными векторами в формате с днями, часами, минутами и секундами, представленными как «1d 3h 2m 28s»:

> head(status[5])
    Duration 
1 0d 20h 46m 31s 
2  2d  0h 13m 54s
3  2d  0h 13m 53s
4  0d  9h 53m 38s
5  5d 12h 17m 37s
6  0d 10h 21m 19s

Я могу разобрать его с помощью регулярного выражения для компонентов, ноне может придумать хороший способ конвертировать продолжительность в секунды.Я могу gsub векторы в выражении, которое приведет к количеству секунд, но столкнется с дорожным блоком, используя eval для результатов.

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

status$duration <- gsub("(\\d+)d\\s+(\\d+)h\\s+(\\d+)m\\s+(\\d+)s.*","\\1*86400+\\2*3600+\\3*60+\\4",as.character(status[,5]),perl=TRUE)

Вышеприведенное создает выражение, которое можно оценить, но я что-то упускаю, когда дело доходит до parse(text=status$duration) и последующего eval.

В Perl я привык брать "захваченные переменные" в выражении регулярного выражения и немедленно использовать их, а не только в строке замены.Есть ли в R похожие возможности?

Спасибо, я, вероятно, упускаю что-то очень простое из-за туманного разума.

Ответы [ 4 ]

6 голосов
/ 18 октября 2011

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

Сначала пересоздайте ваши данные:

status <- c("0d 20h 46m 31s", "2d 0h 13m 54s", "2d 0h 13m 53s", 
       "0d 9h 53m 38s", "5d 12h 17m 37s", "0d 10h 21m 19s")

duration <- gsub("(\\d+)d\\s+(\\d+)h\\s+(\\d+)m\\s+(\\d+)s.*","\\1*86400+\\2*3600+\\3*60+\\4",
                 as.character(status),perl=TRUE)
[1] "0*86400+20*3600+46*60+31" "2*86400+0*3600+13*60+54"  "2*86400+0*3600+13*60+53" 
[4] "0*86400+9*3600+53*60+38"  "5*86400+12*3600+17*60+37" "0*86400+10*3600+21*60+19"

Чтобы оценить один элемент:

eval(parse(text=duration[1]))
[1] 74791

Оберните это в sapply или в ваш любимый оператор apply, чтобы оценить все строки:

sapply(duration, function(x)eval(parse(text=x)))

0*86400+20*3600+46*60+31  2*86400+0*3600+13*60+54 
                   74791                   173634 
 2*86400+0*3600+13*60+53  0*86400+9*3600+53*60+38 
                  173633                    35618 
5*86400+12*3600+17*60+37 0*86400+10*3600+21*60+19 
                  476257                    37279 
5 голосов
/ 18 октября 2011

Первое и последнее решения, представленные ниже, кажутся самыми простыми, но решения со сложными регулярными выражениями более точно соответствуют тому, что могло быть сделано в perl.

Прежде чем перечислять сами решения, обратите внимание, что в них мы предполагаем, что входное значение равно tt, а вектор преобразования mult представляет собой 4-вектор, чьи компоненты представляют собой количество секунд в дне, часах, минутах и ​​секундах. Мы можем установить mult как в комментарии или рассчитать как показано:

tt <- c("0d 20h 46m 31s", "2d 0h 13m 54s", "2d 0h 13m 53s", 
   "0d 9h 53m 38s", "5d 12h 17m 37s", "0d 10h 21m 19s")
# mult <- c(86400, 3600, 60, 1)
mult <- rev(cumprod(rev(c(24, 60, 60, 1))))

Вот 4 подхода:

1) быстро извлекаемые числа Мы можем использовать strapply в пакете gsubfn, чтобы избежать сложных регулярных выражений. strapply используется для извлечения всех чисел, упорядочивая их в матрице и умножая на mult, выводя результат в виде простого числового вектора:

library(gsubfn)
mat <- strapply(tt, "\\d+", as.numeric, simplify = TRUE)
secs <- c(mult %*% mat)

Эти две строки можно объединить в одно утверждение, но мы оставим его, как указано выше, на случай, если вы захотите изучить mat отдельно.

2) связывание со сложным регулярным выражением Другая возможность, также использующая strapply, заключается в следующем единственном утверждении. Захваченные строки помещаются в свободные переменные по мере их появления, поэтому первый захват переходит в day, второй в hour и т. Д. Эта строка может быть ближе к тому, что вы сделали бы в perl:

secs <- strapply(tt, "(\\d+)d (\\d+)h (\\d+)m (\\d+)s", 
 ~ 86400 * as.numeric(day) + 3600 * as.numeric(hour) + 
    60 * as.numeric(minute) + as.numeric(second), simplify = TRUE)

3) со сложным регулярным выражением, но векторизовано или даже короче:

secs <- strapply(tt, "(\\d+)d (\\d+)h (\\d+)m (\\d+)s", 
  ~ as.numeric(list(...)) %*% mult, simplify = TRUE)

4) strsplit и вот еще один ответ на один оператор. Этот не использует strapply, но использует тот факт, что соответствующий разделитель в конце строки просто удаляется без вывода следующей пустой строки. Подробнее см. ?strsplit.

secs <- sapply(strsplit(tt, "[dhms]"), function(x) as.numeric(x) %*% mult)

Результат любого из вышеперечисленного:

> secs
[1]  74791 173634 173633  35618 476257  37279
5 голосов
/ 18 октября 2011

Вы можете начать с разделения строк на пробелы, используя strsplit.

a <- c("0d 20h 46m 31s", "2d 0h 13m 54s", "2d 0h 13m 53s", 
       "0d 9h 53m 38s", "5d 12h 17m 37s", "0d 10h 21m 19s")

a.values <- sapply(a, strsplit, " ")

Теперь a.values будет:

> a.values
$`0d 20h 46m 31s`
[1] "0d"  "20h" "46m" "31s"

$`2d 0h 13m 54s`
[1] "2d"  "0h"  "13m" "54s"

[cut]

Теперь мы напишем небольшую функцию, которая берет вектор из 4 элементов, подобный этому, и преобразует его в секунды. По сути, я занимаюсь удалением буквы в конце каждого элемента, преобразованием ее в число и умножением на подходящее значение (86400 = 24 * 60 * 60 для дней, 3600 = 60 * 60 для часов и т. Д.).

convert.to.sec <- function(timestamp)
    {
    # Remove the last character (d, h, m, s) from each element    
    values <- sapply(timestamp, function(x){as.numeric(substr(x, 1, nchar(x)-1))})

    mult <- c(86400, 3600, 60, 1)
    res <- sum(mult * values)
    }

Теперь мы можем сделать

sapply(a.values, convert.to.sec)

et voilà!

0d 20h 46m 31s  2d 0h 13m 54s  2d 0h 13m 53s  0d 9h 53m 38s 5d 12h 17m 37s 
     74791         173634         173633          35618         476257 
0d 10h 21m 19s 
     37279 
0 голосов
/ 18 октября 2011

Вы можете избежать использования циклов *apply, заставив strsplit произвести вывод, а затем unlist преобразовав в matrix, взвесив и используя colSums:

a <- c("0d 20h 46m 31s", "2d 0h 13m 54s", "2d 0h 13m 53s",
     "0d 9h 53m 38s", "5d 12h 17m 37s", "0d 10h 21m 19s")

mat <- matrix(as.numeric(unlist(strsplit(a,"[a-z][ ]*"))),nrow=4) #transposed by default, rows represent time units, cols cases

colSums(mat*c(86400,3600,60,1))
[1]  74791 173634 173633  35618 476257  37279
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...