R语言编程:基于tidyverse
上QQ阅读APP看书,第一时间看更新

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 可视化股票数据