目录
1.日期和日期时间类型
R 中日期可以保存为 Date 类型,一般用整数保存,数值为从 1970-1-1 经过的天数。
R 中用一种叫做 POSIXct 和 POSIXlt 的特殊数据类型保存日期和时间,可以仅包含日期部分,也可以同时有日期和时间。技术上,POSIXct 把日期时间保存为从 1970 年 1 月 1 日零时到该日期时间的时间间隔秒数,所以数据框中需要保存日期时用 POSIXct 比较合适,需要显示时再转换成字符串形式;POSIXlt 把日期时间保存为一个包含年、月、日、星期、时、分、秒等成分的列表,所以求这些成分可以从 POSIXlt 格式日期的列表变量中获得。日期时间会涉及到所在时区、夏时制等问题,比较复杂。
基础的 R 用 as.Date()、as.POSIXct() 等函数生成日期型和日期时间型,R 扩展包 lubridate 提供了多个方便函数,可以更容易地生成、转换、管理日期型和日期时间型数据。
载入扩展包的方式:
载入扩展包的方式:
library(lubridate)
如果下面内容:
证明你没有安装此扩展包
此时需要安装扩展包,安装方式如下:
点击【程序包】,再点击【安装程序包】
然后,找到 China(Beijing 2)
然后找到需要安装的扩展包 lubridate
然后,点击【确定】即可,安装完成会出现如下内容
这次我们再此载入扩展包 lubridate
2.从字符串生成日期数据
函数 lubridate::today() 返回当前日期:
today()
返回:
函数 lubridate::now() 返回当前日期时间:
now()
返回:
在结果中,我们看到了 CST 字样,CST 是时区,这里使用了操作系统提供的当前时区。CST 不是一个含义清晰的时区,在不同国家对应不同的时区,在中国代表中国标准时间(北京时间)
用 lubridate::ymd(), lubridate::mdy(), lubridate::dmy() 将字符型向量转换为日期型向量,但是他们的输入方式不相同,y 表示年,m 表示月,d 表示日,如: 年 - 月 - 日:
ymd(c("2022-1-24","2022-01-24"))
返回:
月 - 日 - 年:
mdy(c("1-24-2022", "01-24-2022"))
返回:
日 - 月 - 年:
dmy(c("24-1-2022","24-01-2022"))
返回:
如果在年号只有两位数字时,默认对应到 1969-2068 范围。
lubridate::make_date(year, month, day) 可以从三个数值构成日期向量。如
make_date(2022, 1, 24)
返回:
lubridate 包的 ymd、mdy、dmy 等函数添加 hms、hm、h 等后缀,可以用于将字符串转换成日期时间。如
ymd_hms("2022-1-24 19:26:35")
返回:
结果显示中 UTC 是时区,UTC 是协调世界时 (Universal Time Coordinated) 英文缩写,是由国际无线电咨询委员会规定和推荐, 并由国际时间局 (BIH) 负责保持的以秒为基础的时间标度。UTC 相当于本初子午线 (即经度 0 度) 上的平均太阳时,过去曾用格林威治平均时 (GMT) 来表示. 北京时间比 UTC 时间早 8 小时,以 1999 年 1 月 1 日 0000UTC 为例,UTC 时间是零点,北京时间为 1999 年 1 月 1 日早上 8 点整。
在 Date()、as.DateTime()、ymd() 等函数中,可以用 tz= 指定时区,比如北京时间可指定为 tz="Etc/GMT+8" 或 tz="Asia/Shanghai"。
lubridate::make_datetime(year, month, day, hour, min, sec) 可以从最多六个数值组成日期时间,其中时分秒缺省值都是 0。如
make_datetime(2022, 1, 24, 19, 32, 36)
返回:
用 lubridate::as_date() 可以将日期时间型转换为日期型,如
as_date(as.POSIXct("2022-01-24 19:32:36"))
返回:
用 lubridate::as_datetime() 可以将日期型数据转换为日期时间型,如
as_datetime(as.Date("2022-01-24"))
返回:
3.日期显示格式
用 as.character() 函数把日期型数据转换为字符型, 如
x <- as.POSIXct(c('2022-1-24', '2022-01-24')) as.character(x)
返回:
在 as.character() 中可以用 format 选项指定显示格式,格式中 “%Y” 代表四位的公元年号,“%m” 代表两位的月份数字,“%d” 代表两位的月内日期号
如 %m/%d/%Y 表示 月-日-年
x <- as.POSIXct(c('2022-1-24', '2022-01-24')) as.character(x, format='%m/%d/%Y')
返回:
"15Mar98" 这样的日期在英文环境中比较常见,但是在 R 中的处理比较复杂。在下面的例子中,R 日期被转换成了类似"Mar98" 这样的格式,在 format 选项中用了 “%b” 代表三英文字母月份缩写,但是因为月份缩写依赖于操作系统默认语言环境,需要用 Sys.setlocale() 函数设置语言环境为"C"。示例程序如下
x <- as.POSIXct(c('2022-1-24', '2022-01-24')) old.lctime <- Sys.getlocale('LC_TIME') Sys.setlocale('LC_TIME', 'C')
返回:
as.character(x, format='%b%y')
返回:
Sys.setlocale('LC_TIME', old.lctime)
返回:
包含时间的转换,如
x <- as.POSIXct('2022-1-24 19:56:42') as.character(x)
返回:
as.character(x, format='%H:%M:%S')
返回:
这里 “%H” 代表小时(按 24 小时制),“%M” 代表两位的分钟数字,“%S” 代表两位的秒数。
4.访问日期时间的组成值
lubridate 包的如下函数可以取出日期型或日期时间型数据中的组成部分:
- year() 取出年
- month() 取出月份数值
- mday() 取出日数值
- yday() 取出日期在一年中的序号,元旦为 1
- wday() 取出日期在一个星期内的序号,但是一个星期从星期天开始,星期天为 1, 星期一为 2,星期六为 7。
- hour() 取出小时
- minute() 取出分钟
- second() 取出秒
比如, 2022-1-24 是星期一,则 提取月份
month(as.POSIXct("2022-1-24 19:56:42"))
返回:
提取日
mday(as.POSIXct("2022-1-24 19:56:42"))
返回:
提取日期在一个星期内的序号
wday(as.POSIXct("2022-1-24 19:56:42"))
lubridate 的这些成分函数还允许被赋值,结果就修改了相应元素的值,如 将年份由 2022 赋值为 2000
x <- as.POSIXct("2022-1-24 20:08:15") year(x) <- 2000 month(x) <- 1 mday(x) <- 1 x
返回:
update() 可以对一个日期或一个日期型向量统一修改其组成部分的值,如
x <- as.POSIXct("2022-1-24 20:08:15") y <- update(x, year=2000) y
返回:
update() 函数中可以用 year, month, mday, hour, minute, second 等参数修改日期的组成部分。
5.日期舍入计算
lubridate 包提供了 floor_date(), round_date(), ceiling_date() 等函数,对日期可以用 unit= 指定一个时间单位进行舍入。时间单位为字符串,如 seconds, 5 seconds, minutes, 2 minutes, hours, days, weeks, months, years 等。
比如,以 10 minutes 为单位,floor_date() 将时间向前归一化到 10 分钟的整数倍:
x <- ymd_hms("2022-1-24 20:08:15") floor_date(x, unit="10 minutes")
返回:
ceiling_date() 将时间向后归一化到 10 分钟的整数倍:
x <- ymd_hms("2022-1-24 20:08:15") ceiling_date(x, unit="10 minutes")
返回:
round_date() 将时间归一化到最近的 10 分钟的整数倍,时间恰好是 5 分钟倍数时按照类似四舍五入的原则向上取整。例如
x <- ymd_hms("2022-1-24 20:08:15") round_date(x, unit="10 minutes")
返回:
如果单位是星期,会涉及到一个星期周期的开始是星期日还是星期一的问题。用参数week_start=7 指定开始是星期日,week_start=1 指定开始是星期一。
6.日期计算
在 lubridate 的支持下日期可以相减,可以进行加法、除法。lubridate 包提供了如下的三种与时间长短有关的数据类型:
- 时间长度 (duration),按整秒计算
- 时间周期 (period),如日、周
- 时间区间 (interval),包括一个开始时间和一个结束时间
6.1 时间长度
R 的 POSIXct 日期时间之间可以相减,如
d1 <- ymd_hms("2022-01-24 0:0:0") d2 <- ymd_hms("2022-01-24 12:0:5") di <- d2 - d1; di
返回:
结果显示与日期之间差别大小有关系,结果是类型是 difftime。
lubridate 包提供了 duration 类型,处理更方便:
as.duration(di)
返回:
lubridate 的 dseconds(), dminutes(), dhours(), ddays(), dweeks(), dyears() 函数可以直接生成时间长度类型的数据,如
dhours(1)
返回:
lubridate 的时间长度类型总是以秒作为单位,可以在时间长度之间相加,也可以对时间长度乘以无量纲数,如
dhours(1) + dseconds(5)
返回:
dhours(1)*10
返回:
可以给一个日期加或者减去一个时间长度,结果严格数按推移的秒计算,如
d2 <- ymd_hms("2022-01-24 12:0:5") d2 - dhours(5)
返回:
d2 <- ymd_hms("2022-01-24 12:0:5") d2 + ddays(10)
返回:
时间的前后推移在涉及到时区和夏时制时有可能出现未预料到的情况。
用 unclass() 函数将时间长度数据的类型转换为普通数值,如:
unclass(dhours(1))
返回:
6.2 时间周期
时间长度的固定单位是秒,但是像月、年这样的单位,因为可能有不同的天数,所以日历中的时间单位往往没有固定的时长。 lubridate 包的 seconds(), minutes(), hours(), days(),weeks(), years() 函数可以生成以日历中正常的周期为单位的时间长度,不需要与秒数相联系,可以用于时间的前后推移。这些时间周期的结果可以相加、乘以无量纲整数:
years(2) + 10*days(1)
返回:
lubridate 的月度周期因为与已有函数名冲突,所以没有提供,需要使用 lubridate::period(num, units="month") 的格式,其中 num 是几个月的数值。
为了按照日历进行日期的前后平移,而不是按照秒数进行日期的前后平移,应该使用这些时间周期。例如,因为 2016 年是闰年,按秒数给 2016-01-01 加一年,得到的并不是 2017-01-01:
ymd("2016-01-01") + dyears(1)
返回:
使用时间周期函数则得到预期结果:
ymd("2016-01-01") + years(1)
返回:
6.3 时间区间
lubridate 提供了%--% 运算符构造一个时间期间。时间区间可以求交集、并集等。
构造如:
d1 <- ymd_hms("2000-01-01 0:0:0") d2 <- ymd_hms("2000-01-02 12:0:5") din <- (d1 %--% d2); din
返回:
对一个时间区间可以用除法计算其时间长度,如
din / ddays(1)
返回:
din / dseconds(1)
返回:
生成时间区间,也可以用 lubridate::interval(start, end) 函数,如
interval(ymd_hms("2000-01-01 0:0:0"), ymd_hms("2000-01-02 12:0:5"))
返回:
可以指定时间长度和开始日期生成时间区间,如
d1 <- ymd("2022-01-24") din <- as.interval(dweeks(1), start=d1); din
返回:
注意这个时间区间表面上涉及到 8 个日期,但是实际长度还是只有 7 天,因为每一天的具体时间都是按零时计算,所以区间末尾的那一天实际不含在内。 用 lubridate::int_start() 和 lubridate::int_end() 函数访问时间区间的端点,如:
int_start(din)
返回:
int_end(din)
返回:
可以用 as.duration() 将一个时间区间转换成时间长度,用 as.period() 将一个时间区间转换为可变时长的时间周期个数。 用 lubridate::int_shift() 平移一个时间区间,如
din2 <- int_shift(din, by=ddays(3)); din2
返回:
用 lubridate::int_overlaps() 判断两个时间区间是否有共同部分,如
int_overlaps(din, din2)
返回:
时间区间允许开始时间比结束时间晚,用 lubridate::int_standardize() 可以将时间区间标准化成开始时间小于等于结束时间。lubridate() 现在没有提供求交集的功能,一个自定义求交集的函数如下:
int_intersect <- function(int1, int2){ n <- length(int1) int1 <- lubridate::int_standardize(int1) int2 <- lubridate::int_standardize(int2) sele <- lubridate::int_overlaps(int1, int2) inter <- rep(lubridate::interval(NA, NA), n) if(any(sele)){ inter[sele] <- lubridate::interval(pmax(lubridate::int_start(int1[sele]), lubridate::int_start(int2[sele])), pmin(lubridate::int_end(int1[sele]), lubridate::int_end(int2[sele]))) } inter }
测试如下:
d1 <- ymd(c("2018-01-15", "2018-01-18", "2018-01-25")) d2 <- ymd(c("2018-01-21", "2018-01-23", "2018-01-30")) din <- interval(d1, d2); din
返回:
接下来,我们用用上面自定义的函数试试:
首先得先导入这个函数
source("D:/桌面/R语言/int_intersect.r")
然后,我们调用一下
int_intersect(rep(din[1], 2), din[2:3])
返回:
此自定义函数还可以进一步改成允许两个自变量长度不等的情形。
7.基本 R 软件的日期功能
7.1 生成日期和日期时间型数据
对 yyyy-mm-dd 或 yyyy/mm/dd 格式的数据,可以直接用 as.Date() 转换为 Date 类型,如:
x <- as.Date("2022-1-24"); x
返回:
as.numeric(x)
返回:
as.Date() 可以将多个日期字符串转换成 Date 类型,如
as.Date(c("2022-1-24", "2022-01-24"))
返回:
对于非标准的格式,在 as.Date() 中可以增加一个 format 选项,其中用%Y 表示四位数字的年,%m 表示月份数字,%d 表示日数字。如
as.Date("1/24/2022", format="%m/%d/%Y")
返回:
用 as.POSIXct() 函数把年月日格式的日期转换为 R 的标准日期,没有时间部分就认为时间在午夜。 年月日中间的分隔符可以用减号也可以用正斜杠,但不能同时有减号又有斜杠。如
as.POSIXct(c('2022-1-24'))
返回:
as.POSIXct(c('2022/01/24'))
返回:
待转换的日期时间字符串,可以是年月日之后隔一个空格以 “时: 分: 秒” 格式 带有时间。如
as.POSIXct('2022-01-24 13:15:45')
返回:
用 as.POSIXct() 可以同时转换多项日期时间,如
as.POSIXct(c('1998-03-16 13:15:45', '2015-11-22 9:45:3'))
返回:
转换后的日期变量有 class 属性,取值为 POSIXct 与 POSIXt, 并带有一个 tzone(时区)属性。
x <- as.POSIXct(c('1998-03-16 13:15:45', '2015-11-22 9:45:3')) attributes(x)
返回:
在 as.POSIXct() 函数中用 format 参数指定一个日期格式。如
as.POSIXct('1/24/22', format='%m/%d/%y')
返回:
如果日期仅有年和月,必须添加日(添加 01 为日即可)才能读入。比如用’1991-12’ 表示 1991 年 12 月,则如下程序将其读入为’1991-12-01’:
as.POSIXct(paste('1991-12', '-01', sep=''), format='%Y-%m-%d')
返回:
又如
old.lctime <- Sys.getlocale('LC_TIME') Sys.setlocale('LC_TIME', 'C')
返回:
as.POSIXct(paste('01', 'DEC91', sep=''), format='%d%b%y')
返回:
Sys.setlocale('LC_TIME', old.lctime)
返回:
把'DEC91' 转换成了’1991-12-01’。
如果明确地知道时区,在 as.POSIXct() 和 as.POSIXlt() 中可以加选项 tz= 字符串。选项 tz 的缺省值为空字符串,这一般对应于当前操作系统的默认时区。但是,有些操作系统和 R 版本不能使用默认值,这时可以为 tz 指定时区,比如北京时间可指定为 tz='Etc/GMT+8'。如
as.POSIXct('1949-10-01', tz='Etc/GMT+8')
返回:
7.2 取出日期时间的组成值
把一个 R 日期时间值用 as.POSIXlt() 转换为 POSIXlt 类型,就可以用列表元素方法取出其组成的年、月、日、时、分、秒等数值。如
x <- as.POSIXct('1998-03-16 13:15:45') y <- as.POSIXlt(x) cat(1900+y$year, y$mon+1, y$mday, y$hour, y$min, y$sec, '\n')
返回:
注意 year 要加 1900,mon 要加 1。另外,列表元素 wday 取值 1-6 时表示星期一到星期六,取值 0 时表示星期天。 对多个日期,as.POSIXlt() 会把它们转换成一个列表(列表类型稍后讲述),这时可以用列表元素 year, mon, mday 等取出日期成分。如
x <- as.POSIXct(c('1998-03-16', '2015-11-22')) as.POSIXlt(x)$year + 1900
返回:
7.3 日期计算
因为 Date 类型是用数值保存的,所以可以给日期加减一个整数,如:
x <- as.Date("1970-1-5") x1 <- x + 10; x1
返回:
x2 <- x - 5; x2
返回:
所有的比较运算都适用于日期类型。 可以给一个日期加减一定的秒数,如
as.POSIXct(c('1998-03-16 13:15:45')) - 30
返回:
as.POSIXct(c('1998-03-16 13:15:45')) + 10
返回:
但是两个日期不能相加。 给一个日期加减一定天数,可以通过加减秒数实现,如
as.POSIXct(c('1998-03-16 13:15:45')) + 3600*24*2
返回:
这个例子把日期推后了两天。
用 difftime(time1, time2, units='days') 计算 time1 减去 time2 的天数,如
x <- as.Date("1970-1-5") x1 <- x + 10; x1
返回:
函数结果用 c() 包裹以转换为数值, 否则会带有单位。
调用 difftime() 时如果前两个自变量中含有时间部分,则间隔天数也会带有小数部分。如
x <- as.POSIXct(c('1998-03-16 13:15:45', '2015-11-22 9:45:3')) c(difftime(x[2], x[1], units='days'))
返回:
difftime() 中 units 选项还可以取为 'secs', 'mins', 'hours' 等。
练习
设文件dates.csv中包含如下内容:
"出生日期","发病日期"
"1941/3/8","2007/1/1"
"1972/1/24","2007/1/1"
"1932/6/1","2007/1/1"
"1947/5/17","2007/1/1"
"1943/3/10","2007/1/1"
"1940/1/8","2007/1/1"
"1947/8/5","2007/1/1"
"2005/4/14","2007/1/1"
"1961/6/23","2007/1/2"
"1949/1/10","2007/1/2"
把这个文件读入为 R 数据框 dates.tab,运行如下程序定义 date1 和 date2
变量:
date1 <- dates.tab[,' 出生日期'] date2 <- dates.tab[,' 发病日期']
(1) 把 date1、date2 转换为 R 的 POSIXct 日期型。
(2) 求 date1 中的各个出生年。
(3) 计算发病时的年龄,以周岁论(过生日才算)
(4) 把 date2 中发病年月转换为’monyy’ 格式,这里 mon 是如 FEB 这样英文三字母缩写,yy 是两数字的年份。
(5) 对诸如’FEB91’, ’OCT15’ 这样的年月数据,假设 00—20 表示 21 世纪年份,21—99 表示 20 实际年份。编写 R 函数,输入这样的字符型向量,返回相应的 POSIXct 格式日期,具体日期都取为相应月份的 1 号。这个习题和后两个习题可以预习函数部分来做。
(6) 对 R 的 POSIXct 日期,写函数转换成’FEB91’, ’OCT15’ 这样的年月表示,假设 00—20 表示 21 世纪年份,21—99 表示 20 实际年份。
(7) 给定两个 POSIXct 日期向量 birth 和 work,birth 为生日,work 是入职日期,编写 R 函数,返回相应的入职周岁整数值(不到生日时周岁值要减一)。