The day in which a week starts differs depending on context. For countries like the UK, the first day of the week is the first working day, which is Monday. This definition conforms with the ISO 8601 standard definition for the beginning of a week, but there are examples of situations where the first day of the week is different:
This package provides tools to convert dates to weeks and back where a week can start on any day. You can use this package for any of the following:
You can convert dates to weeks starting on any day by using
date2week()
with the week_start
argument. This
argument can be a number from 1 to 7 representing the ISO 8601 day of
the week OR it can be a string representing the day of the week in
either an English locale or the locale defined on your computer. The
default of this argument is the value of get_week_start()
,
which is a thin wrapper around
options("aweek.week_start", 1L)
. Unless you have
specified a default aweek.week_start
option with
set_week_start()
, this will always be set to 1
(Monday).
It is highly recommended that you set the default
aweek.week_start
either in the beginning of your Rscript, Rmarkdown document, or in your .Rprofile.
library("aweek")
set_week_start("Sunday") # setting the default week_start to Sunday
set.seed(2019-03-03)
dat <- as.Date("2019-03-03") + sample(-6:7, 10, replace = TRUE)
dat
## [1] "2019-03-10" "2019-03-05" "2019-02-27" "2019-03-01" "2019-02-28"
## [6] "2019-02-25" "2019-03-03" "2019-02-25" "2019-03-03" "2019-03-02"
## <aweek start: Sunday>
## [1] "2019-W11-1" "2019-W10-3" "2019-W09-4" "2019-W09-6" "2019-W09-5"
## [6] "2019-W09-2" "2019-W10-1" "2019-W09-2" "2019-W10-1" "2019-W09-7"
If you need a different day on the fly, you can supply an integer or
character day to the week_start
argument.
## <aweek start: Monday>
## [1] "2019-W10-7" "2019-W10-2" "2019-W09-3" "2019-W09-5" "2019-W09-4"
## [6] "2019-W09-1" "2019-W09-7" "2019-W09-1" "2019-W09-7" "2019-W09-6"
## <aweek start: Monday>
## [1] "2019-W10-7" "2019-W10-2" "2019-W09-3" "2019-W09-5" "2019-W09-4"
## [6] "2019-W09-1" "2019-W09-7" "2019-W09-1" "2019-W09-7" "2019-W09-6"
If you want to save two extra keystrokes, you can also use the
as.aweek()
method for dates, which wraps
date2week()
:
## <aweek start: Monday>
## [1] "2019-W10-7" "2019-W10-2" "2019-W09-3" "2019-W09-5" "2019-W09-4"
## [6] "2019-W09-1" "2019-W09-7" "2019-W09-1" "2019-W09-7" "2019-W09-6"
What you get back is an aweek
class object. It can be
converted back to a date with either as.Date()
or
week2date()
:
## [1] "2019-03-10" "2019-03-05" "2019-02-27" "2019-03-01" "2019-02-28"
## [6] "2019-02-25" "2019-03-03" "2019-02-25" "2019-03-03" "2019-03-02"
## [1] "2019-03-10" "2019-03-05" "2019-02-27" "2019-03-01" "2019-02-28"
## [6] "2019-02-25" "2019-03-03" "2019-02-25" "2019-03-03" "2019-03-02"
The calculation of weeks from dates requires knowledge of the current day of the week and the number of days past 1 January.
Week numbers are calculated in three steps:
d = 1L + ((i + (7L - s)) %% 7L)
.m = date + (4 - d)
.w = 1L + ((m - yyyy-01-01) %/% 7)
For example, here’s how to calculate the week for Tuesday, 6 December 2016, assuming the week start is a Sunday:
the_date <- as.Date("2016-12-06")
jan_1 <- as.Date("2016-01-01")
i <- as.POSIXlt(the_date)$wday # 2, the ISO date for Tuesday
s <- 7L # week_start for sunday
# 1. Find the day of the week
print(d <- 1L + ((i + (7L - s)) %% 7L))
## [1] 3
## [1] "2016-12-07"
## [1] 49
## [1] "2016-W49-3"
For the weeks around 1 January, the year is determined by the week number. If the month is January, but the week number is 52 or 53, then the year for the week (YYYY) is the calendar year (yyyy) minus 1. However, if the month is December, but the week number is 1, then the year for the week (YYYY) is the calendar year (yyyy) plus 1.
aweek
classThe result you see above is an object of class “aweek”. The
aweek
class is a character that contains the
week_start
attribute. This attribute allows it to be easily
converted back to a date without the user needing to enter the start day
every time. You can convert a character that matches the
YYYY-Www-d
pattern to an aweek
class object
with as.aweek()
:
## <aweek start: Sunday>
## [1] "2019-W10-1"
Under the hood, it checks the validity of the week string and then add the attribute and class:
x <- "2019-W10-1" attr(x, "week_start") <- 7 # Sunday class(x) <- "aweek"
If you need to remove the class, you can just use
as.character()
:
## [1] "2019-W10-1"
The date2week()
function only checks that dates are in
ISO 8601 (yyyy-mm-dd) format before converting to weeks, and
otherwise assumes that the dates are accurate so it’s strongly
recommended to make sure your dates are in either Date
or
POISXt
format and accurate before converting to weeks. The
lubridate can
be used for this purpose.
Use set_week_start()
at the beginning of all your
scripts to explicitly define the day on which your weeks start. This can
be overridden if need be in specific parts of your scripts. Otherwise,
the default will be dependent on the value of
getOption("aweek.week_start", 1L)
.
Because the week_start
arguments default to
get_week_start()
, it’s recommended to specify
week_start
in date2week()
and
week2date()
if you don’t have an aweek
object.
Before you combine aweek objects, confirm that they are actually
aweek objects with inherits(myObject, "aweek")
.
There are times where you would want to aggregate your days into
weeks, you can do this by specifying floor_day = TRUE
in
date2week()
. For example, here we can show the individual
weeks:
## <aweek start: Saturday>
## [1] "2019-W11" "2019-W10" "2019-W09" "2019-W09" "2019-W09" "2019-W09"
## [7] "2019-W10" "2019-W09" "2019-W10" "2019-W10"
## wf
## 2019-W09 2019-W10 2019-W11
## 5 4 1
If you convert this to date, then all the dates will represent the beginning of the week:
## [1] "2019-03-09" "2019-03-02" "2019-02-23" "2019-02-23" "2019-02-23"
## [6] "2019-02-23" "2019-03-02" "2019-02-23" "2019-03-02" "2019-03-02"
## [1] "Saturday" "Saturday" "Saturday" "Saturday" "Saturday" "Saturday"
## [7] "Saturday" "Saturday" "Saturday" "Saturday"
If you want to aggregate your aweek
objects after you
created them, you can always use the trunc()
function:
## <aweek start: Sunday>
## [1] "2019-W11-1" "2019-W10-3" "2019-W09-4" "2019-W09-6" "2019-W09-5"
## [6] "2019-W09-2" "2019-W10-1" "2019-W09-2" "2019-W10-1" "2019-W09-7"
## <aweek start: Sunday>
## [1] "2019-W11" "2019-W10" "2019-W09" "2019-W09" "2019-W09" "2019-W09"
## [7] "2019-W10" "2019-W09" "2019-W10" "2019-W09"
Weeks can be represented as factors, which is useful for tabulations
across weeks. You can use factor = TRUE
in
date2week()
and it will automatically fill in any missing
weeks.
## [1] "2019-03-10" "2019-03-25"
## <aweek start: Monday>
## [1] 2019-W10 2019-W13
## Levels: 2019-W10 2019-W11 2019-W12 2019-W13
If you already have an aweek object and want to convert it to a
factor, you can use factor_aweek()
:
## <aweek start: Sunday>
## [1] 2019-W11 2019-W10 2019-W09 2019-W09 2019-W09 2019-W09 2019-W10 2019-W09
## [9] 2019-W10 2019-W09
## Levels: 2019-W09 2019-W10 2019-W11
Be careful when combining factors with other dates or aweek objects as they will force the other objects to be truncated as well.
You can use change_week_start()
to convert between
different week definitions if you have an aweek
object:
## <aweek start: Sunday>
## [1] "2019-W11-1" "2019-W10-3" "2019-W09-4" "2019-W09-6" "2019-W09-5"
## [6] "2019-W09-2" "2019-W10-1" "2019-W09-2" "2019-W10-1" "2019-W09-7"
## <aweek start: Wednesday>
## [1] "2019-W10-5" "2019-W09-7" "2019-W09-1" "2019-W09-3" "2019-W09-2"
## [6] "2019-W08-6" "2019-W09-5" "2019-W08-6" "2019-W09-5" "2019-W09-4"
## [1] TRUE
# create a table with all days in the week
d <- as.Date("2019-03-03") + 0:6
res <- lapply(weekdays(d), function(i) date2week(d, week_start = i))
names(res) <- weekdays(d)
data.frame(res)
## Sunday Monday Tuesday Wednesday Thursday Friday Saturday
## 1 2019-W10-1 2019-W09-7 2019-W09-6 2019-W09-5 2019-W09-4 2019-W09-3 2019-W10-2
## 2 2019-W10-2 2019-W10-1 2019-W09-7 2019-W09-6 2019-W09-5 2019-W09-4 2019-W10-3
## 3 2019-W10-3 2019-W10-2 2019-W10-1 2019-W09-7 2019-W09-6 2019-W09-5 2019-W10-4
## 4 2019-W10-4 2019-W10-3 2019-W10-2 2019-W10-1 2019-W09-7 2019-W09-6 2019-W10-5
## 5 2019-W10-5 2019-W10-4 2019-W10-3 2019-W10-2 2019-W10-1 2019-W09-7 2019-W10-6
## 6 2019-W10-6 2019-W10-5 2019-W10-4 2019-W10-3 2019-W10-2 2019-W10-1 2019-W10-7
## 7 2019-W10-7 2019-W10-6 2019-W10-5 2019-W10-4 2019-W10-3 2019-W10-2 2019-W11-1
All of these columns contain the same dates:
## Sunday Monday Tuesday Wednesday Thursday Friday Saturday
## 1 2019-03-03 2019-03-03 2019-03-03 2019-03-03 2019-03-03 2019-03-03 2019-03-03
## 2 2019-03-04 2019-03-04 2019-03-04 2019-03-04 2019-03-04 2019-03-04 2019-03-04
## 3 2019-03-05 2019-03-05 2019-03-05 2019-03-05 2019-03-05 2019-03-05 2019-03-05
## 4 2019-03-06 2019-03-06 2019-03-06 2019-03-06 2019-03-06 2019-03-06 2019-03-06
## 5 2019-03-07 2019-03-07 2019-03-07 2019-03-07 2019-03-07 2019-03-07 2019-03-07
## 6 2019-03-08 2019-03-08 2019-03-08 2019-03-08 2019-03-08 2019-03-08 2019-03-08
## 7 2019-03-09 2019-03-09 2019-03-09 2019-03-09 2019-03-09 2019-03-09 2019-03-09
aweek
objectsYou can add dates, aweek objects, or characters to aweek objects:
## <aweek start: Sunday>
## [1] "2010-W10-1" "2019-W10-1" "2019-W10-2" "2019-W10-3" "2019-W10-4"
## [6] "2019-W10-5" "2019-W10-6" "2019-W10-7" "2010-W12-1" "2019-W12-1"
However, you can not combine aweek objects with different
week_start
attributes.
## Error in c.aweek(res$Sunday[1], res$Wednesday[2], res$Friday[3]): All aweek objects must have the same week_start attribute. Please use change_week_start() to adjust the week_start attribute if you wish to combine these objects.
If you want to combine different aweek objects, you must first change
their week_start
attribute:
wed <- change_week_start(res$Wednesday, get_week_start())
fri <- change_week_start(res$Friday, get_week_start())
c(res$Sunday[1], wed[2], fri[3])
## <aweek start: Sunday>
## [1] "2019-W10-1" "2019-W10-2" "2019-W10-3"
Dates combined with aweek objects will will be automatically converted.
## <aweek start: Monday>
## [1] "2019-W09-7" "2019-W10-1" "2019-W10-2" "2019-W10-3" "2019-W10-4"
## [6] "2019-W10-5" "2019-W10-6" "2019-W14-3"
You can also add character representation of weeks, but be aware that
it is assumed that these have the same week_start
as the first object.
## <aweek start: Saturday>
## [1] "2019-W10-2" "2019-W10-3" "2019-W10-4" "2019-W10-5" "2019-W10-6"
## [6] "2019-W10-7" "2019-W11-1" "2019-W14-3"
## <aweek start: Monday>
## [1] "2019-W09-7" "2019-W10-1" "2019-W10-2" "2019-W10-3" "2019-W10-4"
## [6] "2019-W10-5" "2019-W10-6" "2019-W14-3"
These will translate into different dates
## [1] "2019-03-09" "2019-04-01"
## [1] "2019-03-09" "2019-04-03"
You may encounter a situation where you have a merged data frame with
weeks starting on different days. This section will cover two situations
where you may have weeks as numbers and weeks as ISO-week strings. First
we will create our demonstration data that represents the same week with
different week_start
attributes.
# create a table with all days in the week
d <- as.Date("2019-03-03") + 0:6
res <- lapply(weekdays(d), function(i) date2week(d, week_start = i))
resn <- lapply(weekdays(d), function(i) date2week(d, week_start = i, numeric = TRUE))
datf <- data.frame(wday = rep(weekdays(d), each = 7),
week = unlist(res), # note: unlist converts to character
week_number = unlist(resn),
year = 2019,
stringsAsFactors = FALSE)
datf$day <- substring(datf$week, 10, 11)
head(datf, 10)
## wday week week_number year day
## 1 Sunday 2019-W10-1 10 2019 1
## 2 Sunday 2019-W10-2 10 2019 2
## 3 Sunday 2019-W10-3 10 2019 3
## 4 Sunday 2019-W10-4 10 2019 4
## 5 Sunday 2019-W10-5 10 2019 5
## 6 Sunday 2019-W10-6 10 2019 6
## 7 Sunday 2019-W10-7 10 2019 7
## 8 Monday 2019-W09-7 9 2019 7
## 9 Monday 2019-W10-1 10 2019 1
## 10 Monday 2019-W10-2 10 2019 2
To get the weeks (numbers or strings) to aweek objects, you should
use the start
argument to specify which day of the week
they start on. Internally, this translates the week to their
corresponding dates and then to aweek objects with the same
week_start
attribute (which defaults to
get_week_start()
).
Most commonly, you will have weeks across data sets represented by
numbers. These can be converted to aweek objects using the
get_aweek()
function and to dates using the
get_date()
function:
datf$aweek <- with(datf, get_aweek(week = week_number, year = year, day = day, start = wday))
datf$date <- with(datf, get_date(week = week_number, year = year, day = day, start = wday))
head(datf, 10)
## wday week week_number year day aweek date
## 1 Sunday 2019-W10-1 10 2019 1 2019-W10-1 2019-03-03
## 2 Sunday 2019-W10-2 10 2019 2 2019-W10-2 2019-03-04
## 3 Sunday 2019-W10-3 10 2019 3 2019-W10-3 2019-03-05
## 4 Sunday 2019-W10-4 10 2019 4 2019-W10-4 2019-03-06
## 5 Sunday 2019-W10-5 10 2019 5 2019-W10-5 2019-03-07
## 6 Sunday 2019-W10-6 10 2019 6 2019-W10-6 2019-03-08
## 7 Sunday 2019-W10-7 10 2019 7 2019-W10-7 2019-03-09
## 8 Monday 2019-W09-7 9 2019 7 2019-W10-1 2019-03-03
## 9 Monday 2019-W10-1 10 2019 1 2019-W10-2 2019-03-04
## 10 Monday 2019-W10-2 10 2019 2 2019-W10-3 2019-03-05
These functions are also useful for constructing weeks or dates on the fly if you only have a week and a year:
## <aweek start: Sunday>
## [1] "2019-W11-1"
## [1] "2019-03-10"
If you have weeks formatted as ISO-week strings, then you can convert
to aweek objects using as.aweek()
:
## wday week week_number year day aweek
## 1 Sunday 2019-W10-1 10 2019 1 2019-W10-1
## 2 Sunday 2019-W10-2 10 2019 2 2019-W10-2
## 3 Sunday 2019-W10-3 10 2019 3 2019-W10-3
## 4 Sunday 2019-W10-4 10 2019 4 2019-W10-4
## 5 Sunday 2019-W10-5 10 2019 5 2019-W10-5
## 6 Sunday 2019-W10-6 10 2019 6 2019-W10-6
## 7 Sunday 2019-W10-7 10 2019 7 2019-W10-7
## 8 Monday 2019-W09-7 9 2019 7 2019-W10-1
## 9 Monday 2019-W10-1 10 2019 1 2019-W10-2
## 10 Monday 2019-W10-2 10 2019 2 2019-W10-3
## 'data.frame': 49 obs. of 6 variables:
## $ wday : chr "Sunday" "Sunday" "Sunday" "Sunday" ...
## $ week : chr "2019-W10-1" "2019-W10-2" "2019-W10-3" "2019-W10-4" ...
## $ week_number: num 10 10 10 10 10 10 10 9 10 10 ...
## $ year : num 2019 2019 2019 2019 2019 ...
## $ day : chr "1" "2" "3" "4" ...
## $ aweek : 'aweek' chr "2019-W10-1" "2019-W10-2" "2019-W10-3" "2019-W10-4" ...
## ..- attr(*, "week_start")= int 7
We can tabulate them to see how they transformed:
## after
## before 2019-W10-1 2019-W10-2 2019-W10-3 2019-W10-4 2019-W10-5 2019-W10-6 2019-W10-7
## 2019-W09-3 1 . . . . . .
## 2019-W09-4 1 1 . . . . .
## 2019-W09-5 1 1 1 . . . .
## 2019-W09-6 1 1 1 1 . . .
## 2019-W09-7 1 1 1 1 1 . .
## 2019-W10-1 1 1 1 1 1 1 .
## 2019-W10-2 1 1 1 1 1 1 1
## 2019-W10-3 . 1 1 1 1 1 1
## 2019-W10-4 . . 1 1 1 1 1
## 2019-W10-5 . . . 1 1 1 1
## 2019-W10-6 . . . . 1 1 1
## 2019-W10-7 . . . . . 1 1
## 2019-W11-1 . . . . . . 1
If you receive data that contains week definitions, you can convert it back to a date if you know where the week starts.
## [1] "2019-03-03"
## [1] "2019-03-04"
If you have an aweek
object, however, it will use the
week_start
attribute defined in the object, even if the
default week_start
attribute is different:
set_week_start("Monday") # Set the default week_start to ISO week
get_week_start(w) # show the default week_start for w
## [1] 7
## [1] "2019-03-10" "2019-03-05" "2019-02-27" "2019-03-01" "2019-02-28"
## [6] "2019-02-25" "2019-03-03" "2019-02-25" "2019-03-03" "2019-03-02"
## [1] TRUE
## [1] FALSE
You can also use as.Date()
and as.POISXlt()
if you have an aweek
object:
## [1] "2019-03-10" "2019-03-05" "2019-02-27" "2019-03-01" "2019-02-28"
## [6] "2019-02-25" "2019-03-03" "2019-02-25" "2019-03-03" "2019-03-02"
## [1] "2019-03-10 UTC" "2019-03-05 UTC" "2019-02-27 UTC" "2019-03-01 UTC"
## [5] "2019-02-28 UTC" "2019-02-25 UTC" "2019-03-03 UTC" "2019-02-25 UTC"
## [9] "2019-03-03 UTC" "2019-03-02 UTC"