Разделить данные по годам

У меня есть такие данные:

ID    ATTRIBUTE        START          END
 1            A   01-01-2000   15-03-2010
 1            B   05-11-2001   06-02-2002
 2            B   01-02-2002   08-05-2008
 2            B   01-06-2008   01-07-2008

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

Результат может выглядеть так:

YEAR    count(A)    count(B)
2000          1           0
2001          1           1
2002          1           2
2003          1           1
2004          1           1
2005          1           1
2006          1           1
2007          1           1
2008          1           1
2009          1           0
2010          1           0

I второй шаг подсчета вхождений, вероятно, прост.

Но как бы я разделил свои данные на годы?

Заранее спасибо!


person speendo    schedule 24.10.2011    source источник
comment
Вы имеете в виду количество атрибутов (A и B) в год между START и END?   -  person daroczig    schedule 24.10.2011
comment
да, почти. Я имею в виду количество атрибутов (A и B) в год от начала до конца и без двойного подсчета идентификаторов (например, последние две строки моего примера ввода).   -  person speendo    schedule 24.10.2011
comment
что ты уже испробовал? Вы можете создать последовательность лет для каждой строки и назначить АТРИБУТ для каждого года. Когда вы делаете это для каждой строки ваших данных, вы просто подсчитываете количество лет с помощью своего АТРИБУТА. Я уверен, что у @daroczig есть полноценный ответ с кодом. :)   -  person Roman Luštrik    schedule 24.10.2011
comment
В настоящее время я пытаюсь разделить свои данные, чтобы у меня был фрейм данных за каждый год, содержащий только эпизоды, которые были активны в текущем году. Однако это очень сложно и требует много кода. Я надеюсь, что есть более простой способ   -  person speendo    schedule 24.10.2011


Ответы (5)


Вот подход, использующий несколько пакетов Хэдли.

library(lubridate); library(reshape2); library(plyr)

# extract years from start and end dates after converting them to date
dfr2 = transform(dfr, START = year(dmy(START)), END = year(dmy(END)))

# for every row, construct a sequence of years from start to end
dfr2 = adply(dfr2, 1, transform, YEAR = START:END)

# create pivot table of year vs. attribute with number of unique values of ID
dcast(dfr2, YEAR ~ ATTRIBUTE, function(x) length(unique(x)), value_var = 'ID')

РЕДАКТИРОВАТЬ: Если исходный data.frame большой, то adply может занять много времени. Полезной альтернативой в таких случаях является использование пакета data.table. Вот как мы можем заменить вызов adply на data.table.

require(data.table)
dfr2 = data.table(dfr2)[,list(YEAR = START:END),'ID, ATTRIBUTE']
person Ramnath    schedule 24.10.2011
comment
Вау, это круто (+) :) - person daroczig; 24.10.2011
comment
Ух ты, круто. Теперь я получаю таблицу с START:END; Attribute1; Attribute2. Как я могу получить его всего за один год? - person speendo; 24.10.2011
comment
Вы смотрели на вывод оператора dcast? он следует тому же формату, который вы описали в своем вопросе. если это не тот результат, который вы хотели, отредактируйте свой вопрос, чтобы отразить формат вывода. - person Ramnath; 24.10.2011
comment
хм, я немного изменил его, так как adply занял целую вечность. я смотрю на это снова - person speendo; 24.10.2011
comment
Я мечтаю об этом, Рамнат делает это. +1 - person Roman Luštrik; 24.10.2011
comment
Пожалуйста, не распространяйте эту ужасную идею использования = вместо <-. - person mbq; 25.10.2011

Вот решение, которое использует только ядро ​​R. Сначала мы показываем входные данные, чтобы все это было самодостаточным:

DF <- data.frame(ID = c(1, 1, 2, 2), 
    ATTRIBUTE = c("A", "B", "B", "B"), 
    START = c("01-01-2000", "05-11-2001", "01-02-2002", "01-06-2008"), 
    END = c("15-03-2010", "06-02-2002", "08-05-2008", "01-07-2008"))

