Converting character string to numeric value in user defined function R - r

I am trying to create a function that will take an input of character strings "mm/did/yyyy" and return an output vector in numeric form the month, day and year. I essentially want to combine this new function with the weekday() function created below to ultimately return the day of the week the input character string corresponds with.
weekday<-function(q,r,s)
{ (if(q>= 3)
m<-(q-2)
else
m<-(q+10))
k<-r
c<-floor(s/100)
(if(q>=3)
y<-s%%100
else
y<-(s%%100)-1)
f<-(floor((2.6*m)-0.2)+k+y+floor(y/4)+floor(c/4)-(2*c))%%7
if(f==0){return("Sunday")}
else
if(f==1){return("Monday")}
else
if(f==2){return("Tuesday")}
else
if(f==3){return("Wednesday")}
else
if(f==4){return("Thursday")}
else
if(f==5){return("Friday")}
else
if(f==6){return("Saturday")}}
I tried using something along the lines of type.convert but this isn't producing the desired output. Any help would be great thanks!
dateconvert<-function("q/r/s")
{
type.convert(dateconvert(), na.strings = )
weekday(convertedanswer)
Return (weekday)
}

Have you tried lubridate package?
input <- "12/30/2017"
# change into as.date format
inputdate <- strptime(input, "%m/%d/%Y")
library("lubridate")
day(inputdate)
# [1] 30
month(inputdate)
# [1] 12
year(inputdate)
# [1] 2017
It seems like a roundabout way to get to the day of week though. You should try using wday() that comes with lubridate package.
wday(inputdate, label=T)
# [1] Sat
# Levels: Sun < Mon < Tues < Wed < Thurs < Fri < Sat.
# as a Ordered factor (Sunday is first)
wday(inputdate)
# [1] 7
# wday returns the day of the week as a decimal number (01-07, Sunday is 1) or an .

Alternatively without package:
date = "06/10/2017"
POSIXdate = as.POSIXlt(date, format = "%d/%m/%Y")
strftime(POSIXdate, "%A")
#Friday
# Or if you like one-liner
strftime(as.POSIXlt("dd/mm/yyyy", format= "%d/%m/%Y"), "%A")

The lubridate package is great for this! You can use lubridate::mdy() to convert a date in the "mm/dd/yyyy" formate you mentioned, and then lubridate::week() to get the week.
lubridate::week(lubridate::mdy("10/05/2017"))
#> 5
If you want the day itself, rather than the numeric output, you can use
lubridate::wday(lubridate::mdy("10/05/2017"),
label = TRUE)
#> [1] Thurs

Related

Converting non-standard date format strings ("April-20") to date objects R

