基于月份-年份时间格式对数据框进行排序。

4
我在处理一个非常基础的问题:基于时间格式(月份-年份,或者在这种情况下是“%B-%y”)对数据框进行排序。我的目标是计算各种月度统计数据,首先是总和。
数据框的相关部分如下所示*(这一步顺利完成,并符合我的目标。我在这里包含它,以展示问题可能出现的地方)*:
> tmp09
   Instrument AccountValue   monthYear   ExitTime
1         JPM         6997    april-07 2007-04-10
2         JPM         7261      mei-07 2007-05-29
3         JPM         7545     juli-07 2007-07-18
4         JPM         7614     juli-07 2007-07-19
5         JPM         7897 augustus-07 2007-08-22
10        JPM         7423 november-07 2007-11-02
11        KFT         6992      mei-07 2007-05-14
12        KFT         6944      mei-07 2007-05-21
13        KFT         7069     juli-07 2007-07-09
14        KFT         6919     juli-07 2007-07-16
# Order on the exit time, which corresponds with 'monthYear'
> tmp09.sorted <- tmp09[order(tmp09$ExitTime),]
> tmp09.sorted
   Instrument AccountValue   monthYear   ExitTime
1         JPM         6997    april-07 2007-04-10
11        KFT         6992      mei-07 2007-05-14
12        KFT         6944      mei-07 2007-05-21
2         JPM         7261      mei-07 2007-05-29
13        KFT         7069     juli-07 2007-07-09
14        KFT         6919     juli-07 2007-07-16
3         JPM         7545     juli-07 2007-07-18
4         JPM         7614     juli-07 2007-07-19
5         JPM         7897 augustus-07 2007-08-22
10        JPM         7423 november-07 2007-11-02

到目前为止,根据ExitTime进行排序是有效的。但当我尝试计算每月的总数,并试图对此输出进行排序时,问题就出现了:

# Calculate the total results per month
> Tmp09Totals <- tapply(tmp09.sorted$AccountValue, tmp09.sorted$monthYear, sum)
> Tmp09Totals <- data.frame(Tmp09Totals)
> Tmp09Totals
            Tmp09Totals
april-07           6997
augustus-07        7897
juli-07           29147
mei-07            21197
november-07        7423

如何按时间顺序排序输出结果?

我已经尝试过(除了各种尝试将 monthYear 转换为其他日期格式之外):order、sort、sort.list、sort_df、reshape,并根据 tapply、lapply、sapply、aggregate 计算总和。甚至重写行名(通过为它们赋予从1到 length(tmp09.sorted2$AccountValue) 的数字)也不起作用。我还尝试基于在另一个问题中学到的内容为每个月份-年份分配不同的 ID,但是 R 也很难区分不同的月份-年份值。

这个输出的正确顺序应该是 april-07,mei-07,juli-07,augustus07, november-07

apr-07  6997
mei-07  21197
jul-07  29147
aug-07  7897
nov-07  7423
6个回答

9

最好分别列出MonthYear,并按正确顺序使用tapply对两个变量的联合数据进行操作,例如:

## The Month factor
tmp09 <- within(tmp09,
                Month <- droplevels(factor(strftime(ExitTime, format = "%B"),
                                                    levels = month.name)))
## for @Jura25's locale, we can't use the in built English constant
## instead, we can use this solution, from ?month.name:
## format(ISOdate(2000, 1:12, 1), "%B"))
tmp09 <- within(tmp09,
                Month <- droplevels(factor(strftime(ExitTime, format = "%B"),
                                                    levels = format(ISOdate(2000, 1:12, 1), "%B"))))
##
## And the Year factor
tmp09 <- within(tmp09, Year <- factor(strftime(ExitTime, format = "%Y")))

这在我的语言环境中是:

> head(tmp09)
   Instrument AccountValue   monthYear   ExitTime    Month Year
1         JPM         6997    april-07 2007-04-10    April 2007
2         JPM         7261      mei-07 2007-05-29      May 2007
3         JPM         7545     juli-07 2007-07-18     July 2007
4         JPM         7614     juli-07 2007-07-19     July 2007
5         JPM         7897 augustus-07 2007-08-22   August 2007
10        JPM         7423 november-07 2007-11-02 November 2007

然后使用tapply同时处理这两个因素:

> with(tmp09, tapply(AccountValue, list(Month, Year), sum))
          2007
April     6997
May      21197
July     29147
August    7897
November  7423

或者通过aggregate
> with(tmp09, aggregate(AccountValue, list(Month = Month, Year = Year), sum))
     Month Year     x
1    April 2007  6997
2      May 2007 21197
3     July 2007 29147
4   August 2007  7897
5 November 2007  7423