Теперь, когда у нас есть входные данные, решение следующее: yr определяется как функция, извлекающая год. Суть вычисления заключается в утверждении, следующем за определением yr. Для каждой строки DF анонимная функция создает фрейм данных, содержащий годы, охватывающие столбец 1, и ATTRIBUTE и ID в столбцах 2 и 3. Например, фрейм данных, соответствующий первой строке DF, представляет собой 11-ю строку data.frame(YEAR = 2000:2010, ATTRIBUTE = 1, ID = "A"), а Кадр данных, соответствующий второй строке DF, представляет собой две строки data.frame(YEAR = 2001:2002, ATTRIBUTE = 1, ID = "B"). lapply создает список таких фреймов данных, по одному для каждой строки DF, поэтому в приведенном выше примере ввода он создает список с 4 компонентами. Используя do.call, мы rbind компоненты этого списка, т. е. отдельные фреймы данных, создаем один большой фрейм данных. Мы удаляем повторяющиеся строки (используя unique) из этого большого фрейма данных, удаляем столбец ID (третий столбец) и запускаем table для результата:

yr <- function(d) as.numeric(sub(".*-", "", d))
out <- table(unique(do.call(rbind, lapply(1:nrow(DF), function(r) with(DF[r, ],
    data.frame(YEAR = seq(yr(START), yr(END)), ATTRIBUTE, ID)))))[, -3])

Результирующая таблица:

> out
      ATTRIBUTE
YEAR   A B
  2000 1 0
  2001 1 1
  2002 1 2
  2003 1 1
  2004 1 1
  2005 1 1
  2006 1 1
  2007 1 1
  2008 1 1
  2009 1 0
  2010 1 0

РЕДАКТИРОВАТЬ:

Позже Плакат указал, что память может быть проблемой, поэтому вот решение sqldf, которое обрабатывает ключевые большие промежуточные результаты в sqlite вне R (dbname = tempfile() говорит ему сделать это), поэтому любое ограничение памяти R не повлияет на это. Он использует тот же ввод и ту же функцию yr, показанную выше, и возвращает тот же результат, tab такой же, как out выше. Также попробуйте без dbname = tempfile(), если он действительно помещается в памяти.

library(sqldf)

DF2 <- transform(DF, START = yr(START), END = yr(END))
years <- data.frame(year = min(DF2$START):max(DF2$END))

tab.df <- sqldf("select year, ATTRIBUTE, count(*) as count from
    (select distinct year, ATTRIBUTE, ID
    from years, DF2
    where year between START and END)
    group by year, ATTRIBUTE", dbname = tempfile())

tab <- xtabs(count ~., tab.df)
person G. Grothendieck    schedule 24.10.2011
comment
Этот ответ более аккуратный (+1) по сравнению с моим :) - person daroczig; 25.10.2011

Немного запутанно, но попробуйте следующее:

dfr <- data.frame(ID=c(1,1,2,2),ATTRIBUTE=c("A","B","B","B"),START=c("01-01-2000","05-11-2001","01-02-2002","01-06-2008"),END=c("15-03-2010","06-02-2002","08-05-2008","01-07-2008"),stringsAsFactors=F)
dfr$ATTRIBUTE <- factor(dfr$ATTRIBUTE)

actYears <- mapply(":",as.numeric(substr(dfr$START,7,10)),as.numeric(substr(dfr$END,7,10)))

yrRng <- ":"(range(actYears)[1],range(actYears)[2])

yrTable <- sapply(actYears,function(x) yrRng %in% x)
rownames(yrTable) <- yrRange
colnames(yrTable) <- dfr$ATTRIBUTE

Который дает:

yrTable
        A     B     B     B
2000 TRUE FALSE FALSE FALSE
2001 TRUE  TRUE FALSE FALSE
2002 TRUE  TRUE  TRUE FALSE
2003 TRUE FALSE  TRUE FALSE
2004 TRUE FALSE  TRUE FALSE
2005 TRUE FALSE  TRUE FALSE
2006 TRUE FALSE  TRUE FALSE
2007 TRUE FALSE  TRUE FALSE
2008 TRUE FALSE  TRUE  TRUE
2009 TRUE FALSE FALSE FALSE
2010 TRUE FALSE FALSE FALSE

Теперь мы можем построить таблицу:

t(apply(yrTable,1,function(x) table(dfr$ATTRIBUTE[x])))
     A B
2000 1 0
2001 1 1
2002 1 2
2003 1 1
2004 1 1
2005 1 1
2006 1 1
2007 1 1
2008 1 2
2009 1 0
2010 1 0

Это по-прежнему двойной подсчет идентификаторов, но, вероятно, было бы проще объединить перекрывающиеся диапазоны в исходном data.frame.

person James    schedule 24.10.2011
comment
красивый! но это решение выходит за пределы моей памяти (речь идет о действительно большом проекте) - person speendo; 24.10.2011

