首页 理论教育 基于ggplot的政经数据可视化-日期/时间对象

基于ggplot的政经数据可视化-日期/时间对象

时间:2023-11-19 理论教育 版权反馈
【摘要】:在可视化任务中,我们时常会遇到与日期/时间相关的信息。我们固然可以使用诸如"2019-07-09"或"2019年7月"这样的字符,来表示日期/时间信息,并用正则表达式来进行处理,但实际上,R为日期/时间对象提供了专门的对象类型和大量相关的函数。接下来,我们将依次介绍日期/时间表示法、时间序列对象、lubridate包中的若干函数,以及与日期/时间有关的缺失值填补。日期/时间对象的处理与区域设置有关。

基于ggplot的政经数据可视化-日期/时间对象

可视化任务中,我们时常会遇到与日期/时间相关的信息。我们固然可以使用诸如"2019-07-09"或"2019年7月"这样的字符,来表示日期/时间信息,并用正则表达式来进行处理,但实际上,R为日期/时间对象提供了专门的对象类型和大量相关的函数。接下来,我们将依次介绍日期/时间表示法、时间序列对象、lubridate包中的若干函数,以及与日期/时间有关的缺失值填补。读者既可以现在学习这些内容,也可以等到读到后边的折线图的绘制方法时再学习这些内容。

一、日期

1. 生成日期

x=Sys.Date() # 查看当前日期

# [1] "2019-07-09"

class(x) # 查看对象类型

# [1] "Date" # 可见这里的日期对象的类型为Date尽管看上去像一个character对象

## 用as.Date将character对象转化为Date对象用as.character将Date对象转回character对象

x=as.Date("1998-08-31")

y=as.character(x)

## 还可将Date对象转为数值,数值代表的是日期距离1970年1月1日的天数, 1970年1月1日以后的为正数,以前的为负数

x=as.Date(c("1970-01-03", "1969-12-30", "1998-08-31"))

as.numeric(x)

# [1] 2 -2 10469

## 注意:不能用as.Date转化不存在的日期

# 因此,as.Date("2019-2-29")或as.Date("2019-4-31")都将报错

## as.Date可识别的默认格式

# 年份用四位数表示;月和日可以写满两位也可以只写一位;间隔符可以是"-"也可以是"/";但必须保证向量中的元素只包含一种格式

as.Date(c("1998-08-31", "1998-8-31"))

as.Date(c("1998/8/31", "1998/08/31"))

as.Date(c("1998-08-31", "1998-8-31", "1998/8/31", "1998/08/31")) # 结果中出现NA,因为向量包含两种间隔符

## 用format参数帮助函数理解字符中的日期信息

as.Date("19980831", format="%Y%m%d") # 若不指定format,则会报错

# 这里的"%Y"等都是代表日期元素的表达式,其中,"%Y"代表四位数年份,"%y"代表两位数年份,"%m"代表月份,"%d"代表日

as.Date("1998abc08xyz31", format="%Yabc%mxyz%d") # 只要指定format,即使输入字符包含无效内容也仍可被识别

as.Date("8/31 1998", format="%m/%d %Y")

as.Date("19/07 09", format="%y/%m %d") # 这里的"19"只会被理解成2019年,不会被理解成1919年

as.Date("07-09", "%m-%d") # 当不包含年份时,返回当前年份

# as.Date("2018-12", "%Y-%m") # 注意:必须加上日子,否则结果为NA

与日期相关并以"%"开头的表达式,在生成日期/时间对象或从日期/时间对象中提取信息时,常会被用到。它们的具体含义可通过?strptime查到。以下,我们对常用的表达式进行了总结,其中一些要到后文讲到时间对象时才会出现,到时候读者若遇到不明白的表达式可回到此处查看。

%Y:四位数年份。

%y:两位数年份。数字00-68将被识别为20xx,如:2000、2050、2068;数字69-99将被识别为19xx,如:1968、1990、1999。

%m:月份,如:01、02、1、2、11、12。

%B:月份全称,如:八月、August。

%b:月份缩写,如:8月、AUG。

