1.4 数据结构:字符串、日期时间
1.4.1 字符串
字符串是用双引号或单引号括起来的若干字符,建议用双引号,除非字符串中包含双引号。字符串构成的向量,简称为字符向量。
字符串处理不是R语言的主要功能,但也是必不可少的,数据清洗、可视化等操作都会用到。
tidyverse
系列中的stringr
包提供了一系列接口一致的、简单易用的字符串操作函数,足以代替R自带的字符串函数。这些函数都是向量化的,即作用在字符向量上,对字符向量中的每个字符串做某种操作。
library(stringr)
1.字符串的长度(即包含字符个数)
str_length(c(“a”, “R for data science”, NA))
## [1] 1 18 NA
str_pad(c(“a”, “ab”, “abc”), 3) # 填充到长度为3
## [1] “ a” “ ab” “abc”
str_trunc(“R for data science”, 10) # 截断到长度为10
## [1] “R for d...”
str_trim(c(“a “, “b “, “a b”)) # 移除空格
## [1] “a” “b” “a b”
后三个函数都包含参数side=c(“both”, “left”, “right”)
用于设定操作的方向。
2.字符串合并
str_c(..., sep = “”, collapse = NULL)
sep
:设置间隔符,默认为空字符;
collapse
:指定间隔符,将字符向量中的所有字符串合并为一个字符串。
str_c(“x”, 1:3, sep = “”) # 同paste0(“x”, 1:3), paste(“x”, 1:3, sep=””)
## [1] “x1” “x2” “x3”
str_c(“x”, 1:3, collapse = “_”)
## [1] “x1_x2_x3”
str_c(“x”, str_c(sprintf(“%03d”, 1:3)))
## [1] “x001” “x002” “x003”
注意,上述代码中的1:3
自动向下兼容以适应字符串运算,效果同c(“1”,”2”,”3”)
。
将字符串重复n
次,基本格式为:
str_dup(string, times)
string
:为要重复的字符向量。
times
:为重复的次数。
str_dup(c(“A”,”B”), 3)
## [1] “AAA” “BBB”
str_dup(c(“A”,”B”), c(3,2))
## [1] “AAA” “BB”
3.字符串拆分
str_split(string, pattern) # 返回列表
str_split_fixed(string, pattern, n) # 返回矩阵,n控制返回的列数
string
:要拆分的字符串。
pattern
:指定拆分的分隔符,可以是正则表达式。
x = “10,8,7”
str_split(x, “,”)
## [[1]]
## [1] “10” “8” “7”
str_split_fixed(x, “,”, n = 2)
## [,1] [,2]
## [1,] “10” “8,7”
4.字符串格式化输出
只要在字符串内使用“{
变量名
}
”,那么函数str_glue()
和str_glue_data
就可以将字符串中的变量名替换成变量值,后者的参数.x
支持引入数据框、列表等,相关的代码示例如下所示。
str_glue(“Pi = {pi}”)
## Pi = 3.14159265358979
name = “ 李明”
tele = “13912345678”
str_glue(“姓名: {name}”, “电话号码: {tele}”, .sep=”;”)
## 姓名: 李明;电话号码: 13912345678
df = mtcars[1:3,]
str_glue_data(df, “{rownames(df)} 总功率为 {hp} kW.”)
## Mazda RX4 总功率为 110 kW.
## Mazda RX4 Waq 总功率为 110 kW.
## Datsun 710 总功率为 93 kW.
5.字符串排序
str_sort(x, decreasing, locale, ...)
str_order(x, decreasing, locale, ...)
默认decreasing = FALSE
表示升序,前者返回排好序的元素,后者返回排好序的索引;参数locale
可设定语言,默认为 “en”
(即英语)。
x = c(“banana”, “apple”, “pear”)
str_sort(x)
## [1] “apple” “banana” “pear”
str_order(x)
## [1] 2 1 3
str_sort(c(“香蕉”, “苹果”, “梨”), locale = “ch”)
## [1] “梨” “苹果” “香蕉”
6.检测匹配
str_detect(string, pattern, negate=FALSE)
—检测是否存在匹配。
str_which(string, pattern, negate=FALSE)
—查找匹配的索引。
str_count(string, pattern)
—计算匹配的次数。
str_locate(string, pattern)
—定位匹配的位置。
str_starts(string, pattern)
—检测是否以pattern开头。
str_ends(string, pattern)
—检测是否以pattern结尾。
string
:要检测的字符串。
pattern
:匹配的模式,可以是正则表达式。
negate
:默认为FALSE,表示正常匹配;若为TRUE,则为反匹配(即找不匹配的情况)。
x
## [1] “banana” “apple” “pear”
str_detect(x, “p”)
## [1] FALSE TRUE TRUE
str_which(x, “p”)
## [1] 2 3
str_count(x, “p”)
## [1] 0 2 1
str_locate(x, “a.”) # 正则表达式, .匹配任一字符
## start end
## [1,] 2 3
## [2,] 1 2
## [3,] 3 4
7.提取字符串子集
根据指定的起始和终止位置提取子字符串,基本格式为:
str_sub(string, start = 1, end = -1)
例如:
str_sub(x, 1, 3)
## [1] “ban” “app” “pea”
str_sub(x, 1, 5) # 若长度不够, 则尽可能多地提取
## [1] “banan” “apple” “pear”
str_sub(x, -3, -1)
## [1] “ana” “ple” “ear”
提取字符向量中匹配的字符串,基本格式为:
str_subset(string, pattern, negate=FALSE)
若negate = TRUE
, 则返回不匹配的字符串。
str_subset(x, “p”)
## [1] “apple” “pear”
8.提取匹配的内容
str_extract(string, pattern)
str_match(string, pattern)
str_extract()
只提取匹配的内容。
str_match()
提取匹配的内容以及各个分组捕获,并返回矩阵,矩阵的每行对应于字符向量中的一个字符串,每行的第一个元素是匹配内容,其他元素是各个分组捕获,没有匹配则为NA
。
x = c(“1978-2000”, “2011-2020-2099”)
pat = “\\d{4}” # 正则表达式, 匹配4位数字
str_extract(x, pat)
## [1] “1978” “2011”
str_match(x, pat)
## [,1]
## [1,] “1978”
## [2,] “2011”
9.修改字符串
用新字符串替换查找到的匹配字符串。
做字符替换,基本格式为:
str_replace(string, pattern, replacement)
pattern
:要替换的子字符串或模式。
replacement
:要替换为的新字符串。
x
## [1] “1978-2000” “2011-2020-2099”
str_replace(x, “-”, “/”)
## [1] “1978/2000” “2011/2020-2099”
10.其他函数
● 大小写转化。
❏ str_to_upper()
:转换为大写。
❏ str_to_lower()
:转换为小写。
❏ str_to_title()
:转换标题格式(单词首字母大写)。
str_to_lower(“I love r language.”)
## [1] “i love r language.”
str_to_upper(“I love r language.”)
## [1] “I LOVE R LANGUAGE.”
str_to_title(“I love r language.”)
## [1] “I Love R Language.”
● str_conv(string, encoding)
:转化字符串的字符编码。
● str_view(string, pattern, match)
:在Viewer窗口输出(正则表达式)模式匹配结果。
● word(string, start, end, sep = “ “)
:从英文句子中提取单词。
● str_wrap(string, width = 80, indent = 0, exdent = 0)
:调整段落格式。
关于stringr
包
以上用于查找匹配的各个函数,只是查找第一个匹配,要想查找所有匹配,各个函数都有另一个版本(加后缀_all
),例如str_extract_all()
。
以上各个函数中的参数pattern
都支持用正则表达式(Regular Expression)表示模式。
1.4.2 日期时间
日期时间值通常以字符串形式传入R中,然后转化为以数值形式存储的日期时间变量。
R的内部日期是以1970年1月1日至今的天数来存储,内部时间则是以1970年1月1日至今的秒数来存储。
tidyverse系列的lubridate
包提供了更加方便的函数,可以生成、转换、管理日期时间数据,足以代替R自带的日期时间函数。
library(lubridate)
1.识别日期时间
today()
## [1] “2021-09-20”
now()
## [1] “2021-09-20 21:07:18 CST”
as_datetime(today()) # 日期型转日期时间型
## [1] “2021-09-20 UTC”
as_date(now()) # 日期时间型转日期型
## [1] “2021-09-20”
无论年、月、日、时、分、秒按什么顺序及以什么间隔符分隔,总能正确地识别成日期时间值:
ymd(“2020/03~01”)
## [1] “2020-03-01”
myd(“03202001”)
## [1] “2020-03-01”
dmy(“03012020”)
## [1] “2020-01-03”
ymd_hm(“2020/03~011213”)
## [1] “2020-03-01 12:13:00 UTC”
注意:
根据需要可以任意组合(如ymd_h/myd_hm/dmy_hms
),还可以用参数tz ="…"
指定时区。
我们也可以用make_date()
和make_datetime()
从日期时间组件创建日期时间:
make_date(2020, 8, 27)
## [1] “2020-08-27”
make_datetime(2020, 8, 27, 21, 27, 15)
## [1] “2020-08-27 21:27:15 UTC”
2.格式化输出日期时间
用format()
函数输出日期时间:
d = make_date(2020, 3, 5)
format(d, ‘%Y/%m/%d’)
## [1] “2020/03/05”
用stamp()
函数,按给定模板格式输出日期时间:
t = make_datetime(2020, 3, 5, 21, 7, 15)
fmt = stamp(“Created on Sunday, Jan 1, 1999 3:34 pm”)
fmt(t)
## [1] “Created on Sunday, 03 05, 2020 21:07下午”
3.提取日期时间数据的组件
日期时间数据中的“年、月、日、周、时、分、秒”等称为其组件。常用的日期时间组件如表1.2所示。
表1.2 常用的日期时间组件
t = ymd_hms(“2020/08/27 21:30:27”)
t
## [1] “2020-08-27 21:30:27 UTC”
year(t)
## [1] 2020
quarter(t) # 第几季度
## [1] 3
month(t)
## [1] 8
day(t)
## [1] 27
yday(t) # 当年的第几天
## [1] 240
hour(t)
## [1] 21
minute(t)
## [1] 30
second(t)
## [1] 27
weekdays(t)
## [1] “星期四”
wday(t) # 数值表示本周的第几天, 默认周日是第1天
## [1] 5
wday(t,label = TRUE) # 字符因子型表示本周第几天
## [1]周四
## Levels: 周日 < 周一 < 周二 < 周三 < 周四 < 周五 < 周六
week(t) # 当年的第几周
## [1] 35
tz(t) # 时区
## [1] “UTC”
用with_tz()
将时间数据转换为另一个时区的同一时间;用force_tz()
将时间数据的时区强制转换为另一个时区:
with_tz(t, tz = “America/New_York”)
## [1] “2020-08-27 17:30:27 EDT”
force_tz(t, tz = “America/New_York”)
## [1] “2020-08-27 21:30:27 EDT”
还可以模糊提取(取整)不同的时间单位:
round_date(t, unit=”hour”) # 四舍五入取整到小时
## [1] “2020-08-27 22:00:00 UTC”
注意:
类似地,向下取整用floor_date()
,向上取整用ceiling_date()
。
rollback(dates, roll_to_first=FALSE, preserve_hms=TRUE)
:回滚到上月最后一天或本月第一天。
4.时间段数据
● interval()
:计算两个时间点的时间间隔,返回时间段数据。
begin = ymd_hm(“2019-08-10 14:00”)
end = ymd_hm(“2020-03-05 18:15”)
gap = interval(begin, end) # 同begin %--% end
gap
## [1] 2019-08-10 14:00:00 UTC--2020-03-05 18:15:00 UTC
time_length(gap, “day”) # 计算时间段的长度为多少天
## [1] 208.1771
time_length(gap, “minute”) # 计算时间段的长度为多少分钟
## [1] 299775
t %within% gap # 判断t是否属于该时间段
## [1] FALSE
● duration()
:用“数值+时间单位”存储时段的长度。
duration(100, units = “day”)
## [1] “8640000s (~14.29 weeks)”
int = as.duration(gap)
int
## [1] “17986500s (~29.74 weeks)”
● period()
:和duration()
基本相同。
二者的区别:duration
基于数值线,不考虑闰年和闰秒;period
基于时间线,考虑闰年和闰秒。
比如,在duration
中,1
年总是365.25
天;而在period
中,平年有365
天,闰年有366
天。
● 固定单位的时间段
period
时间段:years()
、months()
、weeks()
、days()
、hours()
、minutes()
、seconds()
。
duration
时间段:dyears()
、dmonths()
、dweeks()
、ddays()
、dhours()
、dminutes()
、dseconds()
。
dyears(1)
## [1] “31557600s (~1 years)”
years(1)
## [1] “1y 0m 0d 0H 0M 0S”
5.日期的时间的计算
用“时间点+时间段”可以生成一个新的时间点:
t + int
## [1] “2021-03-24 01:45:27 UTC”
leap_year(2020) # 判断是否闰年
## [1] TRUE
ymd(20190305) + years(1) # 加period的一年
## [1] “2020-03-05”
ymd(20190305) + dyears(1) # 加duration的一年, 365天
## [1] “2020-03-04 06:00:00 UTC”
t + weeks(1:3)
## [1] “2020-09-03 21:30:27 UTC” “2020-09-10 21:30:27 UTC”
## [3] “2020-09-17 21:30:27 UTC”
除法运算:
gap / ddays(1) # 除法运算, 同time_length(gap,’day’)
## [1] 208.1771
gap %/% ddays(1) # 整除
## [1] 208
gap %% ddays(1) #余数
## [1] 2020-03-05 14:00:00 UTC--2020-03-05 18:15:00 UTC
as.period(gap %% ddays(1))
## [1] “4H 15M 0S”
月份加运算:%m+%
,表示日期按月数增加。例如,生成每月同一天的日期数据:
date = as_date(“2019-01-01”)
date %m+% months(0:11)
## [1] “2019-01-01” “2019-02-01” “2019-03-01” “2019-04-01” “2019-05-01”
## [6] “2019-06-01” “2019-07-01” “2019-08-01” “2019-09-01” “2019-10-01”
## [11] “2019-11-01” “2019-12-01”
用“prety_dates()
”可以生成近似的时间刻度:
x = seq.Date(as_date(“2019-08-02”), by = “year”, length.out = 2)
pretty_dates(x, 12)
## [1] “2019-08-01 UTC” “2019-09-01 UTC” “2019-10-01 UTC”
## [4] “2019-11-01 UTC” “2019-12-01 UTC” “2020-01-01 UTC”
## [7] “2020-02-01 UTC” “2020-03-01 UTC” “2020-04-01 UTC”
## [10] “2020-05-01 UTC” “2020-06-01 UTC” “2020-07-01 UTC”
## [13] “2020-08-01 UTC” “2020-09-01 UTC”
1.4.3 时间序列
为了研究某一事件的规律,依据时间发生的顺序将事件在多个时刻的数值记录下来,就构成了一个时间序列,用表示。
例如,国家或地区的年度财政收入、股票市场的每日波动、气象变化、工厂按小时观测的产量等。另外,随温度、高度等变化而变化的离散序列,也可以看作时间序列。
1.ts对象
Base R
提供的ts
数据类型是专门为时间序列设计的,一个时间序列数据其实就是一个数值型向量,且每个数都有一个时刻与之对应。
用ts()
函数生成时间序列,基本格式如下:
ts(data, start=1, end, frequency=1, ...)
data
:数值向量或矩阵。
start
:设置起始时刻。
end
:设置结束时刻。
frequency
:设置时间频率,默认为1,表示一年有1个数据。
ts(data = 1:10, start = 2010, end = 2019) # 年度数据
## Time Series:
## Start = 2010
## End = 2019
## Frequency = 1
## [1] 1 2 3 4 5 6 7 8 9 10
ts(data = 1:10, start = 2010, frequency = 4) # 季度数据
## Qtr1 Qtr2 Qtr3 Qtr4
## 2010 1 2 3 4
## 2011 5 6 7 8
## 2012 9 10
同理,对于月度数据,frequency = 12
;对于周度数据,frequency = 52
;对于日度数据,frequency = 365
。
2.tsibble
fpp3
生态下的tsibble
包提供了整洁的时间序列数据结构tsibble
。
时间序列数据无非就是“指标数据+时间索引”(或者再加“分组索引”)。
注意:
多元时间序列就是包含多个指标列。
分组时间序列数据首先是一个数据框,若有分组变量需采用“长格式”作为一列(长宽格式及转化参见2.4节),只需要指定时间索引、分组索引,就能变成时间序列数据结构。
例如,现有3个公司2017年的日度股票数据(tibble
格式),其中存放3只股票的Stock
列为分组索引:
load(“data/stocks.rda”)
stocks
## # A tibble: 753 x 3
## Date Stock Close
## <date> <chr> <dbl>
## 1 2017-01-03 Google 786.
## 2 2017-01-03 Amazon 754.
## 3 2017-01-03 Apple 116.
## 4 2017-01-04 Google 787.
## 5 2017-01-04 Amazon 757.
## 6 2017-01-04 Apple 116.
## # ... with 747 more rows
用as_tsibble()
将数据框转化为时间序列对象tsibble
, 只需要指定时间索引(index
)、分组索引(key
):
library(fpp3)
stocks = as_tsibble(stocks, key = Stock, index = Date)
stocks
## # A tsibble: 753 x 3 [1D]
## # Key: Stock [3]
## Date Stock Close
## <date> <chr> <dbl>
## 1 2017-01-03 Amazon 754.
## 2 2017-01-04 Amazon 757.
## 3 2017-01-05 Amazon 780.
## 4 2017-01-06 Amazon 796.
## 5 2017-01-09 Amazon 797.
## 6 2017-01-10 Amazon 796.
## # ... with 747 more rows
tsibble
对象非常便于后续处理和探索:
stocks %>%
group_by_key() ٪>٪
index_by(weeks = ~ yearweek(.)) ٪>٪ # 周度汇总
summarise(max_week = mean(Close))
## # A tsibble: 156 x 3 [1W]
## # Key: Stock [3]
## Stock weeks max_week
## <chr> <week> <dbl>
## 1 Amazon 2017 W01 772.
## 2 Amazon 2017 W02 805.
## 3 Amazon 2017 W03 809.
## 4 Amazon 2017 W04 830.
## 5 Amazon 2017 W05 827.
## 6 Amazon 2017 W06 818.
## # ... with 150 more rows
autoplot(stocks) # 可视化
可视化结果如图1.11所示。
图1.11 可视化股票数据