• Articles posted by Conan Zhang

Author Archives: Conan Zhang

About Conan Zhang

Programmer(Java,R,PHP,Javascript)

R语言时间序列扩展类型tsibble

R的极客理想系列文章,涵盖了R的思想,使用,工具,创新等的一系列要点,以我个人的学习和体验去诠释R的强大。

R语言作为统计学一门语言,一直在小众领域闪耀着光芒。直到大数据的爆发,R语言变成了一门炙手可热的数据分析的利器。随着越来越多的工程背景的人的加入,R语言的社区在迅速扩大成长。现在已不仅仅是统计领域,教育,银行,电商,互联网….都在使用R语言。

要成为有理想的极客,我们不能停留在语法上,要掌握牢固的数学,概率,统计知识,同时还要有创新精神,把R语言发挥到各个领域。让我们一起动起来吧,开始R的极客理想。

关于作者:

  • 张丹,数据分析师/程序员/Quant: R,Java,Nodejs
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/r-tsibble/

前言

数据科学tidyverse个项目,已经很大程度上改写的R语言的编程范式,tibble作为标准的数据结构,已替代了基础的data.frame。tsibble作为tibble对时间序列的补充,近一步规范化了数据结构,让编程更简洁。

目录

  1. tsibble介绍
  2. tsibble安装
  3. tsibble包的基本使用

1. tsibble介绍

tsibble是一个tibble的扩展项目,用于扩展对于时间序列数据,形成一个标准化的时间序列数据结构。tibble数据类型请参考文档,R语言数据科学新类型tibble

tsibble 包提供了一个 tbl_ts 数据类,用于表示标准化的时间数据。一个 tsibble 由一个时间索引、键和其他测量变量组成,采用以数据为中心的格式,构建在 tibble 之上。

tsibble的官方网站:https://tsibble.tidyverts.org/

1.1 索引

tsibble的核心设计,就在索引的的结构。tsibble 支持广泛的索引类别:

  • R 语言原生时间结构(如 Date、POSIXct 和 difftime)
  • tsibble 新增的类(如 yearweek、yearmonth 和 yearquarter)
  • 其他常用类:ordered、hms::hms、lubridate::period 和 nanotime::nanotime

对于具有规则间隔的 tbl_ts,需要选择索引的表示形式。例如,月度数据应使用 yearmonth 创建的时间索引,而不是 Date 或 POSIXct。因为一年中的月份确保了规律性,每年都是12个月。然而,如果使用 Date,一个月的天数在28到31天之间变化,会导致不规则的时间空间。这也适用于年-周和年-季度。

tsibble 支持任意的索引类别,只要它们能按从过去到未来的顺序排列。要支持自定义类,需要为该类定义 index_valid(),并通过 interval_pull() 计算时间间隔。

1.2 键

键变量与索引一起唯一标识每条记录:

  • 空键:一个隐式变量。NULL 表示生成单变量时间序列。
  • 单个变量:例如,data(pedestrian) 使用 Sensor 作为键。
  • 多个变量:例如,为 data(tourism) 声明 key = c(Region, State, Purpose)。键可以与 tidy 选择器(如 starts_with())结合使用。

1.3 时间间隔:

时间间隔:interval 函数返回与 tsibble 关联的时间间隔。

  • 规则间隔:包括其值和时间单位:”nanosecond”(纳秒)、”microsecond”(微秒)、”millisecond”(毫秒)、”second”(秒)、”minute”(分)、”hour”(时)、”day”(日)、”week”(周)、”month”(月)、”quarter”(季)、”year”(年)。无法识别的时间间隔标记为 “unit”。
  • 不规则间隔:as_tsibble(regular = FALSE) 生成不规则 tsibble,标记为 !。
  • 未知间隔:标记为 ?,如果是空的 tsibble,或者每个键变量只有一个条目。

时间间隔基于相应的索引表示形式获得:

  • “year” (Y)
  • yearquarter:”quarter” (Q)
  • yearmonth:”month” (M)
  • yearweek:”week” (W)
  • Date:”day” (D)
  • difftime:”week” (W)、”day” (D)、”hour” (h)、”minute” (m)、”second” (s)
  • POSIXt/hms:”hour” (h)、”minute” (m)、”second” (s)、”millisecond” (us)、”microsecond” (ms)
  • period:”year” (Y)、”month” (M)、”day” (D)、”hour” (h)、”minute” (m)、”second”
  • (s)、”millisecond” (us)、”microsecond” (ms)

  • nanotime:”nanosecond” (ns)
  • 其他数值 & ordered(有序因子):”unit”。当由于索引格式不匹配而无法获取时间间隔时,会发出错误。