感谢您详细的回答,Gavin!这个方法正好符合我的需求。特别是聚合函数为完整数据集中的各年份提供了漂亮的堆叠输出(对于各种计算也非常高效)。我在使用 'levels = month.name' 时遇到了错误,但是在将 month.name 替换为自定义向量(包含本地语言的月份名称)后,问题得到了解决。 :) 谢谢! - Jos
@Jura25;是的,抱歉 - 那些是英文月份... ?month.name 在您当前语言环境的月份名称方面有以下示例:format(ISOdate(2000, 1:12, 1), "%B"),这可能会节省您每次使用它们时键入月份名称的时间。 - Gavin Simpson
不用道歉。:) 我主要是在这里提到它,以防其他人可能会用到它。感谢ISOdate函数,我不知道它,确实非常方便。 - Jos

4
尝试使用zoo中的"yearmon"类,因为它可以适当地排序。下面我们创建一个样本DF数据框,然后添加一个"YearMonth"列,其类别为"yearmon"。最后,我们进行聚合。实际处理只需要最后两行代码(其他部分仅用于创建示例数据框)。
Lines <-   "Instrument AccountValue   monthYear   ExitTime
JPM         6997    april-07 2007-04-10
JPM         7261      mei-07 2007-05-29
JPM         7545     juli-07 2007-07-18
JPM         7614     juli-07 2007-07-19
JPM         7897 augustus-07 2007-08-22
JPM         7423 november-07 2007-11-02
KFT         6992      mei-07 2007-05-14
KFT         6944      mei-07 2007-05-21
KFT         7069     juli-07 2007-07-09
KFT         6919     juli-07 2007-07-16"
library(zoo)
DF <- read.table(textConnection(Lines), header = TRUE)

DF$YearMonth <- as.yearmon(DF$ExitTime)
aggregate(AccountValue ~ YearMonth + Instrument, DF, sum)

这将得到以下结果:
> aggregate(AccountValue ~ YearMonth + Instrument, DF, sum)
  YearMonth Instrument AccountValue
1  Apr 2007        JPM         6997
2  May 2007        JPM         7261
3  Jul 2007        JPM        15159
4  Aug 2007        JPM         7897
5  Nov 2007        JPM         7423
6  May 2007        KFT        13936
7  Jul 2007        KFT        13988

一种稍微不同的方法和输出是直接使用read.zoo。它为每个仪器生成一列,为每年/月生成一行。我们读取列并分配适当的类,对于monthYear列,我们使用"NULL",因为我们不会使用它。我们还指定时间索引是剩余列的第三列,并且我们希望将输入按第一列拆分为列。FUN=as.yearmon表示我们希望将时间索引从"Date"类转换为"yearmon"类,并使用sum聚合所有内容。
z <- read.zoo(textConnection(Lines),  header = TRUE, index = 3, 
     split = 1, colClasses = c("character", "numeric", "NULL", "Date"),
     FUN = as.yearmon, aggregate = sum)

生成的动物园对象如下所示:
> z
           JPM   KFT
Apr 2007  6997    NA
May 2007  7261 13936
Jul 2007 15159 13988
Aug 2007  7897    NA
Nov 2007  7423    NA

我们可能更喜欢将它保留为zoo对象,以利用zoo中的其他功能,或者我们可以将其转换为数据框,如下所示:data.frame(Time = time(z), coredata(z)),这将使时间成为单独的一列,或者使用as.data.frame(z),它使用行名称来表示时间。fortify.zoo(z)也适用。

谢谢 G. Grothendieck!我非常喜欢 as.yearmon 函数的输出,它让我不用使用多个列(年和月)作为列表,还能得到更好的输出。我已经将其纳入我的脚本,使其更加全面。 :) - Jos

3
您可以使用 reorder 函数重新排序因子水平。
tmp09$monthYear <- reorder(tmp09$monthYear, as.numeric(as.Date(tmp09$ExitTime)))

技巧在于将日期的数字表示法作为距离1970年1月1日的天数(参见?Date),并使用其平均值作为参考。


谢谢Marek,这确实是一种非常高效的方法。不幸的是,它对我不起作用。reorder(tmp09$monthYear, as.numeric(as.Date(tmp09$ExitTime))) [1] april-07 mei-07 juli-07 juli-07 augustus-07 november-07 [7] mei-07 mei-07 juli-07 juli-07。也许reorder不“知道”我的当前语言环境? - Jos
1
@Jura25 reorder 不会改变值,只会改变因子中级别的顺序。当您在更改后的数据上调用tapply时,您将看到效果。如果您运行tapply(tmp09.sorted$AccountValue, reorder(tmp09.sorted$monthYear, as.numeric(as.Date(tmp09.sorted$ExitTime))), sum),您会得到什么? - Marek
谢谢Marek,这解决了问题! :) 我没有想到重新排序的功能(而不是排序),感谢您的解释! - Jos