%d:日,如:01、02、1、2、30。

%A:星期全称,如:星期一、Monday。

%a:星期缩写,如:周一、MON。

%u:以数字形式出现的星期,使用1-7的数字,如:周一表示为1,周二表示为2。

%w:以数字形式出现的星期,如用0-6的数字,如:周日表示为0,周一表示为1。

%H:24小时制两位数小时,如:00、01、23,但形如"24:00:00"的表达方式也被接受。

%I:12小时制两位数小时,如:01、02、12。

%p:在12小时制下指示上午或下午,如:上午、下午、AM、PM。有的区域设置或操作系统无法使用此表达式。

%M:两位数分钟,即:00、01、59。

%S:整数秒,取值为00-61的数字。

%OS:可带小数的秒,"OS"后边的数字用于指定小数位数,如:对于13.348秒,"OS1"可提取到13.3,"OS2"可提取到13.34,"OS"后不加数字则只提取整数。

日期/时间对象的处理与区域设置有关。如果读者的操作系统的区域设置为简体中文的话,那么as.Date(x="1998/八月/31", format="%Y/%B/%d")将会得到正确的结果,而as.Date(x="1998/AUG/31", format="%Y/%B/%d")则无效。相反,如果读者的操作系统使用英文的话,后者有效,前者无效。注意:我们将使用"%B"和"%b"代表月份的完整名称和缩写。

## 那么如何在不改变Windows系统区域设置的情况下在R内部作出更改

Sys.getlocale("LC_TIME") # 查询当前时间设置方式在简体中文操作系统中,会得到"Chinese (Simplified)_China.936"或类似的结果

Sys.setlocale("LC_TIME", "English") # 我们现在将时间设置方式改为英语,以便使用英文名称

as.Date(c("1998/Aug/31", "1998/aug/31", "1998/AUG/31"), "%Y/%b/%d")

# 可忽略名称的大小写

as.Date(c("August3198", "august3198", "AUGUST3198"), " %B%d%y") # 可忽略名称的大小写

Sys.setlocale("LC_TIME", "Chinese (Simplified)_China.936") # 改回到原来的简体中文设置

## 当多种格式并存并且我们能够罗列出所有可能的格式时可通过以下方式指定多种格式

x=c("1998-08-31", "1998/08/31", "1998年8月31日", "19980831")

myformat=rep(NA, length(x))

for (i in 1: length(x)){

ii=x[i]

myformat[i]=if (grepl("\\-", ii)) "%Y-%m-%d"

else if (grepl("/", ii)) "%Y/%m/%d"

else if (grepl("年", ii)) "%Y年%m月%d日"

else "%Y%m%d"

}

as.Date(x, format=myformat)

## 通过指定天数和起始日期的时间确定日期

as.Date(2, origin="1998-08-31") # "1998-09-02"

as.Date(-2, origin="19980831", format="%Y%m%d") # "1998-08-29"

as.Date(1: 365, origin=as.Date("1998-08-31"))

## 通过seq生成等间隔时间

a=as.Date("1984-08-17"); b=as.Date("1998-08-31")

seq(a, b, length.out=10) # 生成等间隔的10个日期

seq(a, b, "2 month") # 以两个月为步长注意"month"不需要使用复数

seq(a, b, "50 day") # "day""month""year""week"均可用来设置步长

seq(a, b, "2 week")

seq(a, by="2 year", length.out=8)

2. 比较和计算日期

## 日期比较

a=as.Date("1984-08-17"); b=as.Date("1998-08-31")

a < b # 靠后的时间较大

x=seq(a, b, by="1 year")

min(x); max(x); mean(x); median(x)

y=as.character(summary(x)) # 简单汇总

## 日期计算

c(a, b)+5 # 5天后

y=b-a # 相减

class(y) # 相减的结果是一个difftime对象

as.numeric(y) # 转化为普通数值

difftime(b, a) # 另一种相减的写法

difftime(b, a, units="hours") # 用units指定计算间隔时的单位可选择"auto", "secs", "mins", "hours", "days", "weeks"

## 注意闰年带来的影响