时间间隔对子集操作(如 filter()、slice() 和 [.tbl_ts)保持不变。但是,如果结果是一个空的 tsibble,时间间隔总是未知的。当将 tsibble 与其他数据源连接并聚合到不同的时间尺度时,时间间隔会被重新计算。

1.4 时区

如果索引是 POSIXct,将显示索引对应的时区。? 表示获取到的时区是零长度字符 “”。

2. tsibble安装

tsibble包的安装,安装tsibble包非常简单,2条命令就可以完成,安装和加载。


# 安装
> install.packages("tsibble")

# 加载
> library(tsibble)

3. tsibble包的基本使用

3.1 构建年月日数据

构建一个tsibble数据集,“年月日”格式


# 加载时间包
> library(lubridate)

# 创建一个年月的时间
> mth <- make_date("2018") + months(0:3)
> mth
[1] "2018-01-01" "2018-02-01" "2018-03-01" "2018-04-01"

# 构造tsibble:年月日
> tsibble(mth = mth, index = mth)
# A tsibble: 4 x 1 [1D]
  mth       
      
1 2018-01-01
2 2018-02-01
3 2018-03-01
4 2018-04-01

# 构造tsibble:年月
> tsibble(mth = yearmonth(mth), index = mth)
# A tsibble: 4 x 1 [1M]
       mth
     
1 2018 1月
2 2018 2月
3 2018 3月
4 2018 4月

3.2 构建时分秒数据

构建一个tsibble数据集,“时分秒”格式。


# 构建一个时间类型,时区为shanghai
> x <- ymd_h("2015-04-05 01", tz = "Asia/Shanghai")

# 构建tsibble的时间类型
> tsibble(time = x + (c(0, 3, 6, 9)) * 60 * 60, index = time)
# A tsibble: 4 x 1 [3h] 
  time               
               
1 2015-04-05 01:00:00
2 2015-04-05 04:00:00
3 2015-04-05 07:00:00
4 2015-04-05 10:00:00

> tsibble(time = x + hours(c(0, 3, 6, 9)), index = time)
# A tsibble: 4 x 1 [3h] 
  time               
               
1 2015-04-05 01:00:00
2 2015-04-05 04:00:00
3 2015-04-05 07:00:00
4 2015-04-05 10:00:00

3.3 数据合并
分别创建数据集tsbl1为小时和数据集tsbl2为30分钟,把tsbl1和tsbl2进行合并,以小单位(30分钟)为结果输出。


> tsbl1 <- tsibble(
+   time = make_datetime(2018) + hours(0:3),
+   station = "A",
+   index = time, key = station
+ ) %>% print()
# A tsibble: 4 x 2 [1h] 
# Key:       station [1]
  time                station
                  
1 2018-01-01 00:00:00 A      
2 2018-01-01 01:00:00 A      
3 2018-01-01 02:00:00 A      
4 2018-01-01 03:00:00 A      

> tsbl2 <- tsibble(
+   time = make_datetime(2018) + minutes(seq(0, 90, by = 30)),
+   station = "B",
+   index = time, key = station
+ ) %>% print()
# A tsibble: 4 x 2 [30m] 
# Key:       station [1]
  time                station
                  
1 2018-01-01 00:00:00 B      
2 2018-01-01 00:30:00 B      
3 2018-01-01 01:00:00 B      
4 2018-01-01 01:30:00 B      

# 数据合并 
> bind_rows(tsbl1, tsbl2)
# A tsibble: 8 x 2 [30m] 
# Key:       station [2]
  time                station
                  
1 2018-01-01 00:00:00 A      
2 2018-01-01 01:00:00 A      
3 2018-01-01 02:00:00 A      
4 2018-01-01 03:00:00 A      
5 2018-01-01 00:00:00 B      
6 2018-01-01 00:30:00 B      
7 2018-01-01 01:00:00 B      
8 2018-01-01 01:30:00 B

3.4 类型转换:tibble转tsibble

把tibble转tsibble类型。


# 创建数据tibble
> x <- make_datetime(2018) + minutes(0:1)
> tbl <- tibble(  
+     time = c(x, x + minutes(15)),
+     station = rep(c("A", "B"), 2)
+ )

# 类型转换
> tbl %>% 
+     mutate(time = floor_date(time, unit = "15 mins")) %>% 
+     as_tsibble(index = time, key = station)
# A tsibble: 4 x 2 [15m] 
# Key:       station [2]
  time                station
                  
1 2018-01-01 00:00:00 A      
2 2018-01-01 00:15:00 A      
3 2018-01-01 00:00:00 B      
4 2018-01-01 00:15:00 B     

3.5 类型转换:data.frame转tsibble

data.frame转tsibble。


> df<-data.frame(
+     date=as.Date("2020-01-01")+1:1000,
+     type=sample(10,1000,replace = TRUE),
+     v1=rnorm(1000),
+     v2=runif(1000,0,1)
+ ) 

> as_tsibble(df,index = date, key = type)
# A tsibble: 1,000 x 4 [1D]
# Key:       type [10]
   date        type     v1     v2
            
 1 2020-01-20     1 -1.08  0.418 
 2 2020-02-02     1 -0.535 0.500 
 3 2020-02-05     1 -0.575 0.160 
 4 2020-02-11     1 -0.213 0.148 
 5 2020-03-02     1  0.876 0.0332
 6 2020-03-30     1  0.872 0.475 
 7 2020-04-09     1  1.05  0.539 
 8 2020-04-10     1 -1.51  0.641 
 9 2020-05-10     1 -0.630 0.632 
10 2020-06-12     1 -0.316 0.885 
# ℹ 990 more rows
# ℹ Use `print(n = ...)` to see more rows

3.6 tibble和tsibble的数据操作对比

tibble数据操作,需要定义group_by()字段。


> tbl<-tibble(df)
> tbl %>%
+   group_by(type,date) %>%
+   summarise(
+     v1_high = max(v1, na.rm = TRUE),
+     v1_low = min(v1, na.rm = TRUE)
+   )
`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by type and date.
ℹ Output is grouped by type.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(type, date))` for per-operation grouping instead.
# A tibble: 1,000 × 4
# Groups:   type [10]
    type date       v1_high  v1_low
              
 1     1 2020-01-07 -0.657  -0.657 
 2     1 2020-01-15  1.87    1.87  
 3     1 2020-01-27  0.396   0.396 
 4     1 2020-01-28 -1.29   -1.29  
 5     1 2020-01-29 -1.60   -1.60  
 6     1 2020-02-12  0.825   0.825 
 7     1 2020-02-21 -0.908  -0.908 
 8     1 2020-02-28 -1.33   -1.33  
 9     1 2020-02-29  0.0145  0.0145
10     1 2020-03-06  0.177   0.177 
# ℹ 990 more rows
# ℹ Use `print(n = ...)` to see more rows

tsibble类型,通过group_by_key(),隐式地进行分组


> df_tsbl <- as_tsibble(df, key = type)
Using `date` as index variable.
> df_tsbl %>%
+   group_by_key() %>%
+   summarise(
+     v1_high = max(v1, na.rm = TRUE),
+     v1_low = min(v1, na.rm = TRUE)
+   )
# A tsibble: 1,000 x 4 [1D]
# Key:       type [10]
    type date       v1_high  v1_low
              
 1     1 2020-01-07 -0.657  -0.657 
 2     1 2020-01-15  1.87    1.87  
 3     1 2020-01-27  0.396   0.396 
 4     1 2020-01-28 -1.29   -1.29  
 5     1 2020-01-29 -1.60   -1.60  
 6     1 2020-02-12  0.825   0.825 
 7     1 2020-02-21 -0.908  -0.908 
 8     1 2020-02-28 -1.33   -1.33  
 9     1 2020-02-29  0.0145  0.0145
10     1 2020-03-06  0.177   0.177 
# ℹ 990 more rows
# ℹ Use `print(n = ...)` to see more rows

tsibble可以方便的帮助我们的整理时间类型的数据,形成干净标准的数据集,让代码看起来更数据。

转载请注明出处:
http://blog.fens.me/r-tsibble/

用timetk进行时间序列可视化

R的极客理想系列文章,涵盖了R的思想,使用,工具,创新等的一系列要点,以我个人的学习和体验去诠释R的强大。

R语言作为统计学一门语言,一直在小众领域闪耀着光芒。直到大数据的爆发,R语言变成了一门炙手可热的数据分析的利器。随着越来越多的工程背景的人的加入,R语言的社区在迅速扩大成长。现在已不仅仅是统计领域,教育,银行,电商,互联网….都在使用R语言。

要成为有理想的极客,我们不能停留在语法上,要掌握牢固的数学,概率,统计知识,同时还要有创新精神,把R语言发挥到各个领域。让我们一起动起来吧,开始R的极客理想。

关于作者

  • 张丹,数据分析师/程序员/Quant: R,Java,Nodejs
  • blog: http://fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/r-timetk/

前言

时间序列分析,是统计分析一个重要的分支,我们日常生活的很多数据,都是时间序列数据,至少包含一个时间项和数据值。如果能很好地观察时间序列数据,对于我们理解数据能起到非常重要的作用。

timetk就是一个非常方便地观察时间序列数据的包,从多个角度让我们快速理解数据。

目录

  1. timetk包是介绍
  2. timetk的安装
  3. timetk的核心函数
  4. 可视化:基本时间序列图
  5. 可视化:时间序列分组
  6. 可视化:时间序列分箱
  7. 可视化:时间序列回归
  8. 可视化:时间序列异常检测
  9. 可视化:时间序列季节性分解
  10. 可视化:时间序列重采样

1. timetk包是介绍

timetk是R语言的一个时间序列处理工具包,可轻松实现时间序列数据的可视化、整理和特征工程,用于预测和机器学习建模。整合并扩展了包括 dplyr、stats、xts、forecast、slider、padr、recipes 和 rsample 等包中的时间序列功能。Timetk 是一个功能强大的包,属于 modeltime 生态系统的一部分,用于时间序列分析和预测。

官方网站:https://business-science.github.io/timetk/

timetk 包让用户在 R 语言中更便捷地处理时间序列对象。该工具包提供了用于检查和处理基于时间的索引、扩展用于数据挖掘和机器学习的时间特征,以及在不同时间序列类之间进行转换的功能。

主要优势如下:

  • 索引提取:从任意时间序列对象中提取时间序列索引。
  • 时间序列理解:基于时间序列索引生成特征摘要和汇总信息。
  • 构建未来时间序列:根据已有索引创建未来时间序列。
  • 基于时间的tibble类型与主流时间序列数据类型(xts、zoo、zooreg、ts)之间的相互转换:简化转换过程,并在转换为规则时间序列(如 ts)时最大程度保留基于时间的数据信息。

2. timetk的安装

timetk包的安装,安装timetk包非常简单,2条命令就可以完成,安装和加载。


# 安装
> install.packages("timetk")

# 加载
> library(timetk)

3. timetk的核心函数

使用timetk包,可以针对不同R语言时间序列类型做适配,如tk_tbl()适配tibble类型, tk_zoo()适配zoo类型,tk_xts()适配xts类型等。

timetk包中最核心的函数就是画图函数,支持11种可视化的画图效果。

  • plot_time_series 一个或多个时间序列的交互式绘图
  • plot_time_series_boxplot 交互式时间序列箱线图
  • plot_time_series_regression 可视化时间序列线性回归公式
  • plot_anomalies 可视化一个或多个时间序列的异常值
  • plot_anomalies_cleaned 可视化一个或多个时间序列的异常值(清理后)
  • plot_anomalies_decomp 可视化一个或多个时间序列的异常值(分解后)
  • plot_anomaly_diagnostics 可视化一个或多个时间序列的异常值诊断
  • plot_seasonal_diagnostics 可视化一个或多个时间序列的多种季节性特征
  • plot_stl_diagnostics 可视化一个或多个时间序列的 STL 分解特征
  • plot_acf_diagnostics 可视化一个或多个时间序列的 ACF、PACF 和 CCF
  • plot_time_series_cv_plan 可视化时间序列重采样方案

4. 可视化:基本时间序列图

以timetk包自带的数据集taylor_30_min为例,taylor_30_min数据集。为半小时电力需求,2000年6月5日星期一至2000年8月27日星期日期间英格兰和威尔士的半小时电力需求。

taylor_30_min数据集为 tibble类型, 4,032 行 × 2 列:

  • date:日期时间变量,间隔为 30 分钟
  • value:电力需求,单位为兆瓦

查看数据情况


# 查看数据前6条
> head(taylor_30_min)
# A tibble: 6 × 2
  date                value
                
1 2000-06-05 00:00:00 22262
2 2000-06-05 00:30:00 21756
3 2000-06-05 01:00:00 22247
4 2000-06-05 01:30:00 22759
5 2000-06-05 02:00:00 22549
6 2000-06-05 02:30:00 22313

# 查看数据2列统计概览
> summary(taylor_30_min)
      date                         value      
 Min.   :2000-06-05 00:00:00   Min.   :18640  
 1st Qu.:2000-06-25 23:52:30   1st Qu.:24272  
 Median :2000-07-16 23:45:00   Median :29485  
 Mean   :2000-07-16 23:45:00   Mean   :29617  
 3rd Qu.:2000-08-06 23:37:30   3rd Qu.:35132  
 Max.   :2000-08-27 23:30:00   Max.   :38777  

基于数据画图,使用基本plot()函数画图。


> plot(taylor_30_min,type="l")

使用timetk画图,并进行数据拟合


# 加载包
> library(dplyr)
> library(ggplot2)
> library(lubridate)
> library(magrittr)

# 生成基于ggplot2的静态图
> interactive <- FALSE # 画图拟合 > taylor_30_min %>% 
+   plot_time_series(date, value, 
+                    .interactive = interactive,
+                    .plotly_slider = TRUE)
Ignoring unknown labels:
• colour : "Legend"

按月度进行数据拆分拟合


# 画图拟合
> taylor_30_min %>%
+   plot_time_series(date, value, 
+                    .color_var = month(date, label = TRUE),
+                    .interactive = interactive,  
+                    .title = "Taylor's MegaWatt Data",
+                    .x_lab = "Date (30-min intervals)",
+                    .y_lab = "Energy Demand (MW)",
+                    .color_lab = "Month") +
+   scale_y_continuous(labels = scales::label_comma())

使用可交互的画图输出, interactive=TRUE。可生成基于plotly的动态交互的结果。


> taylor_30_min %>% 
+     plot_time_series(date, value, 
+                      .interactive = TRUE, 
+                      .plotly_slider = TRUE)
Ignoring unknown labels:
• colour : "Legend"

5. 可视化:时间序列分组

1. 分组展示

我们再换一组数据集m4_daily,进行演示测试。m4_daily数据集来源:M4 竞赛(2018 年 1 月 – 5 月),完整竞赛总序列数:100,000 条,本样本:4 条日度时间序列,行数:9,743,列:id、date、value。

  • id 因子型 4 条序列各自的唯一标识符
  • date 日期型 每日时间戳
  • value 数值型 对应时间戳的数值

由于这些数据来自 M4 竞赛,每条序列很可能来自不同的领域(例如微观、工业、宏观、金融、人口统计等),并可能表现出:趋势,季节性(周度、年度),异方差性,零值或间歇性数值,不同长度(日度序列的历史数据长度往往各不相同)。


# 查看数据集
> head(m4_daily)
# A tibble: 6 × 3
  id    date       value
        
1 D10   2014-07-03 2076.
2 D10   2014-07-04 2073.
3 D10   2014-07-05 2049.
4 D10   2014-07-06 2049.
5 D10   2014-07-07 2006.
6 D10   2014-07-08 2018.

# 统计概览
> summary(m4_daily)
    id            date                value      
 D10 : 674   Min.   :1978-06-23   Min.   : 1735  
 D160:4197   1st Qu.:2003-01-12   1st Qu.: 5719  
 D410: 676   Median :2006-05-14   Median : 8253  
 D500:4196   Mean   :2005-02-14   Mean   : 8280  
             3rd Qu.:2009-09-13   3rd Qu.:11219  
             Max.   :2016-05-06   Max.   :19433  

分组数据的可视化只需在将数据传入 plot_time_series() 函数之前使用 group_by() 对数据集进行分组即可。关键点如下:

  • 可以通过两种方式添加分组:使用 group_by(),或通过 … 参数添加分组。
  • 分组随后会转换为分面图。
  • .facet_ncol = 2 生成一个两列的分面图。
  • .facet_scales = “free” 允许每个图的 x 轴和 y 轴独立于其他图进行缩放。

> m4_daily %>%
+   group_by(id) %>%
+   plot_time_series(date, value,
+                    .facet_ncol = 2, .facet_scales = "free",
+                    .interactive = interactive)
Ignoring unknown labels:
• colour : "Legend"

2. 对数变换与子分组

让我们切换到一个包含多个分组的小时级数据集。我们可以展示以下内容。其意图是在分面图中展示分组,同时突出数据中的每周窗口(子分组),并对数值进行 log() 变换。

  • .value = log(value):应用对数变换
  • .color_var = week(date):将日期列转换为 lubridate::week() 的周数,颜色将应用于每个周数。

换成m4_hourly,M4 竞赛始于 2018 年 1 月 1 日,于 2018 年 5 月 31 日结束。该竞赛包含 100,000 个时间序列数据集。本数据集包含来自该竞赛的 4 个小时度时间序列样本。


> m4_hourly %>%
+   group_by(id) %>%
+   plot_time_series(date, log(value),             # Apply a Log Transformation
+                    .color_var = week(date),      # Color applied to Week transformation
+                    # Facet formatting
+                    .facet_ncol = 2,
+                    .facet_scales = "free",
+                    .interactive = interactive)

6. 可视化:时间序列分箱

plot_time_series_boxplot() 函数可用于绘制箱线图。箱线图使用聚合方式,这是由 .period 参数定义的分箱参数。

换成m4_monthly,M4 竞赛始于 2018 年 1 月 1 日,于 2018 年 5 月 31 日结束。该竞赛包含 100,000 个时间序列数据集。本数据集包含来自该竞赛的 4 个月度时间序列样本。


> m4_monthly %>%
+   group_by(id) %>%
+   plot_time_series_boxplot(
+     date, value,
+     .period      = "1 year",
+     .facet_ncol  = 2,
+     .interactive = FALSE)
Ignoring unknown labels:
• colour : "Legend"

7. 可视化:时间序列回归

使用时间序列回归图函数 plot_time_series_regression() 可用于快速评估与时间序列相关的关键特征。该函数内部会将一个公式传递给 stats::lm() 函数。通过设置 show_summary = TRUE,可以输出线性回归的摘要信息。

换成m4_monthly数据画图


> m4_monthly %>%
+   group_by(id) %>%
+   plot_time_series_regression(
+     .date_var     = date,
+     .formula      = log(value) ~ as.numeric(date) + month(date, label = TRUE),
+     .facet_ncol   = 2,
+     .interactive  = FALSE,
+     .show_summary = FALSE
+   )

8. 可视化:时间序列异常检测

使用walmart_sales_weekly数据集, 可视化一个或多个时间序列的异常值。

walmart_sales_weekly数据集,沃尔玛招聘——门店销售额预测”竞赛使用了零售数据,涉及门店与店内各个部门的组合。该竞赛于2014年2月20日开始,于2014年5月5日结束。竞赛数据包含位于不同地区的45家零售门店。数据集包含多种外部特征,包括节假日信息、气温、燃油价格和降价信息。本数据集包含门店ID为1的7个部门的样本(共7个时间序列)。

查看数据walmart_sales_weekly


# 查看数据
> head(walmart_sales_weekly)
# A tibble: 6 × 17
  id    Store  Dept Date       Weekly_Sales IsHoliday Type    Size Temperature Fuel_Price MarkDown1 MarkDown2 MarkDown3 MarkDown4
                                                          
1 1_1       1     1 2010-02-05       24924. FALSE     A     151315        42.3       2.57        NA        NA        NA        NA
2 1_1       1     1 2010-02-12       46039. TRUE      A     151315        38.5       2.55        NA        NA        NA        NA
3 1_1       1     1 2010-02-19       41596. FALSE     A     151315        39.9       2.51        NA        NA        NA        NA
4 1_1       1     1 2010-02-26       19404. FALSE     A     151315        46.6       2.56        NA        NA        NA        NA
5 1_1       1     1 2010-03-05       21828. FALSE     A     151315        46.5       2.62        NA        NA        NA        NA
6 1_1       1     1 2010-03-12       21043. FALSE     A     151315        57.8       2.67        NA        NA        NA        NA
# ℹ 3 more variables: MarkDown5 , CPI , Unemployment 

# 统计概览
> summary(walmart_sales_weekly)
       id          Store        Dept            Date             Weekly_Sales    IsHoliday           Type          
 1_1    :143   Min.   :1   Min.   : 1.00   Min.   :2010-02-05   Min.   :  6166   Mode :logical   Length:1001       
 1_3    :143   1st Qu.:1   1st Qu.: 3.00   1st Qu.:2010-10-08   1st Qu.: 28257   FALSE:931       Class :character  
 1_8    :143   Median :1   Median :13.00   Median :2011-06-17   Median : 39886   TRUE :70        Mode  :character  
 1_13   :143   Mean   :1   Mean   :35.86   Mean   :2011-06-17   Mean   : 54646                                     
 1_38   :143   3rd Qu.:1   3rd Qu.:93.00   3rd Qu.:2012-02-24   3rd Qu.: 77944                                     
 1_93   :143   Max.   :1   Max.   :95.00   Max.   :2012-10-26   Max.   :148798                                     
 (Other):143                                                                                                       
      Size         Temperature      Fuel_Price      MarkDown1         MarkDown2          MarkDown3          MarkDown4      
 Min.   :151315   Min.   :35.40   Min.   :2.514   Min.   :  410.3   Min.   :    0.50   Min.   :    0.25   Min.   :    8.0  
 1st Qu.:151315   1st Qu.:57.79   1st Qu.:2.759   1st Qu.: 4039.4   1st Qu.:   40.48   1st Qu.:    6.00   1st Qu.:  577.1  
 Median :151315   Median :69.64   Median :3.290   Median : 6154.1   Median :  144.87   Median :   25.96   Median : 1822.5  
 Mean   :151315   Mean   :68.31   Mean   :3.220   Mean   : 8090.8   Mean   : 2941.32   Mean   : 1225.40   Mean   : 3746.1  
 3rd Qu.:151315   3rd Qu.:80.49   3rd Qu.:3.594   3rd Qu.:10122.0   3rd Qu.: 1569.00   3rd Qu.:  101.64   3rd Qu.: 3750.6  
 Max.   :151315   Max.   :91.65   Max.   :3.907   Max.   :34577.1   Max.   :46011.38   Max.   :55805.51   Max.   :32403.9  
                                                  NA's   :644       NA's   :707        NA's   :651        NA's   :644      
   MarkDown5            CPI         Unemployment  
 Min.   :  554.9   Min.   :210.3   Min.   :6.573  
 1st Qu.: 3127.9   1st Qu.:211.5   1st Qu.:7.348  
 Median : 4325.2   Median :215.5   Median :7.787  
 Mean   : 5018.7   Mean   :216.0   Mean   :7.610  
 3rd Qu.: 6222.2   3rd Qu.:220.6   3rd Qu.:7.838  
 Max.   :20475.3   Max.   :223.4   Max.   :8.106  
 NA's   :644                          

> walmart_sales_weekly %>%
+   filter(id %in% c("1_1", "1_3")) %>%
+   group_by(id) %>%
+   anomalize(Date, Weekly_Sales) %>%
+   plot_anomalies(Date, .facet_ncol = 2, .ribbon_alpha = 0.25, .interactive = FALSE)
frequency = 13 observations per 1 quarter
trend = 52 observations per 1 year
frequency = 13 observations per 1 quarter
trend = 52 observations per 1 year

可视化一个或多个时间序列的异常值,自动进行数据清理后的结果。


> walmart_sales_weekly %>%
+   filter(id %in% c("1_1", "1_3")) %>%
+   group_by(id) %>%
+   anomalize(Date, Weekly_Sales, .message = FALSE) %>%
+   plot_anomalies_cleaned(Date, .facet_ncol = 2, .interactive = FALSE)

把数据按时间序列的多维度进行分解。


> walmart_sales_weekly %>%
+   filter(id %in% c("1_1", "1_3")) %>%
+   group_by(id) %>%
+   anomalize(Date, Weekly_Sales, .message = FALSE) %>%
+   plot_anomalies_decomp(Date, .interactive = FALSE)

把数据按时间序列进行异常诊断。


> walmart_sales_weekly %>%
+   group_by(id) %>%
+   plot_anomaly_diagnostics(Date, Weekly_Sales,
+                            .message = FALSE,
+                            .facet_ncol = 3,
+                            .ribbon_alpha = 0.25,
+                            .interactive = FALSE)

9. 可视化:时间序列季节性分解

季节分析,可视化一个或多个时间序列的多种季节性特征,分别按日期,星期,周,月的4个维度,生成可视化看板。


> taylor_30_min %>%
+   plot_seasonal_diagnostics(date, value, .interactive = FALSE)

STL分解,按STL分解特征拆分,基于局部加权回归的季节性趋势分解。STL分解是一种将时间序列分解为三个组成部分的统计方法:

  • 趋势分量 (Trend) 数据的长期变化方向,反映平滑的总体走势
  • 季节分量 (Seasonal) 周期性重复的模式,如每周、每月或每年的规律性波动
  • 余项分量 (Remainder) 剔除趋势和季节后剩余的随机波动或噪声

数学表达式为:
观测值 = 趋势 + 季节 + 余项


> m4_hourly %>%
+   filter(id == "H10") %>%
+   plot_stl_diagnostics(
+     date, value,
+     # Set features to return, desired frequency and trend
+     .feature_set = c("observed", "season", "trend", "remainder"),
+     .frequency   = "24 hours",
+     .trend       = "1 week",
+     .interactive = FALSE)
frequency = 24 observations per 24 hours
trend = 168 observations per 1 week

可视化ACF, PACF, and CCFs检测


> m4_hourly %>%
+   group_by(id) %>%
+   plot_acf_diagnostics(
+     date, value,               # ACF & PACF
+     .lags = "7 days",          # 7-Days of hourly lags
+     .interactive = FALSE
+   )

10. 可视化:时间序列重采样

使用FANG数据集,FANG是包含科技股(“FB”、“AMZN”、“NFLX”和“GOOG”)每日历史股价的数据集,时间跨度从2013年初到2016年底。FANG包含 4,032 行和 8 个变量的“tibble”(“整洁”数据框):symbol股票代码符号,开高低收等信息。


> head(FANG)
# A tibble: 6 × 8
  symbol date        open  high   low close    volume adjusted
                     
1 FB     2013-01-02  27.4  28.2  27.4  28    69846400     28  
2 FB     2013-01-03  27.9  28.5  27.6  27.8  63140600     27.8
3 FB     2013-01-04  28.0  28.9  27.8  28.8  72715400     28.8
4 FB     2013-01-07  28.7  29.8  28.6  29.4  83781800     29.4
5 FB     2013-01-08  29.5  29.6  28.9  29.1  45871300     29.1
6 FB     2013-01-09  29.7  30.6  29.5  30.6 104787700     30.6

# 数据概览
> summary(FANG)
    symbol               date                 open              high              low              close       
 Length:4032        Min.   :2013-01-02   Min.   :  22.99   Min.   :  23.09   Min.   :  22.67   Min.   :  22.9  
 Class :character   1st Qu.:2014-01-01   1st Qu.: 106.31   1st Qu.: 107.75   1st Qu.: 104.50   1st Qu.: 106.2  
 Mode  :character   Median :2015-01-01   Median : 335.67   Median : 340.68   Median : 331.20   Median : 335.1  
                    Mean   :2015-01-01   Mean   : 382.70   Mean   : 386.38   Mean   : 378.67   Mean   : 382.6  
                    3rd Qu.:2016-01-01   3rd Qu.: 581.47   3rd Qu.: 585.86   3rd Qu.: 576.52   3rd Qu.: 582.1  
                    Max.   :2016-12-30   Max.   :1226.80   Max.   :1228.88   Max.   :1218.60   Max.   :1220.2  
     volume             adjusted     
 Min.   :     7900   Min.   : 13.14  
 1st Qu.:  2699875   1st Qu.: 74.82  
 Median :  7285600   Median :190.75  
 Mean   : 16489749   Mean   :297.12  
 3rd Qu.: 21942350   3rd Qu.:531.37  
 Max.   :365457900   Max.   :844.36  

数据处理,重采样


> FB_tbl <- FANG %>%
+   filter(symbol == "FB") %>%
+   select(symbol, date, adjusted)

# 重采样参数配置
> resample_spec <- time_series_cv(
+   FB_tbl,
+   initial = "1 year",
+   assess  = "6 weeks",
+   skip    = "3 months",
+   lag     = "1 month",
+   cumulative  = FALSE,
+   slice_limit = 6
+ )
Using date_var: date

# 重采样输出
> resample_spec %>% tk_time_series_cv_plan()
# A tibble: 1,812 × 5
   .id    .key     symbol date       adjusted
                   
 1 Slice1 training FB     2015-11-19     106.
 2 Slice1 training FB     2015-11-20     107.
 3 Slice1 training FB     2015-11-23     107.
 4 Slice1 training FB     2015-11-24     106.
 5 Slice1 training FB     2015-11-25     105.
 6 Slice1 training FB     2015-11-27     105.
 7 Slice1 training FB     2015-11-30     104.
 8 Slice1 training FB     2015-12-01     107.
 9 Slice1 training FB     2015-12-02     106.
10 Slice1 training FB     2015-12-03     104.
# ℹ 1,802 more rows
# ℹ Use `print(n = ...)` to see more rows

# 可视化
> resample_spec %>%
+   tk_time_series_cv_plan() %>%
+   plot_time_series_cv_plan(
+     date, adjusted, # date variable and value variable
+     # Additional arguments passed to plot_time_series(),
+     .facet_ncol = 2,
+     .line_alpha = 0.5,
+     .interactive = FALSE
+   )

timetk体验起来是非常棒的,短信几行代码,就把如何观察时间序列数据看得明明白白。

转载请注明出处:
http://blog.fens.me/r-timetk/

2025 微软微软技术直通车:从机器学习向RAG 大模型的范式转变

跨界知识聚会系列文章,“知识是用来分享和传承的”,各种会议、论坛、沙龙都是分享知识的绝佳场所。我也有幸作为演讲嘉宾参加了一些国内的大型会议,向大家展示我所做的一些成果。从听众到演讲感觉是不一样的,把知识分享出来,你才能收获更多。

关于作者

  • 张丹,数据分析师/程序员/Quant: R,Java,Nodejs
  • blog: http://fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/meeting-ms-direct-20251227/

前言

随着生成式AI大模型的普及,很多项目开始从机器学习(ML)向RAG(检索增强生成)结合大模型的范式转变,确实标志着技术重心从特征工程(Feature Engineering) 转向提示词工程(Prompt Engineering)。

这一转变体现了AI从 数据驱动特征 到 知识驱动交互 的演进,通过结合MCP多工具协同,让开发者需同时掌握数据处理、检索优化与提示词设计的能力,以应对更复杂的实际应用场景。

目录

  1. 分享主题:从机器学习向RAG 大模型的范式转变
  2. 会议体验和照片分享

1. 分享主题:用 Copilot 构建机器学习模型

随着大模型的发展,越来越多的工作开始与AI深度结合,机器学习领域也正在经历一场范式转变。本次分享,我将从“机器学习”与“RAG+大模型”的对比出发,探讨两者的本质差异,以及RAG的到来为我们打开了哪些新的可能性。

分享围绕三个层面展开:

第一,应用场景的差异。 机器学习擅长处理结构化、封闭领域的任务,如分类、回归、聚类、推荐,它依赖明确的问题定义和相对静态的数据分布。而RAG+大模型面向开放域、知识密集型的生成式任务,能够动态结合外部知识,处理文本、PDF、网页等非结构化数据。

第二,技术路径的对比。 从数据处理看,机器学习以结构化数据为主,RAG则以非结构化文本为核心。从特征工程看,机器学习依赖领域专家手动设计特征或自动特征交叉;而RAG中,特征被预训练大模型的语义理解能力替代,文本通过Embedding模型自动转化为向量,提示词本身成为新的“特征”——通过角色设定、思维链、示例等方式引导模型输出。

第三,训练与评价的差异。 机器学习通常需要从头训练模型,依赖大量标注数据,评价指标是准确率、F1、AUC等客观指标,可解释性强。RAG+大模型则直接调用预训练基座模型,无需重新训练(或仅需微调),评价聚焦于检索质量,如召回率、相关性评分。

最终我们将看到:机器学习与RAG大模型并非替代关系,而是解决不同问题的不同工具。理解它们的边界与互补性,才能在新的技术范式下做出更合理的技术选型。

我主要为分三个部分进行介绍:

  • 机器学习的应用场景
  • RAG+大模型的应用场景
  • 机器学习和RAG大模型的范式对比

2. 会议体验和照片分享

活动主题:OPC:AI时代的全新创业物种。一句提示词,一个新世界。One Word , One World。

三年前,ChatGPT3.5的横空出世,正式拉开了AI全新时代的序幕。短短三年间,AI以爆发式速度席卷全球,深度改变了个人与组织的生产方式。在创业领域,AI正在重塑资源配置与能力边界,OPC(One Person Company)作为”单人成军+A”的全新创业组织形态,正迅速成为未来趋势。

报名链接:https://www.huodongxing.com/event/8838166856000

2.1 会议主题

本次会议共2个会场,我们MVP的分享在MCP分会场议程。

2.2 讲师阵容

主持人,成芳,微软MvP|资深开发者社区运营实战专家|区块链、敏捷、DevOp等社区联合发起人,核心组织者

朱一婷,微软MVP和RD,NVIDIAGTC专家讲师|光辉城市CTO

主题:《MCP在智能体开发中的应用分析:场景、架构与实践》

张丹,微软MVP,R语言实践者,青萌数海CTO,PPT下载

主题:《从机器学习向RAG+大模型的范式转变》

郝冠军,微软MVP,微软技术直通车创始人

主题:《GitHub MCP Server入门》

王瑞,微软MVP, LyShak品牌创始人|奇安信科技集团安全研究员

主题:《AI技术赋能软件安全-智能二进制攻防的创新实践》

2.3 现场照片

大合照

现场美食

主会场

纯技术沟通,高质量会议,会场满满地,座无虚席!辛苦组织者的小伙伴。

转载请注明出处:
http://blog.fens.me/meeting-ms-direct-20251227/

2026 微软微软技术直通车:用 Copilot 构建机器学习模型

跨界知识聚会系列文章,“知识是用来分享和传承的”,各种会议、论坛、沙龙都是分享知识的绝佳场所。我也有幸作为演讲嘉宾参加了一些国内的大型会议,向大家展示我所做的一些成果。从听众到演讲感觉是不一样的,把知识分享出来,你才能收获更多。

关于作者

  • 张丹,数据分析师/程序员/Quant: R,Java,Nodejs
  • blog: http://fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/meeting-ms-direct-20260321/

前言

用大模型写程序,如今已变得愈发成熟。对于标准的程序结构,大模型生成得又快又好,确实能替代大量重复的开发时间,让我们从繁琐的代码细节中解放出来。然而,数据分析有其特殊性——它不只要求程序结构正确,更考验对业务逻辑的理解、统计学基础的掌握,以及面对不确定性的解释能力。单纯的“代码跑通”并不等同于“分析准确”。本次分享,我们将把大模型置于数据分析的真实场景中,借助GitHub Copilot,一同探索它的能力边界:哪些环节它可以成为得力助手,哪些关键节点依然离不开人的判断。这不仅是一次技术演示,更是一次对AI辅助分析范式的理性审视。

目录

  1. 分享主题:用 Copilot 构建机器学习模型
  2. 会议体验和照片分享

1. 分享主题:用 Copilot 构建机器学习模型

本次分享将聚焦一个核心问题:当大模型开始帮我们写代码,数据分析的哪些环节可以放心交给AI,哪些环节依然离不开人的判断?

我们不会绑定任何具体业务场景,而是以通用数据分析为脉络,系统性地探讨大模型的能力边界。分享将围绕五个主题展开:首先,梳理机器学习建模的标准流程,明确哪些是“确定性执行”,哪些是“不确定性判断”。接着,通过现场演示,看AI如何快速完成统计概览、机器学习建模、可视化图表三类常见任务——从数据加载到模型评估,从基础图表到交互式可视化,直观感受AI的生成效率与代码质量。在此基础上,我们将对比“大模型生成代码”与“古法手写代码”的差异:效率上,AI将分钟级工作压缩至秒级;可控性上,人类仍需对关键逻辑进行审核;思维方式上,开发者的核心能力正从“记忆语法”转向“提问与判断”。最终我们将得出一个务实结论:大模型是数据分析的“超级副驾驶”,它能极大降低编程门槛、提升开发效率,但在业务理解、统计诊断、结果解读等需要深度判断的环节,依然需要人作为主导。适合所有希望借助AI提升效率、同时又保持理性思考的数据从业者与业务分析师参与。

我主要为分三个部分进行介绍:

  • 机器学习建模关键步骤
  • 让AI生成代码:统计概览
  • 让AI生成代码:机器学习
  • 让AI生成代码:可视化工具
  • 大模型生成代码 VS 古法手写代码

2. 会议体验和照片分享

活动主题:微软技术直通车(第二十九期) 之 GitHub Copilot Dev Days 2026 | Beijing —— 代码人生的智能春天

微软技术直通车第二十九期,将于2026年3月21日面向大家。本次活动将作为全球GitHub Copilot Dev Days 2026系列活动的一部分,汇聚各开发者、爱好者和本地科技社区,通过实践体验探索GitHub Copilot的强大功能。并由微软MVP为您深度剖析案例、拆解技术架构。北京部分的活动重点采用嘉宾现场技术分享的方式进行,将为您呈现一场Github Copilot智能技术盛宴——干货满满,技术多多。这是一次引领未来的旅程,让我们一同探索如何将人工智能与当前的生产力环境相结合,创造出令人惊叹的技术创新和无限可能性。

本次活动面向所有开发者开放,由微软MVP主导,技术直通车技术社区主办,设计高度实用,重点介绍现实工作流程、动手作活动以及以GitHub Copilot人工智能辅助编码为核心的工作坊。希望本次活动能够成为您在人工智能领域学术交流和技术应用中的重要一步,给您带来无尽收获。

报名链接:https://www.huodongxing.com/event/6850835727900

官方会议纪要:https://mp.weixin.qq.com/s/1J3fieLkpGsezXasQ-hyxQ

2.1 会议主题

2.2 讲师阵容

朱一婷,微软MVP和RD,NVIDIAGTC专家讲师|光辉城市CTO

主题:《GitHub Copilot:解锁新潜能,激活全场景AI伙伴》

探索GitHub Copilot从助手到智能体协作的全新进化,不再被动等待指令,可自主规划、多步执行复杂任务。多终端全场景覆盖,AI伙伴随时响应。介绍全新的后台智能体能力,以及如何基于GitHub Copilot SDK将GitHub Copilot Agent集成到应用和服务中。

张丹,微软MVP,R语言实践者,青萌数海CTO,PPT下载

主题:《用Copilot构建机器学习模型,让小白也能做数据分析》

用机器学习做数据分析,是一种普遍的智能模型建模的思路。机器学习基于结构化数据,以统计概率为算法基础,计算快,解释性好,但是上手的门槛不低,需要有统计学的知识,以及对业务的理解。利用GitHub Copilot,补齐短板,让小白也能做数据分析。

郝冠军,微软MVP,微软技术直通车创始人

主题:《使用Copilot CLI开发应用》

通过实例介绍基于Copilot CLI的应用开发。基于Copilot CLI,使用Terminal,自定义指令,自定义Prompt,MCP,定制的Agent,以及Skill等,借力AI直接在Terminal中进行软件开发。

2.3 现场照片

大合照

会议组织者:刘力科,连续10余年微软MVP,20余年微软系统工程师,Microsoft 365和Azure AI双方向微软最有价值专家,“微软技术直通车”创始人

纯技术沟通,高质量会议,会场满满地,座无虚席!辛苦组织者的小伙伴。

转载请注明出处:
http://blog.fens.me/meeting-ms-direct-20260321/

用R语言手搓马尔可夫决策过程markov

R的极客理想系列文章,涵盖了R的思想,使用,工具,创新等的一系列要点,以我个人的学习和体验去诠释R的强大。

R语言作为统计学一门语言,一直在小众领域闪耀着光芒。直到大数据的爆发,R语言变成了一门炙手可热的数据分析的利器。随着越来越多的工程背景的人的加入,R语言的社区在迅速扩大成长。现在已不仅仅是统计领域,教育,银行,电商,互联网….都在使用R语言。

要成为有理想的极客,我们不能停留在语法上,要掌握牢固的数学,概率,统计知识,同时还要有创新精神,把R语言发挥到各个领域。让我们一起动起来吧,开始R的极客理想。

关于作者:

  • 张丹,分析师/程序员/Quant: R,Java,Nodejs
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/r-markov/

前言

马尔科夫决策过程是强化学习的数学框架基础,通过状态、动作、奖励和状态转移概率的形式化描述,为智能体提供了与环境交互的理论模型。在强化学习中,MDP为价值函数、策略优化和贝尔曼方程等关键概念提供了理论基础,是理解Q学习、策略梯度等算法的重要前提,帮助智能体学习在不确定环境中做出最优决策。

新的方向,开始强化学习的学习。

目录

  1. 马尔可夫(Markov)决策过程介绍
  2. 用R语言实现马尔可夫链

1. 马尔可夫(Markov)决策过程介绍

马尔可夫决策过程(Markov decision process, MDP),是一种关于事件发生的概率预测方法。它是根据事件的目前状况来预测其将来各个时刻(或时期)变动状况的一种预测方法。马尔可夫决策过程包含一组交互对象,即智能体(agent)和环境,并定义了5个模型要素:状态(state)、动作(action)、策略(policy)、奖励(reward)和回报(return),其中策略是状态到动作的映射,回报是奖励随时间步的折现或积累。

马尔可夫决策过程于随机过程领域,随机过程与概率论中静态的随机现象不同,随机过程的研究对象是随时间演变的随机现象(例如天气随时间的变化、城市交通随时间的变化)。在随机过程中,随机现象在某时刻的取值是一个向量随机变量,用表示,所有可能的状态组成状态集合。随机现象便是状态的变化过程。在某时刻的状态St通常取决于t时刻之前的状态。

状态转移

马尔可夫过程举例,一个具有 6 个状态的马尔可夫过程的例子。

其中每个绿色圆圈(s1-s6)表示一个状态,每个状态都有一定概率(包括概率为 0)转移到其他状态。其中s6 被称为终止状态,它不再转出到其他状态。状态之间的虚线箭头表示状态的转移,箭头旁的数字表示该状态转移发生的概率。从每个状态出发转移到其他状态的概率总和为 1。例如,有s1有 90%概率到自己保持不变,s1有 10%概率转移到s2。而在s2又有 50%概率回到s1,s2有 50%概率转移到s3。

把上面的状态转移过程,用矩阵表示,即状态转移矩阵。其中,矩阵第i行j列的值,则代表从状态Si转移到Sj的概率。

给定一个马尔可夫过程,我们就可以从某个状态出发,根据它的状态转移矩阵生成一个状态序列(episode),这个步骤也被叫做采样(sampling)。例如,从S1出发,可以生成序列S1->S2->S3->S6,或S1->S1->S2->S3->S4->S5->S3->S6等。生成这些序列的概率和状态转移矩阵有关。

马尔可夫的奖励过程

在马尔可夫过程的基础上加入奖励函数r 和 折扣因子γ,就可以得到马尔可夫奖励过程(Markov reward process)。一个马尔可夫奖励过程由(S,P,r,γ)构成,各个组成元素的含义如下所示。

  • S是有限状态的集合。
  • P是状态转移矩阵。
  • r是奖励函数,某个状态s的奖励r(s) 指转移到该状态时可以获得奖励的期望。
  • γ是折扣因子(discount factor),γ的取值范围为[0,1)。引入折扣因子的理由为远期利益具有一定不确定性,有时我们更希望能够尽快获得一些奖励,所以我们需要对远期利益打一些折扣。接近 1 的γ更关注长期的累计奖励,接近 0 的γ更考虑短期奖励。

回报的计算:在一个马尔可夫奖励过程中,从第t时刻状态St开始,直到终止状态时,所有奖励的衰减之和称为回报Gt(Return)。

计算公式:

其中,Rt表示在时刻t获得的奖励。对马尔可夫过程添加奖励函数,构建成一个马尔可夫奖励过程。例如,进入状态s2可以得到-2奖励,表明我们不希望进入s2。进入s4可以获得最高的奖励10,但是进入s6之后奖励为0,并且此时序列也终止了。

手动计算G1的奖励,选取s1为起始状态,设置γ=0.5,采样到一条状态序列为s1->s2->s3->s6,就可以计算s1的回报G1=-2.5,得到 G1 = -1 + 0.5*(-2) + 0.5^2*(-2) = -2.5。

状态价值计算

在马尔可夫奖励过程中,一个状态的期望回报(即从这个状态出发的未来累积奖励的期望)被称为这个状态的价值(value)。所有状态的价值就组成了价值函数(value function),价值函数的输入为某个状态,输出为这个状态的价值。

计算公式:V(s)为价值,Gt为某个状态的回报。

在上式的最后一个等号中,一方面,即时奖励的期望正是奖励函数的输出,另一方面,等式中剩余部分可以根据从状态出发的转移概率得到。获得马尔可夫奖励过程中非常有名的贝尔曼方程(Bellman equation)。

求解得到各个状态的价值V(s),

我们现在用贝尔曼方程来进行简单的验证。例如,对于状态s4来说,当γ=0.5时,则

可以发现左右两边的值是相等的,说明我们求解得到的价值函数是满足状态为时的贝尔曼方程。

参考文章:https://hrl.boyuai.com/chapter/1/马尔可夫决策过程/

2. 用R语言实现马尔可夫链

我们用R语言模拟上面的整个马尔可夫决策的计算过程,先使用手搓的方法,后面再掉包实现。

2.1 马尔可夫过程

首先,定义状态集合s,共6种状态 s1,s2,s3,s4,s5,s6。


# 6种状态
> s <- c("s1", "s2", "s3", "s4", "s5", "s6")
> s
[1] "s1" "s2" "s3" "s4" "s5" "s6"

定义转移概率矩阵p


> p <- matrix(c(
+   0.9,0.1,  0,  0,  0,  0,
+   0.5,  0,0.5,  0,  0,  0,
+     0,  0,  0,0.6,  0,0.4,
+     0,  0,  0,  0,0.3,0.7,
+     0,0.2,0.3,0.5,  0,  0,
+     0,  0,  0,  0,  0,  1
+ ), nrow = 6, byrow = TRUE, dimnames = list(s, s))

查看矩阵结果 
> p
    s1  s2  s3  s4  s5  s6
s1 0.9 0.1 0.0 0.0 0.0 0.0
s2 0.5 0.0 0.5 0.0 0.0 0.0
s3 0.0 0.0 0.0 0.6 0.0 0.4
s4 0.0 0.0 0.0 0.0 0.3 0.7
s5 0.0 0.2 0.3 0.5 0.0 0.0
s6 0.0 0.0 0.0 0.0 0.0 1.0

画图转移概率矩阵


> library(diagram)
> plotmat(p,lwd = 1, box.lwd = 1, cex.txt = 0.8, 
+         box.size = 0.02, box.type = "circle", shadow.size = 0.001,
+         box.col = "light blue",  arr.length=0.1, arr.width=0.1,
+         self.cex = 1.5,self.shifty = -0.01,self.shiftx = 0.04, 
+         main = "Markov Chain")

模拟随机的状态转移过程。


# 模拟马尔科夫链的函数
> simulate_markov_chain <- function(transition_matrix, initial_state, n_steps, stop=NULL) {
+   states <- rownames(transition_matrix)
+   chain <- character(n_steps + 1)
+   chain[1] <- initial_state
+   
+   for (i in 1:n_steps) {
+     current_state <- chain[i]
+     next_state <- sample(states, size = 1, prob = transition_matrix[current_state, ])
+     chain[i + 1] <- next_state
+   }
+   
+   # 停止状态
+   if(!is.null(stop)){
+     chain<-chain[1:which(chain==stop)[1]]  
+   }
+   
+   return(chain)
+ }

模拟马尔科夫链的结果。


# 设置初始状态和步数
> initial_state <- "s1" # 初始状态
> n_steps <- 300        # 执行步数
> stop_state<-'s6'      # 结束状态
> markov_chain <- simulate_markov_chain(p, initial_state, n_steps,stop_state)
> markov_chain
[1] "s1" "s1" "s1" "s1" "s1" "s2" "s3" "s6"

# 再次随机模拟结果
> markov_chain <- simulate_markov_chain(p, initial_state, n_steps)
> markov_chain
[1] "s1" "s1" "s1" "s2" "s3" "s4" "s5" "s4" "s6"

可视化展示马尔科夫链过程


> library(ggplot2)
> library(dplyr)
 
> # 准备数据用于绘图
> chain_df <- data.frame(
+     step = 1:length(markov_chain),
+     state = factor(markov_chain, levels = s)
+ )
 
> # 绘制马尔科夫链路径
> ggplot(chain_df, aes(x = step, y = state, group = 1)) +
+     geom_line(color = "gray") +
+     geom_point(aes(color = state), size = 3) +
+     theme_minimal()

模拟多条马尔科夫链观察收敛性


# 5条路径
> n_simulations <- 5
> n_steps <- 50
> simulations <- list()

> for (i in 1:n_simulations) {
+   simulations[[i]] <- simulate_markov_chain(p, initial_state, n_steps)
+ }

# 准备数据用于绘图
> sim_df <- data.frame(
+   step = rep(0:n_steps, n_simulations),
+   state = unlist(simulations),
+   simulation = rep(1:n_simulations, each = n_steps + 1)
+ )

# 绘制多条马尔科夫链
> ggplot(sim_df, aes(x = step, y = state, group = simulation, color = factor(simulation))) +
+   geom_line(alpha = 0.6) +
+   geom_point(size = 1.5) +
+   labs(title = "多条马尔科夫链模拟",
+        x = "时间步", y = "状态",
+        color = "模拟编号") +
+   theme_minimal()

我们模拟了5条路径的马尔科夫链

计算马尔科夫链的稳态分布


# 计算稳态分布
> calculate_steady_state <- function(transition_matrix, tolerance = 1e-6) {
+   n_states <- nrow(transition_matrix)
+   # 初始猜测
+   pi <- rep(1/n_states, n_states)
+   
+   while (TRUE) {
+     pi_new <- pi %*% transition_matrix
+     if (max(abs(pi_new - pi)) < tolerance) break
+     pi <- pi_new
+   }
+   
+   return(as.vector(pi_new))
+ }

> steady_state <- calculate_steady_state(p)
> names(steady_state) <- s

# 稳态分布:
> steady_state
          s1           s2           s3           s4           s5           s6 
1.663447e-05 1.794239e-06 1.016132e-06 7.652676e-07 2.406675e-07 9.999795e-01

# 可视化稳态分布
> barplot(steady_state, col = c("gold", "gray", "blue"),
+         main = "马尔科夫链稳态分布",
+         ylab = "概率", xlab = "状态",
+         ylim = c(0, 0.6))

2.2 马尔可夫奖励过程

奖励过程:为每个状态定义了即时奖励,模拟了带奖励的马尔科夫链,并计算了总奖励和折现奖励。

编写带奖励的马尔科夫链函数,用于奖励计算。


# 模拟带奖励的马尔科夫链
> simulate_markov_reward <- function(transition_matrix, rewards, initial_state, n_steps, gamma = 0.9, stop=NULL) {
+     states <- rownames(transition_matrix)
+     chain <- character(n_steps + 1)
+     reward_sequence <- numeric(n_steps + 1)
+     discounted_rewards <- numeric(n_steps + 1)
+     
+     chain[1] <- initial_state
+     reward_sequence[1] <- rewards[initial_state]
+     discounted_rewards[1] <- (gamma^0) * rewards[initial_state]
+     
+     for (i in 1:n_steps) {
+         current_state <- chain[i]
+         next_state <- sample(states, size = 1, prob = transition_matrix[current_state, ])
+         chain[i + 1] <- next_state
+         reward_sequence[i + 1] <- rewards[next_state]
+         discounted_rewards[i + 1] <- (gamma^i) * rewards[next_state]
+     }
+     
+     return(list(
+         chain = chain,
+         reward = unlist(reward_sequence),
+         discounted_reward = unlist(discounted_rewards),
+         total_reward = sum(unlist(reward_sequence)),
+         total_discounted_reward = sum(unlist(discounted_rewards))
+     ))
+ }

设置模拟参数


> initial_state <- "s1"    # 初始状态
> n_steps <- 100           # 步长
> gamma <- 0.9             # 折扣因子

# 奖励规则
> rewards<-data.frame("s1"=-1,"s2"=-2,"s3"=-2,"s4"=10,"s5"=1,"s6"=0)

运行带奖励的马尔科夫链函数,


> result <- simulate_markov_reward(p, rewards, initial_state, n_steps, gamma)
 
# 总奖励
> result$total_reward
[1] -23

# 总折现奖励
> result$total_discounted_reward
[1] -9.48585606660266

查看完整和运行结果


> result
$chain          # 马尔科夫链过程
  [1] "s1" "s1" "s1" "s1" "s1" "s1" "s1" "s1" "s1" "s1" "s1"
 [12] "s1" "s1" "s1" "s2" "s1" "s1" "s1" "s1" "s1" "s1" "s1"
 [23] "s1" "s2" "s1" "s1" "s1" "s2" "s3" "s4" "s6" "s6" "s6"
 [34] "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6"
 [45] "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6"
 [56] "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6"
 [67] "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6"
 [78] "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6"
 [89] "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6" "s6"
[100] "s6" "s6"

$reward        # 各个步骤的奖励值
  [1] -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -2 -1 -1 -1
 [19] -1 -1 -1 -1 -1 -2 -1 -1 -1 -2 -2 10  0  0  0  0  0  0
 [37]  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
 [55]  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
 [73]  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
 [91]  0  0  0  0  0  0  0  0  0  0  0

$discounted_reward   # 各个步骤的折现奖励值
  [1] -1.00000000 -0.90000000 -0.81000000 -0.72900000
  [5] -0.65610000 -0.59049000 -0.53144100 -0.47829690
  [9] -0.43046721 -0.38742049 -0.34867844 -0.31381060
 [13] -0.28242954 -0.25418658 -0.45753585 -0.20589113
 [17] -0.18530202 -0.16677182 -0.15009464 -0.13508517
 [21] -0.12157665 -0.10941899 -0.09847709 -0.17725876
 [25] -0.07976644 -0.07178980 -0.06461082 -0.11629947
 [29] -0.10466953  0.47101287  0.00000000  0.00000000
 [33]  0.00000000  0.00000000  0.00000000  0.00000000
 [37]  0.00000000  0.00000000  0.00000000  0.00000000
 [41]  0.00000000  0.00000000  0.00000000  0.00000000
 [45]  0.00000000  0.00000000  0.00000000  0.00000000
 [49]  0.00000000  0.00000000  0.00000000  0.00000000
 [53]  0.00000000  0.00000000  0.00000000  0.00000000
 [57]  0.00000000  0.00000000  0.00000000  0.00000000
 [61]  0.00000000  0.00000000  0.00000000  0.00000000
 [65]  0.00000000  0.00000000  0.00000000  0.00000000
 [69]  0.00000000  0.00000000  0.00000000  0.00000000
 [73]  0.00000000  0.00000000  0.00000000  0.00000000
 [77]  0.00000000  0.00000000  0.00000000  0.00000000
 [81]  0.00000000  0.00000000  0.00000000  0.00000000
 [85]  0.00000000  0.00000000  0.00000000  0.00000000
 [89]  0.00000000  0.00000000  0.00000000  0.00000000
 [93]  0.00000000  0.00000000  0.00000000  0.00000000
 [97]  0.00000000  0.00000000  0.00000000  0.00000000
[101]  0.00000000

$total_reward   # 总奖励
[1] -23

$total_discounted_reward    # 总折现奖励
[1] -9.485856

可视化马尔科夫链状态序列,以上面带奖励的马尔科夫链生成的数据集result为基础。


> library(ggplot2)
> library(gridExtra)
 
> # 准备数据
> sim_data <- data.frame(
+     step = 0:n_steps,
+     state = factor(result$chain, levels = s),
+     reward = result$reward,
+     discounted_reward = result$discounted_reward
+ )

> # 绘制马尔科夫链状态序列状态
> p1 <- ggplot(sim_data, aes(x = step, y = state, color = state)) +
+     geom_point(size = 3) +
+     geom_line(aes(group = 1), color = "gray") +
+     labs(title = "马尔科夫链状态序列", x = "时间步", y = "状态")

# 绘制马尔科夫链状态序列奖励
> p2 <- ggplot(sim_data, aes(x = step, y = reward)) +
+     geom_col(aes(fill = state)) +
+     scale_fill_manual(values = c("Sunny" = "gold", "Cloudy" = "gray", "Rainy" = "blue")) +
+     labs(title = "即时奖励", x = "时间步", y = "奖励")

# 绘制马尔科夫链状态序列折现奖励 
> p3 <- ggplot(sim_data, aes(x = step, y = discounted_reward)) +
+     geom_col(aes(fill = state)) +
+     scale_fill_manual(values = c("Sunny" = "gold", "Cloudy" = "gray", "Rainy" = "blue")) +
+     labs(title = paste0("折现奖励 (γ=", gamma, ")"), x = "时间步", y = "折现奖励")
> 
> # 组合图形
> grid.arrange(p1, p2, p3, ncol = 1)

价值函数计算,通过解析解法,直接求解Bellman方程得到精确解。


# 计算状态价值函数
> calculate_state_values <- function(transition_matrix, rewards, gamma = 0.9, epsilon = 1e-6) {
+     n_states <- nrow(transition_matrix)
+     I <- diag(n_states)
+     
+     # 构建线性方程组: V = R + γPV → (I - γP)V = R
+     A <- I - gamma * transition_matrix
+     b <- rewards
+     
+     # 解线性方程组
+     V <- solve(A, b)
+     
+     return(V)
+ }

查看参数


> p
    s1  s2  s3  s4  s5  s6
s1 0.9 0.1 0.0 0.0 0.0 0.0
s2 0.5 0.0 0.5 0.0 0.0 0.0
s3 0.0 0.0 0.0 0.6 0.0 0.4
s4 0.0 0.0 0.0 0.0 0.3 0.7
s5 0.0 0.2 0.3 0.5 0.0 0.0
s6 0.0 0.0 0.0 0.0 0.0 1.0

> rewards
  s1 s2 s3 s4 s5 s6
1 -1 -2 -2 10  1  0

> gamma<-0.5
> gamma
[1] 0.5

计算状态价值


> state_values <- calculate_state_values(p, rewards, gamma)
> state_values
       s1        s2        s3        s4        s5        s6 
-2.019502 -2.214518  1.161428 10.538093  3.587286  0.000000 

本文初步探索了,马尔科夫决策过程中的基本概念,包括奖励过程、价值函数和策略优化。通过调整折扣因子γ、奖励值或转移矩阵来观察这些变化如何影响最优策略和价值函数。

本文的代码已上传到github:https://github.com/bsspirit/ml/blob/master/mavkov.r

本文有部分代码是通过Deepseek生成,再次惊叹Deepseek的强大。

转载请注明出处:
http://blog.fens.me/r-markov/