开发者

How to subtract months from a date in R?

开发者 https://www.devze.com 2023-02-15 04:29 出处:网络
I\'m trying to subtract n months from a date as follows: maturity <- as.Date("2012/12/开发者_运维知识库31")

I'm trying to subtract n months from a date as follows:

maturity <- as.Date("2012/12/开发者_运维知识库31")

m <- as.POSIXlt(maturity)

m$mon <- m$mon - 6

but the resulting date is 01-Jul-2012, and not 30-Jun-2012, as I should expect. Is there any short way to get such result?


1) seq.Date. Note that June has only 30 days so it cannot give June 31st thus instead it gives July 1st.

seq(as.Date("2012/12/31"), length = 2, by = "-6 months")[2]
## [1] "2012-07-01"

If we knew it was at month end we could do this:

seq(as.Date(cut(as.Date("2012/12/31"), "month")), length=2, by="-5 month")[2]-1
## "2012-06-30"

2) yearmon. Also if we knew it was month end then we could use the "yearmon" class of the zoo package like this:

library(zoo)
as.Date(as.yearmon(as.Date("2012/12/31")) -.5, frac = 1)
## [1] "2012-06-30"

This converts the date to "yearmon" subtracts 6 months (.5 of a year) and then converts it back to "Date" using frac=1 which means the end of the month (frac=0 would mean the beginning of the month). This also has the advantage over the previous solution that it is vectorized automatically, i.e. as.Date(...) could have been a vector of dates.

Note that if "Date" class is only being used as a way of representing months then we can get rid of it altogether and directly use "yearmon" since that models what we want in the first place:

as.yearmon("2012-12") - .5
## [1] "Jun 2012"

3) mondate. A third solution is the mondate package which has the advantage here that it returns the end of the month 6 months ago without having to know that we are month end:

library(mondate)
mondate("2011/12/31") - 6
## mondate: timeunits="months"
## [1] 2011/06/30

This is also vectorized.

4) lubridate. This lubridate answer has been changed in line with changes in the package:

library(lubridate)
as.Date("2012/12/31") %m-% months(6)
## [1] "2012-06-30"

lubridate is also vectorized.

5) sqldf/SQLite

library(sqldf)
sqldf("select date('2012-12-31', '-6 months') as date")
##         date
## 1 2012-07-01

or if we knew we were at month end:

sqldf("select date('2012-12-31', '+1 day', '-6 months', '-1 day') as date")
##         date
## 1 2012-06-30


you can use lubridate package for this

library(lubridate)
maturity <- maturity %m-% months(6)

there is no reason for changing the day field.

you can set your day field back to the last day in that month by

day(maturity) <- days_in_month(maturity)


lubridate works correctly with such calculations:

library(lubridate)
as.Date("2000-01-01") - days(1)    # 1999-12-31
as.Date("2000-03-31") - months(1)  # 2000-02-29

but sometimes fails:

as.Date("2000-02-29") - years(1)   # NA, should be 1999-02-28


tidyverse has added the clock package in addition to the lubridate package that has nice functionality for this:

library(clock)

# sequence of dates
date_build(2018, 1:5, 31, invalid = "previous")
[1] "2018-01-31" "2018-02-28" "2018-03-31" "2018-04-30" "2018-05-31"

When the date is sequenced, 2018-02-31 is not a valid date. The invalid argument makes explicit what to do in this case: go to the last day of the "previous" valid date.

There is also a series add functions, but in your case you would use add_months. Again it has the invalid argument that you can specify:

x <- as.Date("2022-03-31")

# The previous valid moment in time
add_months(x, -1, invalid = "previous")
[1] "2022-02-28"

# The next valid moment in time, 2022-02-31 is not a valid date
add_months(x, -1, invalid = "next")
[1] "2022-03-01"

# Overflow the days. There were 28 days in February, 2020, but we
# specified 31. So this overflows 3 days past day 28.
add_months(x, -1, invalid = "overflow")
[1] "2022-03-03"

You can also specify invalid to be NA or if you leave off this argument you could get an error.


Technically you cannot add/subtract 1 month to all dates (although you can add/subtract 30 days to all dates, but I suppose, that's not something you want). I think this is what you are looking for

> lubridate::ceiling_date(as.Date("2020-01-31"), unit = "month")
[1] "2020-02-01"
> lubridate::floor_date(as.Date("2020-01-31"), unit = "month")
[1] "2020-01-01"


UPDATE, I just realised that Tung-nguyen also wrote the same method and has a two line version here https://stackoverflow.com/a/44690219/19563460 Keeping this answer here so newbies can see different ways of doing it

With the R updates, you can now do this easily in base R using seq.date(). Here are some examples of implementing this that should work without additional packages

ANSWER 1: typing directly

maturity <- as.Date("2012/12/31")

seq(maturity, length.out=2, by="-3 months")[2]

# see here for more help
?seq.date

ANSWER 2: Adding in some flexibility, e.g. 'n' months

maturity <- as.Date("2012/12/31")
n  <- 3
bytime <- paste("-",n," months",sep="")

seq(maturity,length.out=2,by=bytime)[2]

ANSWER 3: Make a function

# Here's a little function that will let you add X days/months/weeks
# to any base R date.  Commented for new users

#---------------------------------------------------------
# MyFunction
# DateIn, either a date or a string that as.Date can convert into one
# TimeBack, number of units back/forward
# TimeUnit, unit of time e.g. "weeks"/"month"/"days"
# Direction can be "back" or "forward", not case sensitive
#---------------------------------------------------------

MyFunction <- function(DateIn,TimeBack,TimeUnit,Direction="back"){
   #--- Set up the by string
   if(tolower(Direction)=="back"){
      bystring <- paste("-",TimeBack," ",tolower(TimeUnit),sep="")
   }else{
      bystring <- paste(TimeBack," ",tolower(TimeUnit),sep="")
   }   

   #--- Return the new date using seq in the base package
   output <- seq(as.Date(DateIn),length.out=2,by=bystring)[2]
   return(output)
}


# EXAMPLES
  
MyFunction("2000-02-29",3,"months","forward")


Answer <-  MyFunction(DateIn="2002-01-01",TimeBack=14,
         TimeUnit="weeks",Direction="back")
print(Answer)


maturity <- as.Date("2012/12/31")
n  <- 3
MyFunction(DateIn=maturity,TimeBack=n,TimeUnit="months",Direction="back")

ANSWER 4: I quite like my little function, so I just uploaded it to my mini personal R package.

This is freely available, so now technically the answer is use the JumpDate function from the Greatrex.Functions package

Can't guarantee it'll work forever and no support available, but you're welcome to use it.

# Install/load my package
install.packages("remotes")
remotes::install_github('hgreatrex/Greatrex.Functions',force=TRUE)
library(Greatrex.Functions)

# run it
maturity <- as.Date("2012/12/31")
n  <- 3
Answer <- JumpDate(DateIn=maturity,TimeBack=n,TimeUnit="months",
         Direction="back",verbose=TRUE)
print(Answer)


JumpDate("2000-02-29",3,"months","forward")

# Help file here
?Greatrex.Functions::JumpDate

You can see how I made the function/package here: https://github.com/hgreatrex/Greatrex.Functions/blob/master/R/JumpDate.r

With nice instructions here on making your own mini compilation of functions. http://web.mit.edu/insong/www/pdf/rpackage_instructions.pdf
and here How do I insert a new function into my R package?

Hope that helps! I hope it's also useful to see the different levels of designing an answer to a coding problem, depending on how often you need it and the level of flexibility you need.

0

精彩评论

暂无评论...
验证码 换一张
取 消