Я не собирался давать ответ здесь, так как проблема казалась немного сложной, поэтому я мог бы придумать только уродливое решение, но после прочтения комментария @Roman Luštrik я не смог избежать этой проблемы :)

В любом случае, я не уверен, что вам понравится это решение, так что будьте готовы!

Загрузка ваших демо-данных:

dfr <- structure(list(ID = c(1, 1, 2, 2), ATTRIBUTE = structure(c(1L, 2L, 2L, 2L), .Label = c("A", "B"), class = "factor"), START = c("01-01-2000", "05-11-2001", "01-02-2002", "01-06-2008"), END = c("15-03-2010", "06-02-2002", "08-05-2008", "01-07-2008")), .Names = c("ID", "ATTRIBUTE", "START", "END"), row.names = c(NA, -4L), class = "data.frame")

Мы не имеем дело с месяцами и так далее, просто сохраняем год в таблице:

> dfr$START <- as.numeric(substr(dfr$START, 7, 10))
> dfr$END <- as.numeric(substr(dfr$END, 7, 10))
> dfr
  ID ATTRIBUTE START  END
1  1         A  2000 2010
2  1         B  2001 2002
3  2         B  2002 2008
4  2         B  2008 2008

Удалите повторяющиеся строки (путем объединения лет на основе ID и ATTRIBUTE):

> dfr <- merge(aggregate(START ~ ID + ATTRIBUTE, dfr, min), aggregate(END ~ ID + ATTRIBUTE, dfr, max), by=c('ID', 'ATTRIBUTE'))
> dfr
  ID ATTRIBUTE START  END
1  1         A  2000 2010
2  1         B  2001 2002
3  2         B  2002 2008

И запустите однострочник с некоторыми apply, lapply, do.call и друзьями, чтобы показать красоту R! :)

> t(table(do.call(rbind, lapply(apply(dfr, 1, function(x) cbind(x[2], x[3]:x[4])), function(x) as.data.frame(x)))))
      V1
V2     A B
  2000 1 0
  2001 1 1
  2002 1 2
  2003 1 1
  2004 1 1
  2005 1 1
  2006 1 1
  2007 1 1
  2008 1 1
  2009 1 0
  2010 1 0
person daroczig    schedule 24.10.2011
comment
хорошее решение! но использование min и max для агрегирования не удастся, если диапазоны не перекрываются. пример 2002–2008 и 1997–1999. Ваша функция агрегирования интерпретирует это как 1997–2008, что не совсем правильно. - person Ramnath; 24.10.2011
comment
@Ramnath абсолютно прав, я не думал об этом. И я знаю, что использование substr для извлечения года из даты также является уродливым хаком, это лучше сделать с помощью date или около того, например: format(as.Date(dfr$START, '%m-%d-%Y'), '%Y') - person daroczig; 24.10.2011

Спасибо за все ваши ответы!

Все они действительно хороши, но некоторые доводят мой компьютер до предела, потому что мне приходится обрабатывать очень большие объемы данных.

Наконец, я посмотрел на все ваши решения и построил немного другое:

data <- structure(list(ID = c(1, 1, 2, 2), ATTRIBUTE = structure(c(1L, 2L, 2L, 2L), .Label = c("A", "B"), class = "factor"), START = c("2000-01-01", "2001-11-05", "2002-02-01", "2008-06-01"), END = c("2010-03-15", "2002-02-06", "2008-05-08", "2008-07-01")), .Names = c("ID", "ATTRIBUTE", "START", "END"), row.names = c(NA, -4L), class = "data.frame")

data$START <- as.Date(data$START)
data$END <- as.Date(data$END)
data$y0 <- (format(data$START,"%Y"))
data$y1 <- (format(data$END,"%Y"))

attributeTable <- function(dfr) {
  years <- data.frame(row.names(seq(min(dfr$y0), max(dfr$y1))))

  for (i in min(dfr$y0):max(dfr$y1)) {
    years[paste(i), "A"] <- length(unique(dfr$ID[dfr$y0 <= i & dfr$y1 >= i & dfr$ATTRIBUTE == "A"]))
    years[paste(i), "B"] <- length(unique(dfr$ID[dfr$y0 <= i & dfr$y1 >= i & dfr$ATTRIBUTE == "B"]))
  }

  years
}

attributeTable(data)

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

Скорость этого решения как минимум вполне приемлемая.

person speendo    schedule 25.10.2011
comment
В ответ на ваш комментарий о том, что у вас большой ввод, я добавил в свой ответ решение sqldf, которое имеет минимальные требования к памяти от R. - person G. Grothendieck; 26.10.2011