as.Date("2020-7-9")-as.Date("2019-7-9") # 366

as.Date("2019-7-9")-as.Date("2018-7-9") # 365

## 顺序

x=as.Date(c("1970-01-03", "1969-12-30", "2019-07-09", "1900-0101"))

sort(x); order(x); rank(x)

二、时间

R自带的时间对象为POSIXct对象,它与Date对象的差异在于,前者除了年/月/日外还包括时钟时间。

1. 生成时间

x=Sys.time() # 获取当前时间

# "2019-07-09 16:08:31 CST"

class(x) # "POSIXct" "POSIXt"

x=as.POSIXct("2019-07-09 16:08:31 CST") # 将字符转化为POSIXct对象

as.character(x) # 将POSIXct对象转为字符

## 字符包含的时区会被忽略因为R会强制使用本地时区如果字符不包含时区,会被自动补上本地时区

x=c("2019-07-09 12:24:16 UTC", "2019-07-09 12:24:16", "2019-07-09 12:24:16 EST")

y=as.POSIXct(x) # "2019-07-09 12:24:16 CST" "2019-07-09 12:24:16 CST" "2019-07-09 12:24:16 CST"

y=as.POSIXct(x, tz="UTC") # 要想指定时区必须使用tz参数

# 注意时区UTC和GMT是相同的

# 笔者的时区为CST若要显示此时区应设定tz="Asia/Shanghai"

y=as.POSIXct(x, tz="Asia/Shanghai")

## 修改format参数

## 以下我们将使用"%H""%M""%OS"来提取小时分钟和秒的信息

## as.POSIXct函数会自动尝试以下格式"%Y-%m-%d %H:%M:%OS""%Y/%m/%d %H:%M:%OS""%Y-%m-%d %H:%M""%Y/%m/%d %H:%M""%Y-%m-%d""%Y/%m/%d"

as.POSIXct(x="2019-07-09 13:01") # 不加秒

as.POSIXct("7/9-2019", format="%m/%d-%Y") # 只有日期

as.POSIXct("8:20:01 2019-07-09", tz="HST", format="%H:%M:%OS %Y-%m%d") # 自定义format

## 注意在as.POSIXct中指定origin参数时会因时区问题出错

as.POSIXct(3600, origin="2019-07-09 13:56:40")

# [1] "2019-07-09 22:56:40 CST" # 结果并不是预想的"2019-07-09 14:56:40 UTC"这是因为这种写法相当于as.POSIXct(3600, origin=as.POSIXct("2019 07-09 13:56:40", tz="UTC"), tz="Asia/Shanghai")

## 解决办法一在两个位置同时用tz参数指定同一个时区

as.POSIXct(3600, origin=as.POSIXct("2019-07-09 13:56:40", tz="UTC"), tz="UTC")

as.POSIXct(3600, origin=as.POSIXct("2019-07-09 13:56:40", tz="Asia/Shanghai"), tz="Asia/Shanghai")

## 解决办法二用+得到本地时区的结果

as.POSIXct("2019-07-09 13:56:40")+3600

## 12小时制与24小时制"%I"代表12小时制的时间格式它必须与"%p"搭配使用

# 当Sys.getlocale("LC_TIME")的值为中文简体时"%p"与"上午"或"下午"匹配

as.POSIXct("2019-07-09 下午 8:24:16", format="%Y-%m-%d %p %I:%M:%OS")

# 当Sys.getlocale("LC_TIME")的值为英文时"%p"与"am"或"pm"匹配忽略大小写

Sys.setlocale("LC_TIME", "English")

as.POSIXct("2019-07-09 PM 8:24:16", format="%Y-%m-%d %p %I:%M:%OS")

# 完成上述操作后请将区域修改成原样Sys.setlocale("LC_TIME", "Chinese(Simplified)_China.936")

## 用seq生成序列

a=as.POSIXct("2019-07-09 13:56:40"); b=as.POSIXct("2019-07-09 14:56:40")

seq(a, b, length.out=10)

seq(a, b, by="2 min")

