R subtracting 1 month from today's date gives NA

10,623

Solution 1

The calculation of months is indeed perfomed by base R but not the way your think. Months is used to get the month of a date object.

#Example
today <- Sys.Date()
months(today)
[1] "March"

To add or substract months, you should use %m+% from lubridate:

today <- Sys.Date()
today %m+% months(-1)
[1] "2017-02-28"

Solution 2

One month ago is non-defined in this context. February 29th only exists in leap years.

See the lubridate documentation:

Note: Arithmetic with periods can results in undefined behavior when non-existent dates are involved (such as February 29th in non-leap years). Please see Period-class for more details and %m+% and add_with_rollback for alternative operations.

The lubridate package can handle what you are doing, but you need to perform the operaton using %m+%.

Share:
10,623
Amy M
Author by

Amy M

Software and programming languages: R, STATA, Python Area of expertise: infectious disease epidemiology and microbiology Programming interests: automation of routine analyses and reporting, big data, automated data cleaning, probabilistic machine learning algorithms.

Updated on August 04, 2022

Comments

  • Amy M
    Amy M almost 2 years

    I have a script in which I subset my data according to some set time periods and wanted to subset all the records that had occurred in the last month.

    However if I try to subtract one month from today's date it yields an NA:

    > today <- Sys.Date()
    > today
    [1] "2017-03-29"
    > today - months(1)
    [1] NA
    

    I do have lubridate loaded but I think this calculation is being performed with base R. If I subtract 2 or more months it works fine:

    > today - months(2)
    [1] "2017-01-29"
    > today - months(3)
    [1] "2016-12-29"
    

    Does anyone have any ideas about what might be going on?

    UPDATE: I think this is something to do with simple date subtraction not handling leap year cases (2017 is not a leap year so "2017-02-29" does not exist).

    Are there other packages / functions that take into account leap years? For the above example I would expect the answer to revert to the last day of the previous month, i.e.:

    today - months(1)
    # Should yield:
    "2017-02-28"
    

    Would it make sense for this calculation to give the same results for both today and yesterday (or what is the ISO convention for this)?

    > sessionInfo()
    R version 3.3.2 (2016-10-31)
    Platform: x86_64-w64-mingw32/x64 (64-bit)
    Running under: Windows 7 x64 (build 7601) Service Pack 1
    
    locale:
    [1] LC_COLLATE=English_United Kingdom.1252  LC_CTYPE=English_United Kingdom.1252   
    [3] LC_MONETARY=English_United Kingdom.1252 LC_NUMERIC=C                           
    [5] LC_TIME=English_United Kingdom.1252    
    
    attached base packages:
    [1] stats     graphics  grDevices utils     datasets  methods   base     
    
    other attached packages:
     [1] xlsx_0.5.7         xlsxjars_0.6.1     rJava_0.9-8        MRAtools_0.6.8     stringdist_0.9.4.4 stringr_1.2.0     
     [7] stringi_1.1.3      lubridate_1.6.0    data.table_1.10.4  PKI_0.1-3          base64enc_0.1-3    digest_0.6.12     
    [13] getPass_0.1-1      RPostgreSQL_0.5-1  DBI_0.5-1         
    
    loaded via a namespace (and not attached):
    [1] magrittr_1.5   rstudioapi_0.6 tools_3.3.2    parallel_3.3.2
    
  • TARehman
    TARehman about 7 years
    When using lubridate, the months function is overwritten and does not function like base R.
  • Pierre Lapointe
    Pierre Lapointe about 7 years
    @TARehman No, the lubridate function is month with no "s". Base R has months
  • TARehman
    TARehman about 7 years
    Base R's months function won't take a number, but when I load lubridate, I can give months a number. Why is that? See also in the documentation: rdocumentation.org/packages/lubridate/versions/1.6.0/topics/‌​… which seems to suggest it is overriding the base R function.
  • Amy M
    Amy M about 7 years
    Have accepted @P Lapointe's worked answer but +1 for pointing me towards the specific help on this topic in lubridate.
  • Pierre Lapointe
    Pierre Lapointe about 7 years
    I think it's the %m+% function that accepts more inputs because lubridate::months(Sys.Date()) gives an error saying that months is not part of lubridate.
  • TARehman
    TARehman about 7 years
    That's genuinely weird, because when lubridate is loaded, months(1) returns "1m 0d 0H 0M 0S". I'm not a big user of lubridate, so really just more for my own information than anything. :)
  • Pierre Lapointe
    Pierre Lapointe about 7 years
    @TARehman I agree. Not sure why months(1) works after lubridate is loaded.
  • M.Viking
    M.Viking over 2 years