I have a vector of date strings in the form month_name-2_digit_year i.e.
a = rbind("April-21", "March-21", "February-21", "January-21")
I'm trying to convert that vector into a vector of date objects. I'm aware this question is very similar to this: Convert non-standard date format to date in R posted some years ago, but unfortunately, it has not answered my question.
I have tried the following as.Date() calls to do this, but it just returns a vector of NA. I.e.
b = as.Date(a, format = "%B-%y")
b = as.Date(a, format = "%B%y")
b = as.Date(a, "%B-%y")
b = as.Date(a, "%B%y")
I'm also attempted to do it using the convertToDate function from the openxlsx package:
b = convertToDate(a, format = "%B-%y")
I have also tried all the above but using a single character string rather than a vector, but that produced the same issue.
I'm a little lost as to why this isn't working, as this format has worked in reverse earlier in my script (that is, I had a date object already in dd-mm-yyyy format and converted it to month_name-yy using %B-%y). Is there another way to go from string to date when the string is a non-standard (anything other than dd-mm-yyy or mm-dd-yy if you're in the US) date format?
For the record my R locales are all UK and english.
Thanks in advance.
A Date must have all three of day, month and year. Convert to yearmon class which requires only month and year and then to Date as in (1) and (2) below or add the day as in (3).
(1) and (3) give first of month and (2) gives the end of the month.
(3) uses only functions from base R.
Also consider not converting to Date at all but just use yearmon objects instead since they directly represent a year and month which is what the input represents.
library(zoo)
# test input
a <- c("April-21", "March-21", "February-21", "January-21")
# 1
as.Date(as.yearmon(a, "%B-%y"))
## [1] "2021-04-01" "2021-03-01" "2021-02-01" "2021-01-01"
# 2
as.Date(as.yearmon(a, "%B-%y"), frac = 1)
## [1] "2021-04-30" "2021-03-31" "2021-02-28" "2021-01-31"
# 3
as.Date(paste(1, a), "%d %B-%y")
## [1] "2021-04-01" "2021-03-01" "2021-02-01" "2021-01-01"
In addition to zoo, which #G. Grothendieck mentioned, you can also use clock or lubridate.
clock supports a variable precision calendar type called year_month_day. In this case you'd want "month" precision, then you can set the day to whatever you'd like and convert back to Date.
library(clock)
x <- c("April-21", "March-21", "February-21", "January-21")
ymd <- year_month_day_parse(x, format = "%B-%y", precision = "month")
ymd
#> <year_month_day<month>[4]>
#> [1] "2021-04" "2021-03" "2021-02" "2021-01"
# First of month
as.Date(set_day(ymd, 1))
#> [1] "2021-04-01" "2021-03-01" "2021-02-01" "2021-01-01"
# End of month
as.Date(set_day(ymd, "last"))
#> [1] "2021-04-30" "2021-03-31" "2021-02-28" "2021-01-31"
The simplest solution may be to use lubridate::my(), which parses strings in the order of "month then year". That assumes that you want the first day of the month, which may or may not be correct for you.
library(lubridate)
x <- c("April-21", "March-21", "February-21", "January-21")
# Assumes first of month
my(x)
#> [1] "2021-04-01" "2021-03-01" "2021-02-01" "2021-01-01"

how to arrange false(no exist) character column to date in r?(similar Converting character column to Date in r)

ymd("2011-11-31")
All formats failed to parse. No formats found.[1] NA
2011-11 have 30 days not 31 so ymd get failed state.
My data have some false date in date column like this and I want to learn elegant way to handle.
is there any package or function that data to turn like this "2011-12-01"?
Not that I know of, but you could define your own function to handle it.
Here I take the year-month portion of the date and then add the number of days on and let it wrap into the next month (or even year) if required.
# two invalid, one valid date
x <- c("2011-11-31", "2000-04-31", "2010-01-10", "2011-12-32")
parse_bad_dates <- function(x) {
as.Date(paste(substr(x, 1, 7), "1"), format="%Y-%m %d") +
as.numeric(substr(x, 9, 10)) - 1
}
parse_bad_dates(x)
#[1] "2011-12-01" "2000-05-01" "2010-01-10" "2012-01-01"
Similar answer here but works with rolling months and years too
library(lubridate)
d <- c("2011-11-31",'2011-13-04','2011-12-32')
parse_false_date <- function(d) {
x <- strcapture("(\\d{4})-(\\d{2})-(\\d{2})", d,
data.frame(y=integer(),m=integer(),d=integer()))
make_date(x$y)+months(x$m-1)+days(x$d-1)
}
parse_false_date(d)
#> [1] "2011-12-01" "2012-01-04" "2012-01-01"

Convert a string into dates using R

I have a column of dates written as monthyear in the format:
11960 - this would be Jan 1960
121960 - this would be Dec 1960
I would like to convert this column into a day-month-year format assuming the first of the month as each date.
I have tried (using one number as an example as opposed to dt$dob)
x <- sprintf("%08d%", 11960)
and then x <- as.date(x, format = "%d%m%Y)
but this gives me NAs as I assume it doesn't like the 00 at the start
So I tried pasting 01 to each value but this pastes it to the end (R noob here). I was thinking maybe posting 01 to the start and then using the sprintf function may work still:
paste 01 to start of 11960 = 011960
sprintf("%08d%", 011960) to maybe give 0101960?
Then use as.Date to convert?
Many thanks for your help
i used paste0() instead of sprintf, but it seems it works.
> x<-paste0("010",11960)
> x
[1] "01011960"
> as.Date(x , format = "%d%m%Y" )
[1] "1960-01-01"
EDIT for 2 digit months i use ifelse() and nchar()
y<-c(11960,11970,11980, 111960,111970,111980)
x<-ifelse(nchar(y) == 5,paste0("010",y),paste0("01",y))
> x
[1] "01011960" "01011970" "01011980" "01111960" "01111970" "01111980"
as.Date(x , format = "%d%m%Y" )
[1] "1960-01-01" "1970-01-01" "1980-01-01" "1960-11-01" "1970-11-01" "1980-11-01"

Why subtracting months from a lubridate date gives inconsistent results? [duplicate]

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.

How to parse an invalid date with lubridate?

I need to parse dates and have a cases like "31/02/2018":
library(lubridate)
> dmy("31/02/2018", quiet = T)
[1] NA
This makes sense as the 31st of Feb does not exist. Is there a way to parse the string "31/02/2018" to e.g. 2018-02-28 ? So not to get an NA, but an actual date?
Thanks.
We can write a function assuming you would only have dates which could be higher than the actual date and would have the same format always.
library(lubridate)
get_correct_date <- function(example_date) {
#Split vector on "/" and get 3 components (date, month, year)
vecs <- as.numeric(strsplit(example_date, "\\/")[[1]])
#Check number of days in that month
last_day_of_month <- days_in_month(vecs[2])
#If the input date is higher than actual number of days in that month
#replace it with last day of that month
if (vecs[1] > last_day_of_month)
vecs[1] <- last_day_of_month
#Paste the date components together to get new modified date
dmy(paste0(vecs, collapse = "/"))
}
get_correct_date("31/02/2018")
#[1] "2018-02-28"
get_correct_date("31/04/2018")
#[1] "2018-04-30"
get_correct_date("31/05/2018")
#[1] "2018-05-31"
With small modification you can adjust the dates if they have different format or even if some dates are smaller than the first date.

Resources