## round.POSIXttrunc.POSIXt可选units有"secs", "mins", "hours", "days","months", "years"

x=c("2019-05-16", "2019-05-17", "2019-04-16", "2019-02-15", "202002-15")

round.POSIXt(as.Date(x), "months")

# [1] "2019-05-01 CST" "2019-06-01 CST" "2019-05-01 CST" "2019-0301 CST" "2020-02-01 CST" # 注意结果跟所在月份的天数有关

round.POSIXt(as.POSIXct("2019-07-09 15:30:18"), units="months") #注意在保留月份时结果后边仍然会附带上本月1日

trunc.POSIXt(as.POSIXct("2019-07-30 15:38:18"), units="months") #与round.POSIXct进行四舍五入不同trunc.POSIXt只截取到所要求的位数不会四舍五入

trunc.POSIXt(as.POSIXct("2019-05-16 15:38:18"), units="hours")

2. 比较和计算时间

对时间进行比较和计算的方式与对日期进行比较和计算的方式相仿。

a=as.POSIXct("2019-07-09 13:56:40"); b=as.POSIXct("2019-07-09 14:56:40")

x=seq(a, b, length.out=10)

b > a

mean(x); max(x); min(x); median(x)

y=as.character(summary(x)) # 简单汇总

a+c(3600, 7200)

difftime(b, a)

difftime(b, a, units="secs") # units默认为"auto"即自动选择可选择"secs""mins""hours""days""weeks"

3. 日期/时间对象的保存

char1=c("1998/8/31", "2019/7/9")

char2=c("1998-8-31 06:06:06", "2019-7-9 11:11:11")

date=as.Date(c("1998/8/31", "2019/7/9"))

time=as.POSIXct(c("1998-8-31 06:06:06", "2019-7-9 11:11:11"))

char3=paste(c("1998-8-31 06:06:06", "2019-7-9 11:11:11"), "abcde", sep="")

dat=data.frame(char1, char2, date, time, char3)

# write.csv(dat, "datetime.csv") # 打开csv文件后会发现上述信息会以Excel默认的格式显示

# dat=read.csv("datetime.csv", row.names=1) # 但是读取文件后发现格式正常

三、从日期/时间对象中提取信息

以上,我们用as.Date、as.POSIXct等函数生成日期/时间,但是,如果一个对象已经是日期/时间对象了,我们如何从中提取出年份、月份等信息并将其书写成我们需要的格式呢?

## 日期

x=as.Date("2019-07-09")

format(x) # "2019-07-09" # 相当于as.character(x)

format(x, "%m-%d") # "07-09" # 按照我们定义的格式输出

format(x, "这是月%m这是日%d")

format(x, "%m, %d, %A")

## 时间

x=as.POSIXct("2019-07-09 13:30:01")

format(x, "%Y-%m-%d")

format(x, "现在时间%H时%M分")

format(x, "%p%I:%M") # 24小时和12小时制转换

## 时区的影响

x=as.POSIXct("2019-07-09 13:30:01") # "2019-07-09 13:30:01 CST" #时区为笔者所在的CST

format(x, "%H:%M:%OS") # "13:30:01" # 不修改时区(www.xing528.com)

format(x, "%H:%M:%OS", tz="UTC") # "05:30:01" # 将时区改为UTC则会出现8个小时的变动

## 几个方便函数

x=as.POSIXct(c("1998-8-31 06:06:06", "2019-7-9 11:11:11", "2019-0709 15:24:16"))

weekdays(x, abbreviate=FALSE)

months(x, abbreviate=FALSE)

quarters(x) # 返回所在季度取值为"Q1""Q2""Q3""Q4"

#==========

# 练习

#==========

## 对一组出生日期进行分组将1970年1月1日以前出生的归为老年将1990年1月1日以后出生的归为青年将这两个日期之间的归为中年

birth=as.Date(c("1960-03-10", "1975-11-05", "1997-08-30", "1988-1230"))

limit1=as.Date("1990-01-01"); limit2=as.Date("1970-01-01")

g=rep(NA, length(birth))

