I'm hoping to retrieve the month number from a fiscal year that starts in November (i.e. the first day of the fiscal year is November 1st). The following code provides my desired output, borrowing the week_start syntax of lubridate::wday, where year_start is analogous to week_start:
library('lubridate')
dateToRetrieve = ymd('2017-11-05')
#output: [1] "2017-11-05"
monthFromDate = month(dateToRetrieve, year_start=11)
#output: [1] 1
Since this functionality doesn't yet exist, I'm looking for an alternative solution that provides the same output. Adding period(10, units="month") to each date does not work because the length of different months leads to issues translating between months (e.g. March 31st minus a month = February 31st, which doesn't make sense).
I checked a somewhat similar question on the lubridate github here, but didn't see any solutions. Does anyone have an idea that will provide my desired functionality?
Many thanks,
1) lubridate Below x can be a character vector or a Date vector:
x <- "2017-11-05" # test data
(month(x) - 11) %% 12 + 1
## [1] 1
2) Base R To do this with only base R first calculate the month number giving mx as shown and then perform the same computation:
mx <- as.POSIXlt(x)$mon + 1
(mx - 11) %% 12 + 1
## [1] 1
It is a not pretty way... but you could create a vector range of months starting at November, call the full month of the date object, then match the two objects together to get the vector position.
suppressPackageStartupMessages(library('lubridate'))
x <- format(ISOdate(2004,1:12,1),"%B")[c(11,12,1:10)]
match(as.character(month(ymd('2017-11-05'), label = TRUE, abbr = FALSE)), x)
#> [1] 1
match(as.character(month(ymd('2017-01-15'), label = TRUE, abbr = FALSE)), x)
#> [1] 3
match(as.character(month(ymd('2017-05-01'), label = TRUE, abbr = FALSE)), x)
#> [1] 7
Related
I'm currently writing a script in the R Programming Language and I've hit a snag.
I have time series data organized in a way where there are 30 days in each month for 12 months in 1 year. However, I need the data organized in a proper 365 days in a year calendar, as in 30 days in a month, 31 days in a month, etc.
Is there a simple way for R to recognize there are 30 days in a month and to operate within that parameter? At the moment I have my script converting the number of days from the source in UNIX time and it counts up.
For example:
startingdate <- "20060101"
endingdate <- "20121230"
date <- seq(from = as.Date(startingdate, "%Y%m%d"), to = as.Date(endingdate, "%Y%m%d"), by = "days")
This would generate an array of dates with each month having 29 days/30 days/31 days etc. However, my data is currently organized as 30 days per month, regardless of 29 days or 31 days present.
Thanks.
The first 4 solutions are basically variations of the same theme using expand.grid. (3) uses magrittr and the others use no packages. The last two work by creating long sequence of numbers and then picking out the ones that have month and day in range.
1) apply This gives a series of yyyymmdd numbers such that there are 30 days in each month. Note that the line defining yrs in this case is the same as yrs <- 2006:2012 so if the years are handy we could shorten that line. Omit as.numeric in the line defining s if you want character string output instead. Also, s and d are the same because we have whole years so we could omit the line defining d and use s as the answer in this case and also in general if we are always dealing with whole years.
startingdate <- "20060101"
endingdate <- "20121230"
yrs <- seq(as.numeric(substr(startingdate, 1, 4)), as.numeric(substr(endingdate, 1, 4)))
g <- expand.grid(yrs, sprintf("%02d", 1:12), sprintf("%02d", 1:30))
s <- sort(as.numeric(apply(g, 1, paste, collapse = "")))
d <- s[ s >= startingdate & s <= endingdate ] # optional if whole years
Run some checks.
head(d)
## [1] 20060101 20060102 20060103 20060104 20060105 20060106
tail(d)
## 20121225 20121226 20121227 20121228 20121229 20121230
length(d) == length(2006:2012) * 12 * 30
## [1] TRUE
2) no apply An alternative variation would be this. In this and the following solutions we are using yrs as calculated in (1) so we omit it to avoid redundancy. Also, in this and the following solutions, the corresponding line to the one setting d is omitted, again, to avoid redundancy -- if you don't have whole years then add the line defining d in (1) replacing s in that line with s2.
g2 <- expand.grid(yr = yrs, mon = sprintf("%02d", 1:12), day = sprintf("%02d", 1:30))
s2 <- with(g2, sort(as.numeric(paste0(yr, mon, day))))
3) magrittr This could also be written using magrittr like this:
library(magrittr)
expand.grid(yr = yrs, mon = sprintf("%02d", 1:12), day = sprintf("%02d", 1:30)) %>%
with(paste0(yr, mon, day)) %>%
as.numeric %>%
sort -> s3
4) do.call Another variation.
g4 <- expand.grid(yrs, 1:12, 1:30)
s4 <- sort(as.numeric(do.call("sprintf", c("%d%02d%02d", g4))))
5) subset sequence Create a sequence of numbers from the starting date to the ending date and if each number is of the form yyyymmdd pick out those for which mm and dd are in range.
seq5 <- seq(as.numeric(startingdate), as.numeric(endingdate))
d5 <- seq5[ seq5 %/% 100 %% 100 %in% 1:12 & seq5 %% 100 %in% 1:30]
6) grep Using seq5 from (5)
d6 <- as.numeric(grep("(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|30)$", seq5, value = TRUE))
Here's an alternative:
date <- unclass(startingdate):unclass(endingdate) %% 30L
month <- rep(1:12, each = 30, length.out = NN <- length(date))
year <- rep(1:(NN %/% 360 + 1), each = 360, length.out = NN)
(of course, we can easily adjust by adding constants to taste if you want a specific day to be 0, or a specific month, etc.)
I have this number
20101213 which is a representation of this data 2010 Dec 13th I want to extract the year, month and day numbers from that number. So I should have three variables contain the values.
What I have tried:
value = 20101213
as.numeric(strsplit(as.character(value), "")[[1]])
The result is [1] 2 0 1 0 1 0 1 0
but I didn't know how to continue, may you help me please
You probably want to get this into a date-time format anyways for future computing, so how about:
(x <- strptime(20101213, "%Y%m%d"))
# [1] "2010-12-13 EST"
This will enable you to do computations that you wouldn't have been able to with just the year, month number, and day number, such as grabbing the day of the week (0=Sunday, 1=Monday, ...) or day of the year:
x$wday
# [1] 1
x$yday
# [1] 346
Further, you could easily extract the year, month number, and day of month number:
c(x$year+1900, x$mon+1, x$mday)
# [1] 2010 12 13
Edit: As pointed out by #thelatemail, an alternative that doesn't involve remembering offsets is:
as.numeric(c(format(x, "%Y"), format(x, "%m"), format(x, "%d")))
# [1] 2010 12 13
year <- as.numeric(substr(as.character(value),start = 1,stop = 4))
month <- as.numeric(substr(as.character(value),start = 5,stop = 6))
day <- as.numeric(substr(as.character(value),start = 7,stop = 8))
If you don't want to deal with string representation you could also just use mod function like this:
# using mod
year = floor(value/10000)
month = floor((value %% 10000)/100)
day = value %% 100
Which will then extract the relevant parts of the number as expected.
I have a vector of dates of the form BW01.68, BW02.68, ... , BW26.10. BW stands for "bi-week", so for example, "BW01.68" represents the first bi-week of the year 1968, and "BW26.10" represents the 26th (and final) bi-week of the year 2010. Using R, how could I convert this vector into actual dates, say, of the form 01-01-1968, 01-15-1968, ... , 12-16-2010? Is there a way for R to know exactly which dates correspond to each bi-week? Thanks for any help!
An alternative solution.
biwks <- c("BW01.68", "BW02.68", "BW26.10")
bw <- substr(biwks,3,4)
yr <- substr(biwks,6,7)
yr <- paste0(ifelse(as.numeric(yr) > 15,"19","20"),yr)
# the %j in the date format is the number of days into the year
as.Date(paste(((as.numeric(bw)-1) * 14) + 1,yr,sep="-"),format="%j-%Y")
#[1] "1968-01-01" "1968-01-15" "2010-12-17"
Though I will note that a 'bi-week' seems a strange measure and I can't be sure that just using 14 day blocks is what is intended in your work.
You can make this code a lot shorter. I have spaced out each step to help understanding but you could finish it off in one (long) line of code.
bw <- c('BW01.68', 'BW02.68','BW26.10','BW22.13')
# the gsub will ensure that bw01.1 the same as bw01.01, bw1.01, or bw1.1
#isolating year no
yearno <- as.numeric(
gsub(
x = bw,
pattern = "BW.*\\.",
replacement = ""
)
)
#isolating and converting bw to no of days
dayno <- 14 * as.numeric(
gsub(
x = bw,
pattern = "BW|\\.[[:digit:]]{1,2}",
replacement = ""
)
)
#cutoff year chosen as 15
yearno <- yearno + 1900
yearno[yearno < 1915] <- yearno[yearno < 1915] + 100
# identifying dates
dates <- as.Date(paste0('01/01/',yearno),"%d/%m/%Y") + dayno
# specifically identifinyg mondays of that week no
mondaydates <- dates - as.numeric(strftime(dates,'%w')) + 1
Output -
> bw
[1] "BW01.68" "BW02.68" "BW26.10" "BW22.13"
> dates
[1] "1968-01-15" "1968-01-29" "2010-12-31" "2013-11-05"
> mondaydates
[1] "1968-01-15" "1968-01-29" "2010-12-27" "2013-11-04"
PS: Just be careful that you're aligned with how bw is measured in your data and whether you're translating it correctly. You should be able to manipulate this to get it to work, for instance you might encounter a bw 27.
I struggle mightily with dates in R and could do this pretty easily in SPSS, but I would love to stay within R for my project.
I have a date column in my data frame and want to remove the year completely in order to leave the month and day. Here is a peak at my original data.
> head(ds$date)
[1] "2003-10-09" "2003-10-11" "2003-10-13" "2003-10-15" "2003-10-18" "2003-10-20"
> class((ds$date))
[1] "Date"
I "want" it to be.
> head(ds$date)
[1] "10-09" "10-11" "10-13" "10-15" "10-18" "10-20"
> class((ds$date))
[1] "Date"
If possible, I would love to set the first date to be October 1st instead of January 1st.
Any help you can provide will be greatly appreciated.
EDIT: I felt like I should add some context. I want to plot an NHL player's performance over the course of a season which starts in October and ends in April. To add to this, I would like to facet the plots by each season which is a separate column in my data frame. Because I want to compare cumulative performance over the course of the season, I believe that I need to remove the year portion, but maybe I don't; as I indicated, I struggle with dates in R. What I am looking to accomplish is a plot that compares cumulative performance over relative dates by season and have the x-axis start in October and end in April.
> d = as.Date("2003-10-09", format="%Y-%m-%d")
> format(d, "%m-%d")
[1] "10-09"
Is this what you are looking for?
library(ggplot2)
## make up data for two seasons a and b
a = as.Date("2010/10/1")
b = as.Date("2011/10/1")
a.date <- seq(a, by='1 week', length=28)
b.date <- seq(b, by='1 week', length=28)
## make up some score data
a.score <- abs(trunc(rnorm(28, mean = 10, sd = 5)))
b.score <- abs(trunc(rnorm(28, mean = 10, sd = 5)))
## create a data frame
df <- data.frame(a.date, b.date, a.score, b.score)
df
## Since I am using ggplot I better create a "long formated" data frame
df.molt <- melt(df, measure.vars = c("a.score", "b.score"))
levels(df.molt$variable) <- c("First season", "Second season")
df.molt
Then, I am using ggplot2 for plotting the data:
## plot it
ggplot(aes(y = value, x = a.date), data = df.molt) + geom_point() +
geom_line() + facet_wrap(~variable, ncol = 1) +
scale_x_date("Date", format = "%m-%d")
If you want to modify the x-axis (e.g., display format), then you'll probably be interested in scale_date.
You have to remember Date is a numeric format, representing the number of days passed since the "origin" of the internal date counting :
> str(Date)
Class 'Date' num [1:10] 14245 14360 14475 14590 14705 ...
This is the same as in EXCEL, if you want a reference. Hence the solution with format as perfectly valid.
Now if you want to set the first date of a year as October 1st, you can construct some year index like this :
redefine.year <- function(x,start="10-1"){
year <- as.numeric(strftime(x,"%Y"))
yearstart <- as.Date(paste(year,start,sep="-"))
year + (x >= yearstart) - min(year) + 1
}
Testing code :
Start <- as.Date("2009-1-1")
Stop <- as.Date("2011-11-1")
Date <- seq(Start,Stop,length.out=10)
data.frame( Date=as.character(Date),
year=redefine.year(Date))
gives
Date year
1 2009-01-01 1
2 2009-04-25 1
3 2009-08-18 1
4 2009-12-11 2
5 2010-04-05 2
6 2010-07-29 2
7 2010-11-21 3
8 2011-03-16 3
9 2011-07-09 3
10 2011-11-01 4
I have two data frames in R. One frame has a persons year of birth:
YEAR
/1931
/1924
and then another column shows a more recent time.
RECENT
09/08/2005
11/08/2005
What I want to do is subtract the years so that I can calculate their age in number of years, however I am not sure how to approach this. Any help please?
The following function takes a vectors of Date objects and calculates the ages, correctly accounting for leap years. Seems to be a simpler solution than any of the other answers.
age = function(from, to) {
from_lt = as.POSIXlt(from)
to_lt = as.POSIXlt(to)
age = to_lt$year - from_lt$year
ifelse(to_lt$mon < from_lt$mon |
(to_lt$mon == from_lt$mon & to_lt$mday < from_lt$mday),
age - 1, age)
}
You can solve this with the lubridate package.
> library(lubridate)
I don't think /1931 is a common date class. So I'll assume all the entries are character strings.
> RECENT <- data.frame(recent = c("09/08/2005", "11/08/2005"))
> YEAR <- data.frame(year = c("/1931", "/1924"))
First, let's notify R that the recent dates are dates. I'll assume the dates are in month/day/year order, so I use mdy(). If they're in day/month/year order just use dmy().
> RECENT$recent <- mdy(RECENT$recent)
recent
1 2005-09-08
2 2005-11-08
Now, lets turn the years into numbers so we can do some math with them.
> YEAR$year <- as.numeric(substr(YEAR$year, 2, 5))
Now just do the math. year() extracts the year value of the RECENT dates.
> year(RECENT$recent) - YEAR
year
1 74
2 81
p.s. if your year entries are actually full dates, you can get the difference in years with
> YEAR1 <- data.frame(year = mdy("01/08/1931","01/08/1924"))
> as.period(RECENT$recent - YEAR1$year, units = "year")
[1] 74 years and 8 months 81 years and 10 months
I use a custom function, see code below, convenient to use in mutate and quite flexible (you'll need the lubridate package).
Examples
get_age("2000-01-01")
# [1] 17
get_age(lubridate::as_date("2000-01-01"))
# [1] 17
get_age("2000-01-01","2015-06-15")
# [1] 15
get_age("2000-01-01",dec = TRUE)
# [1] 17.92175
get_age(c("2000-01-01","2003-04-12"))
# [1] 17 14
get_age(c("2000-01-01","2003-04-12"),dec = TRUE)
# [1] 17.92176 14.64231
Function
#' Get age
#'
#' Returns age, decimal or not, from single value or vector of strings
#' or dates, compared to a reference date defaulting to now. Note that
#' default is NOT the rounded value of decimal age.
#' #param from_date vector or single value of dates or characters
#' #param to_date date when age is to be computed
#' #param dec return decimal age or not
#' #examples
#' get_age("2000-01-01")
#' get_age(lubridate::as_date("2000-01-01"))
#' get_age("2000-01-01","2015-06-15")
#' get_age("2000-01-01",dec = TRUE)
#' get_age(c("2000-01-01","2003-04-12"))
#' get_age(c("2000-01-01","2003-04-12"),dec = TRUE)
get_age <- function(from_date,to_date = lubridate::now(),dec = FALSE){
if(is.character(from_date)) from_date <- lubridate::as_date(from_date)
if(is.character(to_date)) to_date <- lubridate::as_date(to_date)
if (dec) { age <- lubridate::interval(start = from_date, end = to_date)/(lubridate::days(365)+lubridate::hours(6))
} else { age <- lubridate::year(lubridate::as.period(lubridate::interval(start = from_date, end = to_date)))}
age
}
You can do some formating:
as.numeric(format(as.Date("01/01/2010", format="%m/%d/%Y"), format="%Y")) - 1930
With your data:
> yr <- c(1931, 1924)
> recent <- c("09/08/2005", "11/08/2005")
> as.numeric(format(as.Date(recent, format="%m/%d/%Y"), format="%Y")) - yr
[1] 74 81
Since you have your data in a data.frame (I'll assume that it's called df), it will be more like this:
as.numeric(format(as.Date(df$recent, format="%m/%d/%Y"), format="%Y")) - df$year
Given the data in your example:
> m <- data.frame(YEAR=c("/1931", "/1924"),RECENT=c("09/08/2005","11/08/2005"))
> m
YEAR RECENT
1 /1931 09/08/2005
2 /1924 11/08/2005
Extract year with the strptime function:
> strptime(m[,2], format = "%m/%d/%Y")$year - strptime(m[,1], format = "/%Y")$year
[1] 74 81
Based on the previous answer, convert your columns to date objects and subtract. Some conversion of types between character and numeric is necessary:
> foo=data.frame(RECENT=c("09/08/2005","11/08/2005"),YEAR=c("/1931","/1924"))
> foo
RECENT YEAR
1 09/08/2005 /1931
2 11/08/2005 /1924
> foo$RECENTd = as.Date(foo$RECENT, format="%m/%d/%Y")
> foo$YEARn = as.numeric(substr(foo$YEAR,2,999))
> foo$AGE = as.numeric(format(foo$RECENTd,"%Y")) - foo$YEARn
> foo
RECENT YEAR RECENTd YEARn AGE
1 09/08/2005 /1931 2005-09-08 1931 74
2 11/08/2005 /1924 2005-11-08 1924 81
Note I've assumed you have that slash in your year column.
Also, tip for when asking questions about dates is to include a day that is past the twelfth so we know if you are a month/day/year person or a day/month/year person.
I think this might be a bit more intuitive and requires no formatting or stripping:
as.numeric(as.Date("2002-02-02") - as.Date("1924-08-03")) / 365
gives output:
77.55342
Then you can use floor(), round(), or ceiling() to round to a whole number.
Really solid way that also supports vectors using the lubridate package:
age <- function(date.birth, date.ref = Sys.Date()) {
if (length(date.birth) > 1 & length(date.ref) == 1) {
date.ref <- rep(date.ref, length(date.birth))
}
date.birth.monthdays <- paste0(month(date.birth), day(date.birth)) %>% as.integer()
date.ref.monthdays <- paste0(month(date.ref), day(date.ref)) %>% as.integer()
age.calc <- 0
for (i in 1:length(date.birth)) {
if (date.birth.monthdays[i] <= date.ref.monthdays[i]) {
# didn't had birthday
age.calc[i] <- year(date.ref[i]) - year(date.birth[i])
} else {
age.calc[i] <- year(date.ref[i]) - year(date.birth[i]) - 1
}
}
age.calc
}
This also accounts for leap years. I just check if someone has had a birthday already.