Разбивка данных по годам
у меня есть такие данные:
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 второй шаг подсчета случаев, вероятно, прост.
но как бы я разделил свои данные на годы?
спасибо заранее!
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')
EDIT: если оригинал data.frame
большой, то adply
может занять много времени. Полезной альтернативой в таких случаях является использование data.table
пакета. Вот как мы можем заменить adply
звонок с помощью data.table
.
require(data.table)
dfr2 = data.table(dfr2)[,list(YEAR = START:END),'ID, ATTRIBUTE']
вот решение, которое использует только ядро 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")
. The 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
EDIT:
плакат позже указал, что память может быть проблемой, поэтому вот решение 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)
немного запутанно, но попробуйте это:
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
.
Я не собирался давать ответ здесь, поскольку проблема казалась немного сложной, поэтому я мог бы придумать только уродливое решение, но после прочтения комментария @ 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
Спасибо за все ваши ответы!
все они очень аккуратные, но некоторые приводят мой компьютер к его пределам, потому что мне приходится обрабатывать очень большие объемы данных.
Я, наконец, посмотрел на все ваши решения и построил немного другой:
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)
недостатком является то, что я должен определить каждую возможную форму атрибута. Возможно, есть способ сделать это автоматически,но я еще не нашел его.
скорость этого решение, по крайней мере, вполне приемлемо.