for (i in 1: length(birth)){

ii=birth[i]

g[i]=if (ii < limit2) "老年" else if (ii >= limit2 & ii < limit1) "中年" else "青年"

}

四、时间序列对象

时间序列对象是时间与数值的结合。例如,为了储存5个月的失业率信息,我们可以使用数据框,数据框的第1列是采样时间,第2列是失业率数值;但是,我们在R中还可以使用时间序列对象。

dat=ts(c(0.06, 0.07, 0.05, 0.04, 0.06), frequency=12, start=1)

# Jan Feb Mar Apr May

# 1 0.06 0.07 0.05 0.04 0.06

class(dat) # "ts"

## 使用ts函数可以自动将采样数值与月份和季度匹配起来

ts(1: 15, frequency=12, start=c(1998, 2)) # 这里有15个数值需要分配给15个时间点ts函数对这里的参数值的理解是每年采样12次也就是按月采样,从1998年2月开始采样

ts(1: 15, frequency=4, start=c(1998, 2)) # 与上例不同这里的frequency= 4被ts函数视为一年采样4次也就是按季度采样而c(1998, 2)则代表从1998年第2季度采样

## 使用ts函数但不让数值与月和季度匹配

ts(1: 14, frequency=6, start=3) # 从第3个时间位置开始采样每个时间位置采样6次

# Start = c(3, 1)

# End = c(5, 2)

# Frequency = 6

# 输出结果的含义是第1个数值来自第3个时间位置的第1次采样最后一个数值是第5个时间位置的第2次采样因此共有6+6+2=14个数值

ts(1: 14, frequency=6, start=c(3, 3)) # 现在改成第1个数值是第3个时间位置的第3次采样

# Start = c(3, 3)

# End = c(5, 4)

# Frequency = 6

ts(1: 40, frequency=1, start=1978) # 一年一个数值

## 如果输入的是数据框ts函数会将每一列当成一套独立的数据

x=data.frame(a=1: 12, b=101: 112)

dat=ts(x, frequency=4, start=c(2008, 1))

class(dat) # "mts" "ts" "matrix"

## 截取时间序列对象的一部分

x=ts(1: 31, frequency=12, start=c(1998, 1))

window(x, start=c(1998, 3), end=c(2000, 5)) # 截取1998年3月至2000年5月的数据

x=ts(1: 23, frequency=6, start=3)

window(x, start=c(3, 2), end=c(6, 5)) # 截取第3个时间位置第2次采样至第6个时间位置第5次采样之间的数据

五、lubridate包

lubridate包提供了很多用于处理日期/时间对象的方便函数。

# install.packages("lubridate")

library(lubridate)

## lubridate包提供了一些用于把字符转化成日期/时间对象的函数这些函数比as.Date和as.POSIXct灵活得多

ymd(c("1998-08-31", "19980831", "1998/8/31", "1998-8/31", "1998, AUG 31st"))

# ymd(c("1998年8月31日", "1998年八月31日")) # 对中文的支持存在不确定性所以请尽量用英文

mdy(c("8-31-1998", "08311998"))

dmy(c("31/8/1998", "31st/Aug/1998"))

ydm("1998 31st, 8")

ymd_hms(c("2019-07-09 20:08:03", "2019-07-09 8:08:03 pm", "2019-0709 8:08:03")) # 注意lubridate包默认使用UTC时区

ymd_hms("2019-07-09 20:08:00", tz = "Asia/Shanghai") # 要使用CST时区需手动设置tz函数

ymd_hm("2019-07-09 20:08")

ymd_h("2019-07-09 20")

# 同类函数还有dmy_hmsdmy_hmdmy_hmdy_hmsmdy_hmmdy_hydm_hmsydm_hmydm_h我们根据名称就可猜到它们的用途

floor_date(ymd("2019-07-09"), "month") # "2019-07-01" # 向下取整

ceiling_date(ymd("2019-07-09"), "year") # "2020-01-01" # 向上取整

## 提取信息

x=ymd_hms("2019-07-09 20:08:03")

second(x) # 提取秒返回数值而不是字符

