【问题标题】:Split data by year按年份拆分数据
【发布时间】:2011-12-13 22:33:57
【问题描述】:

我有这样的数据:

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

我现在想统计每年具有某种属性的不同 ID 的数量。

结果可能如下所示:

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

我计算出现次数的第二步可能很容易。

但是我如何将我的数据分成几年呢?

提前谢谢你!

【问题讨论】:

  • 您的意思是每年STARTEND之间的属性(AB)计数?
  • 是的,差不多。我的意思是每年从开始到结束的属性计数(A 和 B),并且没有重复计算 ID(就像我的输入示例的最后两行)
  • 到目前为止你尝试过什么?您可以为每一行创建一个年份序列,并将属性分配给每一年。当您对每一行数据执行此操作时,您只需使用属性计算年数。我敢肯定@daroczig 在他的路上有一个完整的代码答案。 :)
  • 我目前正在尝试拆分我的数据,以便我每年都有一个数据框,其中仅包含当年活跃的剧集。然而,这是非常复杂的,需要大量的代码。希望有更简单的方法

标签: r split dataframe


【解决方案1】:

这是使用一些 Hadley 软件包的方法。

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 包。以下是我们如何使用data.table 替换adply 调用。

require(data.table)
dfr2 = data.table(dfr2)[,list(YEAR = START:END),'ID, ATTRIBUTE']

【讨论】:

  • 哇,这是一个整洁的 (+) :)
  • 哇,太酷了。现在我得到一张带有START:END; Attribute1; Attribute2 的桌子。我怎么能用一年的时间拿到它?
  • 您查看dcast 语句的输出了吗?它遵循您在问题中描述的相同格式。如果这不是您想要的输出,请编辑您的问题以反映输出格式。
  • 嗯,我稍微修改了一下,因为 adply 需要很长时间。我又看了一遍
  • 请不要传播这种使用= 而不是<- 的糟糕想法。
【解决方案2】:

这是一个仅使用 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 列中的年份以及第2 和第3 列中的ATTRIBUTEID。例如,对应于@987654327 第一行的数据框@是第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 解决方案,它在 R 之外的 sqlite 中处理关键的大型中间结果(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)

【讨论】:

  • 与我的相比,这个答案是一个更简洁的单行 (+1) :)
【解决方案3】:

有点复杂,但试试这个:

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

它仍然重复计算 ID,但合并原始 data.frame 中的重叠范围可能会更容易。

【讨论】:

  • 不错!但是这个解决方案打破了我的内存限制(这是一个真正的大项目)
【解决方案4】:

我不打算在这里给出答案,因为这个问题似乎有点棘手,所以我只能提出一个丑陋的解决方案,但在阅读了@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

清除重复的行(通过合并基于IDATTRIBUTE 的年份):

> 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

并与一些applylapplydo.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

【讨论】:

  • 不错的解决方案!但如果范围不重叠,使用minmax 聚合将失败。例如 2002 - 2008 和 1997 - 1999。您的聚合函数会将其解释为 1997 - 2008,这不太正确。
  • @Ramnath 是绝对正确的,我没有想到那个。而且我知道使用substr从日期中提取年份也是一个丑陋的黑客,这应该通过使用date左右来完成,例如:format(as.Date(dfr$START, '%m-%d-%Y'), '%Y')
【解决方案5】:

感谢您的所有回答!

所有这些都非常整洁,但有些驱动我的计算机到了极限,因为我必须处理大量的数据。

我终于查看了您的所有解决方案并构建了一个略有不同的解决方案:

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)

缺点是,我必须定义属性的每个可能的形状。也许有一种方法可以自动执行此操作,但我还没有找到。

这个解决方案的速度至少是可以接受的。

【讨论】:

  • 为了回应您的评论,即您有大量输入,我在我的答案中添加了一个 sqldf 解决方案,该解决方案对 R 的内存要求最低。
猜你喜欢
  • 2012-03-03
  • 1970-01-01
  • 2017-11-04
  • 2017-11-05
  • 1970-01-01
  • 2018-12-06
  • 1970-01-01
  • 2015-10-28
  • 1970-01-01
相关资源
最近更新 更多