1

看起来主要问题是如何按照时间顺序对一系列的月份-年份字符串进行排序。最简单的方法是在每个月份-年份字符串的开头添加"01",然后将它们作为普通日期进行排序。所以拿到你的最终数据框Tmp09Totals,然后这样做:

monYear <- rownames(Tmp09Totals)
sortedMonYear <- format(sort( as.Date( paste('01-', monYear, sep = ''),
                                       '%d-%B-%y')), 
                       '%B-%y')
Tmp09Totals[ sortedMonYear, , drop = FALSE]

但是如果你一开始就按照正确的顺序获取因素,你就不需要进行任何操作。你的答案重新排列了输出。然而,如果你没有按照逻辑顺序获取输入,那么你必须对从这些数据产生的每个输出进行重新排序。 - Gavin Simpson
感谢您的回复,Prasad。看起来有点复杂,但我刚刚测试了一下,它运行良好(即使是多年)。我真的很喜欢您创新的方法,在“monthYear”之前粘贴“01”,以便能够将其转换为常规日期。这是一个在我的未来R探险中需要记住的好方法。 :) 谢谢! - Jos

1

编辑:我一开始误解了问题。首先复制问题中给出的数据,然后

> tmp09 <- read.table(file="clipboard", header=TRUE)
> Sys.setlocale(category="LC_TIME", locale="Dutch_Belgium.1252")
[1] "Dutch_Belgium.1252"

# create POSIXlt variable from monthYear
> tmp09$d <- strptime(paste("2007", tmp09$monthYear, sep="-"), "%Y-%B-%d")

# create ordered factor
> tmp09$dFac <- droplevels(cut(tmp09$d, breaks="month", ordered=TRUE))
> tmp09[order(tmp09$d), ]
   Instrument AccountValue   monthYear   ExitTime          d       dFac
1         JPM         6997    april-07 2007-04-10 2007-04-07 2007-04-01
2         JPM         7261      mei-07 2007-05-29 2007-05-07 2007-05-01
11        KFT         6992      mei-07 2007-05-14 2007-05-07 2007-05-01
12        KFT         6944      mei-07 2007-05-21 2007-05-07 2007-05-01
3         JPM         7545     juli-07 2007-07-18 2007-07-07 2007-07-01
4         JPM         7614     juli-07 2007-07-19 2007-07-07 2007-07-01
13        KFT         7069     juli-07 2007-07-09 2007-07-07 2007-07-01
14        KFT         6919     juli-07 2007-07-16 2007-07-07 2007-07-01
5         JPM         7897 augustus-07 2007-08-22 2007-08-07 2007-08-01
10        JPM         7423 november-07 2007-11-02 2007-11-07 2007-11-01

> Tmp09Totals <- tapply(tmp09$AccountValue, tmp09$dFac, sum)
> Tmp09Totals
2007-04-01 2007-05-01 2007-07-01 2007-08-01 2007-11-01 
      6997      21197      29147       7897       7423

感谢您的回复。Sys.setlocale()函数是一个非常好的想法,但我担心它并没有起到太大的作用。新列(tmp09$d)存在一些错误,但按ExitTime列排序确实有效。但我特别关注Tmp09Totals列的排序,该列包含基于monthYear列的各个月份的总和。如果我的答案不够清晰,我非常抱歉,我会编辑它以更好地阐明我的观点。尽管如此,感谢您的回复并提出解决方案。非常感谢! - Jos
1
感谢澄清。遗漏的步骤是使用 cut() 从日期创建有序因子。我希望这更接近您所想要的。 - caracal
感谢Caracal的回答!它确实给出了我想要的输出,但是对于每一年都是“2007”的多年份,粘贴函数会有些问题。然而,我从你的答案中学到了一些有用的“技巧”(比如剪切),所以你的努力并不是全部白费。谢谢! :) - Jos

0

虽然这是一篇旧文,但还是值得采用 data.table 的方法:

按照 @caracal 描述的方式读取数据并设置本地环境。

> Sys.setlocale(category="LC_TIME", locale="Dutch_Belgium.1252")
[1] "Dutch_Belgium.1252"
> tmp09 <- read.table(file="clipboard", header=TRUE)
> tmp09$ExitTime <- as.Date(tmp09$ExitTime)

按要求汇总数据

require(data.table)
> data.table(tmp09)[, 
+                   .(Tmp09Total = sum(AccountValue)),
+                   by = .(Date = format(ExitTime, "%B-%y"))]
          Date Tmp09Total
1:    april-07       6997
2:      mei-07      21197
3:     juli-07      29147
4: augustus-07       7897
5: november-07       7423

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接