## 同类函数还有minutehourdayyeartz

wday(x) # 返回数值形式的星期1为周日

wday(x, label=TRUE) # 返回定序变量周日周一周二……

yday(x) # 一年中的第几天

week(x) # 一年中的第几周

month(x); month(x, label=TRUE) # 月份

leap_year(2020) # 判断是否是闰年

## 时间段

period_a=ymd("20190630") %--% ymd("20190731") # 生成第1个类型为Interval的时间段

period_b=ymd("20190715") %--% ymd("20190804") # 生成第2个时间段

int_overlaps(period_a, period_b) # 两个时间段是否有重合

intersect(period_a, period_b) # 如果有重合重合的部分是什么

union(period_a, period_b) # 合并两个时间段

## Duration对象和Period对象

## 按照lubridate包的文档的解释Duration对象是以秒计算的时间

dseconds(x=1); dminutes(x=1); dhours(x=1)

ddays(x=1); dweeks(x=1); dyears(x=1) #有的版本需要使用lubridate::: dmonths

## Period对象的表示方法有所不同

seconds(x=1); minutes(x=1); hours(x=1); days(x=1)

weeks(x=1); months(x=1); years(x=1)

ymd("2012-01-01")+dyears(1)

# "2012-12-31 06:00:00 UTC" # Duration对象不随闰年改变所以这里相当于加上了365.25天

ymd("2012-01-01")+years(1)

# "2013-01-01" # Period对象随闰年改变所以这里正确地加上了366天而不是365天

## 相加时出现日期不存在的情况

ymd("2012-01-31")+months(1) # NA # 加上1个月被认为是加上31天而2月31日不存在

ymd("2012-01-31")+months(2) # "2012-03-31"

ymd("2012-01-31")+months(3) # NA # 4月31日也不存在

## 解决办法用%m+%确保输出确实存在的日期

ymd("2012-01-31") %m+% months(1) # "2012-02-29"

ymd("2012-01-31") %m+% months(3) # "2012-04-30"

## 修改单个元素

x=ymd_hms("2019-07-09 20:08:03")

second(x)=33

year(x)=2008

tz(x)="Asia/Shanghai" # 等同于force_tz(x, "Asia/Shanghai")

with_tz(x, "America/Chicago") # "2019-07-09 07:08:33 CDT" # 转化成另一个时区的时间

六、填充缺失值

1. 固定值填充和线性填充

尽管我们可以去掉包含缺失值的个案,但有时,为了得到完整或美观的图表,我们还需保留这些个案并把缺失值填充上。在处理跟日期和时间相关的缺失值时,我们可使用zoo包。

# install.packages("zoo")

library(zoo)

## 我们使用na.approx函数进行线性填充但在这之前必须把待填充的向量转化成zoo对象

x=zoo(c(NA, 1, NA, NA, NA, 3, NA, 7, 4, NA))

class(x) # "zoo"

y=na.approx(x, na.rm=FALSE, method="linear")

# 注意有时第一个值或最后一个值是缺失值默认情况下这些值不但不会被填充而且会被删除为保持结果的项数与输入的项数一致务必设置na.rm=FALSE

as.numeric(y) # 将填充结果转为数值向量

## 使用固定值填充默认设置是将数值按顺序排列用缺失值左边的非缺失值填充

na.approx(x, na.rm=FALSE, method="constant")

# 当把默认的f=0改为f=1时则用缺失值右边的非缺失值填充

na.approx(x, na.rm=FALSE, method="constant", f=1)

# 事实上这里的f可以改成0与1之间的任意数

na.approx(x, na.rm=FALSE, method="constant", f=0.25) # 以1与3之间的缺失值为例(3-1)*0.25=0.51+0.5=1.5所以这些缺失值被替换成了1.5

## zoo函数的索引号参数order.by

# 默认情况下索引号是连续的

x=zoo(c(10, NA, 30, 40, 50)) # 相当于zoo(c(10, NA, 30, 40, 50), order.

by=1: 5)

na.approx(x)

# 但索引号也可以是非连续的

x=zoo(c(10, NA, 50, 30, 40), order.by=c(1, 2, 5, 3, 4))

na.approx(x)

# 索引号不连续的情境是例如尽管我们按照时间点12345采样但是当把数值录入到表格里时并没有按顺序录入

dat=data.frame(v=c(10, NA, 50, 30, 40), time=c(1, 2, 5, 3, 4))

x=zoo(dat$v, order.by=dat$time)

na.approx(x)

# 注意索引号不同时结果也不同所以为防止出错尽量使用连续的索引号

x=zoo(c(0, NA, NA, 1), order.by=c(1, 2, 3, 4)); na.approx(x)

x=zoo(c(0, NA, NA, 1), order.by=c(1, 2, 3, 5)); na.approx(x)

## 使用na.locf填充头部和尾部的缺失值

x=zoo(c(NA, NA, 1, NA, 2, 3, NA, NA))

y=na.approx(x, na.rm=FALSE) # 此时向量头部和尾部仍是缺失值

y=na.locf(y, na.rm=FALSE) # 填充尾部缺失值

y=na.locf(y, na.rm=FALSE, from Last=TRUE) # 填充头部缺失值

# 可见要填充全部缺失值我们需同时使用na.approx和na.locf

## 现在假设我们要对1至6月的数据作图但实际上我们只有235月的数据,这意味着我们需要填充146月的缺失值

d1=as.Date(c("2019-02-01", "2019-03-01", "2019-05-01")) # 3个有数据的月份

x1=c(22, 33, 55) # 与上述3个月份相对应的数值

# 接下来开始填充第1步生成完整的日期

d2=seq(as.Date("2019-01-01"), as.Date("2019-06-01"), by="month")

# 第2步生成完整的带缺失值的数据并将非缺失值填入

x2=rep(NA, length(d2))

pos=match(d1, d2)

x2[pos]=x1

# 第3步填充

# 方法1以日期为索引号

z=zoo(x2, order.by=d2)

result=na.approx(z, na.rm=FALSE)

# 方法2以123……为索引号结果与方法1不同

z=zoo(x2, order.by=1: length(x2))

result=na.approx(z, na.rm=FALSE)

# 第4步填充两边的缺失值

result=na.locf(result, na.rm=FALSE)

result=na.locf(result, na.rm=FALSE, from Last=TRUE)

## 上述方法1和方法2的区别在以下例子中更为明显

z=zoo(c(1, NA, 3, NA, 5), order.by=as.Date(c("2019-01-01", "2019-0102", "2019-01-03", "2019-01-05", "2019-01-31")))

na.approx(z, na.rm=FALSE) # 1月2日与1月1日与1月3日是等间隔的,所以缺失值被替换成了1日与3日的中间值2相反1月5日与1月3日与1月31日不是等间隔的所以缺失值没有被替换成中间值4

z=zoo(c(1, NA, 3, NA, 5), order.by=1: 5)

na.approx(z, na.rm=FALSE) # 如果只以123……为索引号则日期之间的真实间隔被忽略所以缺失值被替换成了2和4

2. 其他填充方法

用zoo包中的na.spline函数可实现样条填充,使用方法与na.approx相同。impute TS包中的na.interpolation函数也可实现以线性方式填充。

# install.packages("impute TS")

library(impute TS)

x=c(NA, 1, 3, NA, 5, 6, NA, NA, NA, 9, NA)

na.interpolation(x, option="linear") # option还可选"spline""stine"

此外impute TS包还包含其他填充方法

na.kalman(x, model="Struct TS") # 用Struct TS函数拟合模型并填充

na.kalman(x, model="auto.arima") # 用auto.arima函数拟合模型并填充

na.ma(x, k=2, weighting="exponential") # 加权移动平均法参数k指定使用多少个非缺失值例如k=2代表使用左边2个+右边2个=4个非缺失值weighting为加权方法默认为"exponential"还可选择"simple"或"linear"

na.mean(x, option="mean") # option还可以是"mean""median""mode",代表以均值中位数或众数填充

na.replace(x, fill=9999) # 填充一个特定数

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