• Archive by category "R语言实践"
  • (Page 2)

Blog Archives

2017微软技术暨生态大会:R语言搭建多因子体系

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

关于作者

  • 张丹, 程序员R,Nodejs,Java
  • weibo:@Conan_Z
  • blog:http://blog.fens.me
  • email: bsspirit@gmail.com

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

前言

微软的技术大会,够规模,够档次,更切身地感受到,微软已经变得开放了!同时,微软股价创造了历史新高,比尔盖茨来华访问。

在本次大会上,我主要介绍的是开源技术R语言,在金融量化投资领域的应用。本次分享,仅仅从传播知识的角度,用IT人能够理解的语言,说出基金经理在做的事情

目录

  1. 我的演讲主题:R语言搭建多因子体系
  2. 会议体验和照片分享

1. 我的演讲主题:R语言搭建多因子体系

感谢微软对于MVP获得者的邀请,让MVP有展示个人能力的机会。我本次分享的主题为:R语言搭建多因子体系,主要内容来自我的一篇博文:R语言搭建多因子体系(未发布)。

分享主题的目录大纲如下:

  1. 故事开始
  2. 金融理论
  3. 多因子体系
  4. R语言建模
  5. 实例应用

本此分析主要是从金融的角度切入,介绍多因子的体系,进行选股,并通过R语言进行实现的。多因子方法选股,是目前主流的主动型基金的选股操作方法。本次分享,从一个故事引入,让没有金融背景的朋友,也能快速进入场景。用IT人能够理解的语言,说出基金经理在做的事情。

同时,我也在致力于推动R语言在中国金融领域的发展,让R可以给更多的用户使用,培养出更多的数据分析师。也希望让我们中国人的技术能够走出去到世界的舞台。希望多能认识志同道合的朋友,一起做一些事情。

2. 会议体验和照片分享

会议的主页:https://www.microsoft.com/china/techsummit/2017/

本次微软大会由百个主题组成,主要是微软的产品技术介绍。我被安装在11月03月下午的分享。让我没想到的是R语言相关的主题有3个,只不过大家的兴趣点似乎并不在数据分析或R语言。作为小众的R语言,还要有很长的路要走啊!

我的介绍和照片分享。

2.2 会议相关照片

本次的场地在 北京国际饭店会议中心,展位上也有各种新技术,和新厂商。

xbox和VR

金融解决方案

现代化工作模式

大学师弟,看起来比我压力还大。

本次大会办出世界企业的水平,希望明年有机会去微软总部西雅图,参加2018 MVP Global大会。我要赶紧准备签证去!

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

打赏作者

R语言数据科学新类型tibble

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

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

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

关于作者:

  • 张丹, 程序员R,Nodejs,Java
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

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

前言

最近正在整理用R语言进行数据处理的操作方法,发现了 RStudio 公司开发的数据科学工具包tidyverse,一下子就把我吸引了。通过2天时间,我把tidyverse项目整体的学了一遍,给我的启发是非常大的。tidyverse 重新定义了数据科学的工作路径,而且路径上每个核心节点,都定义了对应的R包。这真是一项造福数据分析行业的工程,非常值得称赞!!

tidyverse个项目,包括了一系列的子项目,其中tibble被定义为取代传统data.frame的数据类型,完全有颠覆R的数据操作的可能。跟上R语言领袖的脚步,领先进入数据科学新的时代。

目录

  1. tibble介绍
  2. tibble安装
  3. tibble包的基本使用
  4. tibble的源代码分析

1. tibble介绍

tibble是R语言中一个用来替换data.frame类型的扩展的数据框,tibble继承了data.frame,是弱类型的,同时与data.frame有相同的语法,使用起来更方便。tibble包,也是由Hadley开发的R包。

tibble对data.frame做了重新的设定:

  • tibble,不关心输入类型,可存储任意类型,包括list类型
  • tibble,没有行名设置 row.names
  • tibble,支持任意的列名
  • tibble,会自动添加列名
  • tibble,类型只能回收长度为1的输入
  • tibble,会懒加载参数,并按顺序运行
  • tibble,是tbl_df类型

tibble的项目主页:https://github.com/tidyverse/tibble

2. tibble安装

本文所使用的系统环境

  • Win10 64bit
  • R: 3.2.3 x86_64-w64-mingw32/x64 b4bit

tibble是在CRAN发布的标准库,安装起来非常简单,2条命令就可以了。


~ R
> install.packages('tibble')
> library(tibble)

RStudio官方把tibble项目,集成到了tidyverse项目中了,官方建议直接安装tidyverse项目,这样整个用来做数据科学的库都会被下载下来。


~ R
> install.packages('tidyverse')
> library(tidyverse)
#> Loading tidyverse: ggplot2
#> Loading tidyverse: tibble
#> Loading tidyverse: tidyr
#> Loading tidyverse: readr
#> Loading tidyverse: purrr
#> Loading tidyverse: dplyr
#> Conflicts with tidy packages ----------------------------------------------
#> filter(): dplyr, stats
#> lag():    dplyr, stats

tidyverse项目,是一个包括了数据科学的一个集合工具项目,用于数据提取,数据清理,数据类型定义,数据处理,数据建模,函数化编程,数据可视化,包括了下面的包。

  • ggplot2, 数据可视化
  • dplyr, 数据处理
  • tidyr, 数据清理
  • readr, 数据提取
  • purrr, 函数化编程
  • tibble, 数据类型定义

tidyverse项目的地址:https://github.com/tidyverse/tidyverse。高效的使用R语言做数据科学,请参考开源图书 R for Data Science.

3. tibble包的基本使用

对于tibble包的使用,主要需要掌握创建、数据转型、数据查看、数据操作、与data.frame的区别点。复杂的数据处理功能,是dplyr项目来完成,下一篇讲dplyr的文章再给大家介绍。

3.1 创建tibble

创建一个tibble类型的data.frame是非常简单的,语法与传统的data.frame是类似的。


# 创建一个tibble类型的data.frame
> t1<-tibble(1:10,b=LETTERS[1:10]);t1
# A tibble: 10 x 2
   `1:10`     b
    <int> <chr>
 1      1     A
 2      2     B
 3      3     C
 4      4     D
 5      5     E
 6      6     F
 7      7     G
 8      8     H
 9      9     I
10     10     J

# 创建一个data.frame
> d1<-data.frame(1:10,b=LETTERS[1:10]);d1
   X1.10 b
1      1 A
2      2 B
3      3 C
4      4 D
5      5 E
6      6 F
7      7 G
8      8 H
9      9 I
10    10 J

从上面的输出可以看到tibble类型,会在输出时多一行,用来指定每一列的类型。

tibble用缩写定义了7种类型:

  • int,代表integer
  • dbl,代表double
  • chr,代表character向量或字符串。
  • dttm,代表日期+时间(a date + a time)
  • lgl,代表逻辑判断TRUE或者FALSE
  • fctr,代表因子类型factor
  • date,代表日期dates.

查看类型,发现tbl_df继承了tbl继承是data.frame,所以tibble是data.frame的子类型。


# t1为tbl_df类型
> class(t1)
[1] "tbl_df"     "tbl"        "data.frame"

# 是data.frame类型
> class(d1)
[1] "data.frame"

让我们多角度来观察t1变量。


# 判断是不是tibble类型
> is.tibble(t1)
[1] TRUE

# 查看t1的属性
> attributes(t1)
$names
[1] "1:10" "b"   

$class
[1] "tbl_df"     "tbl"        "data.frame"

$row.names
 [1]  1  2  3  4  5  6  7  8  9 10

# 查看t1的静态结构
> str(t1)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':	10 obs. of  2 variables:
 $ 1:10: int  1 2 3 4 5 6 7 8 9 10
 $ b   : chr  "A" "B" "C" "D" ...

通过文本排列来创建一个tibble


> tribble(
+   ~colA, ~colB,
+   "a",   1,
+   "b",   2,
+   "c",   3
+ )
# A tibble: 3 x 2
   colA  colB
  <chr> <dbl>
1     a     1
2     b     2
3     c     3

通过vector创建tibble


> tibble(letters)
# A tibble: 26 x 1
   letters
     <chr>
 1       a
 2       b
 3       c
 4       d
 5       e
 6       f
 7       g
 8       h
 9       i
10       j
# ... with 16 more rows

通过data.frame创建tibble,这时就会报错了。


> tibble(data.frame(1:5))
Error: Column `data.frame(1:5)` must be a 1d atomic vector or a list

通过list创建tibble


> tibble(x = list(diag(1), diag(2)))
# A tibble: 2 x 1
              x
         <list>
1 <dbl [1 x 1]>
2 <dbl [2 x 2]>

我们看到tibble其实是存储list类型,这是data.frame做不到的。

通过一个tibble,创建另一个tibble,这时也会报错了。

> tibble(x = tibble(1, 2, 3))
Error: Column `x` must be a 1d atomic vector or a list

3.2 数据类型转换

tibble是一个新的类型,R语言中大部分的数据都是基于原有的数据类型,所以原有数据类型与tiblle类型的转换就显的非常重要了。

把一个data.frame的类型的转换为tibble类型


# 定义一个data.frame类型变量
> d1<-data.frame(1:5,b=LETTERS[1:5]);d1
  X1.5 b
1    1 A
2    2 B
3    3 C
4    4 D
5    5 E

# 把data.frame转型为tibble
> d2<-as.tibble(d1);d2
# A tibble: 5 x 2
   X1.5      b
  <int> <fctr>
1     1      A
2     2      B
3     3      C
4     4      D
5     5      E

# 再转回data.frame
> as.data.frame(d2)
  X1.5 b
1    1 A
2    2 B
3    3 C
4    4 D
5    5 E

我们可以看到tibble与data.frame的转型是非常平滑的,一个转型函数就够,不需要中间做任何的特殊处理。

把一个vector转型为tibble类型,但是不能再转回vector了。


# vector转型到tibble
> x<-as.tibble(1:5);x
# A tibble: 5 x 1
  value
  <int>
1     1
2     2
3     3
4     4
5     5

# tibble转型到vector, 不成功
> as.vector(x)
# A tibble: 5 x 1
  value
  <int>
1     1
2     2
3     3
4     4
5     5

把list转型为tibble。


# 把list转型为tibble
> df <- as.tibble(list(x = 1:500, y = runif(500), z = 500:1));df
# A tibble: 500 x 3
       x          y     z
   <int>      <dbl> <int>
 1     1 0.59141749   500
 2     2 0.61926125   499
 3     3 0.06879729   498
 4     4 0.69579561   497
 5     5 0.05087461   496
 6     6 0.63172517   495
 7     7 0.41808985   494
 8     8 0.78110219   493
 9     9 0.95279741   492
10    10 0.98930640   491
# ... with 490 more rows

# 把tibble再转为list
> str(as.list(df))
List of 3
 $ x: int [1:500] 1 2 3 4 5 6 7 8 9 10 ...
 $ y: num [1:500] 0.5914 0.6193 0.0688 0.6958 0.0509 ...
 $ z: int [1:500] 500 499 498 497 496 495 494 493 492 491 ...

tibble与list的转型也是非常平滑的,一个转型函数就够。

把matrix转型为tibble。


# 生成一个matrix
> m <- matrix(rnorm(15), ncol = 5)

# matrix转为tibble
> df <- as.tibble(m);df
# A tibble: 3 x 5
          V1         V2         V3         V4         V5
                               
1  0.8436494  2.1420238  0.2690392 -0.4752708 -0.2334994
2  1.0363340  0.8653771 -0.3200777 -1.7400856  1.2253651
3 -0.2170344 -1.1346455  0.2204718  1.2189431  0.7020156

# tibble转为matrix
> as.matrix(df)
             V1         V2         V3         V4         V5
[1,]  0.8436494  2.1420238  0.2690392 -0.4752708 -0.2334994
[2,]  1.0363340  0.8653771 -0.3200777 -1.7400856  1.2253651
[3,] -0.2170344 -1.1346455  0.2204718  1.2189431  0.7020156

从上面的转型测试可以看到,tibble类型是非常友好的,可以与data.frame, list, matrix 进行相互转型操作。tibble与vector是不能进行直接转型的,这与data.frame的行为是一致的,如果需要转型,我们可以分别取出每一列进行拼接,或转为matrix再操作。

3.3 tibble数据查询

通常我们是str()函数来观察数据的静态组成结果,在tibble包提供了一个glimpse(),可以方便我们来观察tibble和data.frame类型的数据。

比较glimpse()和str()对于data.frame的数据查看输出


> glimpse(mtcars)
Observations: 32
Variables: 11
$ mpg   21.0, 21.0, 22.8, 21.4, 18.7, 18.1, 14.3, 24.4, 22.8, 19.2, 17.8, 16.4, 17....
$ cyl   6, 6, 4, 6, 8, 6, 8, 4, 4, 6, 6, 8, 8, 8, 8, 8, 8, 4, 4, 4, 4, 8, 8, 8, 8, ...
$ disp  160.0, 160.0, 108.0, 258.0, 360.0, 225.0, 360.0, 146.7, 140.8, 167.6, 167.6...
$ hp    110, 110, 93, 110, 175, 105, 245, 62, 95, 123, 123, 180, 180, 180, 205, 215...
$ drat  3.90, 3.90, 3.85, 3.08, 3.15, 2.76, 3.21, 3.69, 3.92, 3.92, 3.92, 3.07, 3.0...
$ wt    2.620, 2.875, 2.320, 3.215, 3.440, 3.460, 3.570, 3.190, 3.150, 3.440, 3.440...
$ qsec  16.46, 17.02, 18.61, 19.44, 17.02, 20.22, 15.84, 20.00, 22.90, 18.30, 18.90...
$ vs    0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, ...
$ am    1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, ...
$ gear  4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 4, 3, 3, 3, 3, 3, ...
$ carb  4, 4, 1, 1, 2, 1, 4, 2, 2, 4, 4, 3, 3, 3, 4, 4, 4, 1, 2, 1, 1, 2, 2, 4, 2, ...

# 打印静态结构
> str(mtcars)
'data.frame':	32 obs. of  11 variables:
 $ mpg : num  21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
 $ cyl : num  6 6 4 6 8 6 8 4 4 6 ...
 $ disp: num  160 160 108 258 360 ...
 $ hp  : num  110 110 93 110 175 105 245 62 95 123 ...
 $ drat: num  3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ...
 $ wt  : num  2.62 2.88 2.32 3.21 3.44 ...
 $ qsec: num  16.5 17 18.6 19.4 17 ...
 $ vs  : num  0 0 1 1 0 1 0 1 1 1 ...
 $ am  : num  1 1 1 0 0 0 0 0 0 0 ...
 $ gear: num  4 4 4 3 3 3 3 4 4 4 ...
 $ carb: num  4 4 1 1 2 1 4 2 2 4 ...

比较glimpse()和str()对于tibble的数据查看输出。


# 新建tibble
> df <- tibble(x = rnorm(500), y = rep(LETTERS[1:25],20))

# 查看df
> glimpse(df)
Observations: 500
Variables: 2
$ x  -0.3295530, -2.0440424, 0.1444697, 0.8752439, 1.7705952, 0.5898253, 0.1991844,...
$ y  "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P"...

# 查看df静态结构
> str(df)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':	500 obs. of  2 variables:
 $ x: num  -0.33 -2.044 0.144 0.875 1.771 ...
 $ y: chr  "A" "B" "C" "D" ...

按列出数据,一层[]返回的结果还是tibbe,二层[]与$返回的结果为列组成的向量。


> df <- tibble(x = 1:3, y = 3:1)

# 按列取,返回tibble
> df[1]
# A tibble: 3 x 1
      x
  <int>
1     1
2     2
3     3

# 按列取,返回向量
> df[[1]]
[1] 1 2 3
> df$x
[1] 1 2 3

按行取数据,这时一定要用,来做分隔符


# 取第一行
> df[1,]
# A tibble: 1 x 2
      x     y
  <int> <int>
1     1     3

# 取前2行
> df[1:2,]
# A tibble: 2 x 2
      x     y
  <int> <int>
1     1     3
2     2     2

# 取第二列的2,3行
> df[2:3,2]
# A tibble: 2 x 1
      y
  <int>
1     2
2     1

3.4 tibble数据操作

增加一列。


# 创建一个tibble
> df <- tibble(x = 1:3, y = 3:1);df
# A tibble: 3 x 2
      x     y
  <int> <int>
1     1     3
2     2     2
3     3     1

# 增加一列
> add_column(df, z = -1:1, w = 0)
# A tibble: 3 x 4
      x     y     z     w
  <int> <int> <int> <dbl>
1     1     3    -1     0
2     2     2     0     0
3     3     1     1     0

增加一行,还是基于上面生成的df变量。


# 在最后,增加一行
> add_row(df, x = 99, y = 9)
# A tibble: 4 x 2
      x     y
  <dbl> <dbl>
1     1     3
2     2     2
3     3     1
4    99     9

# 插入第二行,增加一行
> add_row(df, x = 99, y = 9, .before = 2)
# A tibble: 4 x 2
      x     y
  <dbl> <dbl>
1     1     3
2    99     9
3     2     2
4     3     1

3.5 tibble与data.frame的区别

列名,可以自由定义,并且会自动补全。


> tb <- tibble(
+   `:)` = "smile",
+   ` ` = "space",
+   `2000` = "number",
+   `列名` = "hi",
+   1,1L
+ )
> tb
# A tibble: 1 x 6
   `:)`   ` ` `2000`  列名   `1`  `1L`
  <chr> <chr>  <chr> <chr> <dbl> <int>
1 smile space number    hi     1     1

数据,按顺序执行懒加载。


> a <- 1:5
> tibble(a, b = a * 2)
# A tibble: 5 x 2
      a     b
  <int> <dbl>
1     1     2
2     2     4
3     3     6
4     4     8
5     5    10

打印输出控制,tibble的打印控制被重写了,所以执行print()函数时,模型会先进行类型匹配,然后调用print.tbl()。


# 创建tiblle
> tb<-tibble(a=1:5, b = a * 2, c=NA, d='a', e=letters[1:5])

# 打印前10行,不限宽度
> print(tb,n = 10, width = Inf)
# A tibble: 5 x 5
      a     b     c     d     e
  <int> <dbl> <lgl> <chr> <chr>
1     1     2    NA     a     a
2     2     4    NA     a     b
3     3     6    NA     a     c
4     4     8    NA     a     d
5     5    10    NA     a     e

# 打印前3行,宽度30
> print(tb,n = 3, width = 30)
# A tibble: 5 x 5
      a     b     c     d
  <int> <dbl> <lgl> <chr>
1     1     2    NA     a
2     2     4    NA     a
3     3     6    NA     a
# ... with 2 more rows, and 1
#   more variables: e 

# 用print函数,打印data.frame
> df<-data.frame(tb)
> print(df)
  a  b  c d e
1 1  2 NA a a
2 2  4 NA a b
3 3  6 NA a c
4 4  8 NA a d
5 5 10 NA a e

3.7 特殊的函数

lst,创建一个list,具有tibble特性的list。 lst函数的工作原理,类似于执行[list()],这样的操作。


# 创建一个list,懒加载,顺序执行
> lst(n = 5, x = runif(n))
$n
[1] 5
$x
[1] 0.6417069 0.2674489 0.5610810 0.1771051 0.1504583

enframe,快速创建tibble。enframe提供了一个模板,只有2列name和value,快速地把2个向量匹配的tibble中,可以按行生成或按列生成。


# 按列生成
> enframe(1:3)
# A tibble: 3 x 2
   name value
  <int> <int>
1     1     1
2     2     2
3     3     3

# 按行生成
> enframe(c(a = 5, b = 7))
# A tibble: 2 x 2
   name value
  <chr> <dbl>
1     a     5
2     b     7

deframe,把tibble反向转成向量,这个函数就实现了,tibble到向量的转换。它默认把name列为索引,用value为值。


# 生成tibble
> df<-enframe(c(a = 5, b = 7));df
# A tibble: 2 x 2
   name value
  <chr> <dbl>
1     a     5
2     b     7

# 转为vector
> deframe(df)
a b 
5 7 

3.8 用于处理data.frame函数

tibble还提供了一些用于处理data.frame的函数。


# 创建data.frame
> df<-data.frame(x = 1:3, y = 3:1)

# 判断是否有叫x的列
> has_name(df,'x')
[1] TRUE

# 判断是否有行名
> has_rownames(df)
[1] FALSE

# 给df增加行名
> row.names(df)<-LETTERS[1:3];df
  x y
A 1 3
B 2 2
C 3 1

# 判断是否有行名
> has_rownames(df)
[1] TRUE

# 去掉行名
> remove_rownames(df)
  x y
1 1 3
2 2 2
3 3 1

# 把行名转换为单独的一列
> df2<-rownames_to_column(df, var = "rowname");df2
  rowname x y
1       A 1 3
2       B 2 2
3       C 3 1

# 把一列设置为行名
> column_to_rownames(df2, var = "rowname")
  x y
A 1 3
B 2 2
C 3 1

# 把行索引转换为单独的一列
> rowid_to_column(df, var = "rowid")
  rowid x y
1     1 1 3
2     2 2 2
3     3 3 1

这些data.frame的工具函数,我猜是用于data.frame到tibble的数据类型转换用的,因为tiblle是没有行名的。

4. tibble的源代码分析

对于tibble包的深入理解,我们需要分析tibble包底层的源代码,以及设计原理。我们打开github上是tibble项目,找到tibble.R的源代码,先来了解一下tibble类型的定义。

找到tibble函数的定义:


tibble <- function(...) {
  xs <- quos(..., .named = TRUE)
  as_tibble(lst_quos(xs, expand = TRUE))
}

tibble函数的构成是非常简单地,用quos()和lst_quos()函数来分割参数,再用as_tibble()函数,生成tibble类型。

我们再找到as_tibble函数的定义:


as_tibble <- function(x, ...) {
  UseMethod("as_tibble")
}

as_tibble.tbl_df <- function(x, ..., validate = FALSE) {
  if (validate) return(NextMethod())
  x
}

这个函数是一个S3类型的函数,可以S3面向对象类型的方法,来查找tibble相关的重写的函数。关于S3类型的详细介绍,请参与文章R语言基于S3的面向对象编程


> methods(generic.function=as_tibble)
[1] as_tibble.data.frame* as_tibble.default*    as_tibble.list*       as_tibble.matrix*    
[5] as_tibble.NULL*       as_tibble.poly*       as_tibble.table*      as_tibble.tbl_df*    
[9] as_tibble.ts*    

利用S3的查询函数,把整个tibble类型定义的泛型化函数都找到了。

接下来,我们继续到tbl_df的类型的定义


#' @importFrom methods setOldClass
setOldClass(c("tbl_df", "tbl", "data.frame"))

最后,这样就明确了tbl_df是类的定义,包括了属性和方法,而tibble是实例化的对象。通过对tibble函数的源代码分析,了解tibble本身的结构是怎么样的。那么再接下来,就是如何利用tibble来进行用于数据科学的数据处理过程。请继续阅读下一篇文章:R语言数据科学数据处理包dplyr。

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

打赏作者

用R语言把数据玩出花样

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

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

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

关于作者:

  • 张丹, 程序员R,Nodejs,Java
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

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

前言

作为数据分析师,每天都有大量的数据需要处理,我们会根据业务的要求做各种复杂的报表,包括了分组、排序、过滤、转置、差分、填充、移动、合并、分裂、分布、去重、找重、填充 等等的操作。

有时为了计算一个业务指标,你的SQL怎么写都不会少于10行时,另外你可能也会抱怨Excel功能不够强大,这个时候R语言绝对是不二的选择了。用R语言可以高效地、优雅地解决数据处理的问题,让R来帮你打开面向数据的思维模式。

目录

  1. 为什么要用R语言做数据处理?
  2. 数据处理基础
  3. 个性化的数据变换需求

1. 为什么要用R语言做数据处理?

R语言是非常适合做数据处理的编程语言,因为R语言的设计理念,就是面向数据的,为了解决数据问题。读完本文,相信你就能明白,什么是面向数据的设计了。

一个BI工程师每天的任务,都是非常繁琐的数据处理,如果用Java来做简直就是折磨,但是换成R语言来做,你会找到乐趣的。

当接到一个数据处理的任务后,我们可以把任务拆解为很多小的操作,包括了分组、排序、过滤、转置、差分、填充、移动、合并、分裂、分布、去重、找重等等的操作。对于实际应用的复杂的操作来说,就是把这些小的零碎的操作,拼装起来就好了。

在开始之前,我们要先了解一下R语言支持的数据类型,以及这些常用类型的特点。对于BI的数据处理的工作来说,可能有4种类型是最常用的,分别是向量、矩阵、数据框、时间序列。

  • 向量 Vector : c()
  • 矩阵 Matrix: matrix()
  • 数据框 DataFrame: data.frame()
  • 时间序列 XTS: xts()

我主要是用R语言来做量化投资,很多的时候,都是和时间序列类型数据打交道,所以我把时间序列,也定义为R语言最常用的数据处理的类型。时间序列类型,使用的是第三方包xts中定义的类型。

2. 数据处理基础

本机的系统环境:

  • Win10 64bit
  • R: version 3.2.3 64bit

2.1 创建一个数据集

创建一个向量数据集。


> x<-1:20;x
 [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20

创建一个矩阵数据集。


> m<-matrix(1:40,ncol=5);m
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    9   17   25   33
[2,]    2   10   18   26   34
[3,]    3   11   19   27   35
[4,]    4   12   20   28   36
[5,]    5   13   21   29   37
[6,]    6   14   22   30   38
[7,]    7   15   23   31   39
[8,]    8   16   24   32   40

创建一个数据框数据集。


> df<-data.frame(a=1:5,b=c('A','A','B','B','A'),c=rnorm(5));df
  a b          c
1 1 A  1.1519118
2 2 A  0.9921604
3 3 B -0.4295131
4 4 B  1.2383041
5 5 A -0.2793463

创建一个时间序列数据集,时间序列使用的第三方的xts类型。关于xts类型的详细介绍,请参考文章 可扩展的时间序列xts。


> library(xts)
> xts(1:10,order.by=as.Date('2017-01-01')+1:10)
           [,1]
2017-01-02    1
2017-01-03    2
2017-01-04    3
2017-01-05    4
2017-01-06    5
2017-01-07    6
2017-01-08    7
2017-01-09    8
2017-01-10    9
2017-01-11   10

2.2 查看数据概况

通常进行数据分析的第一步是,查看一下数据的概况信息,在R语言里可以使用summary()函数来完成。


# 查看矩阵数据集的概况
> m<-matrix(1:40,ncol=5)
> summary(m)
       V1             V2              V3              V4              V5       
 Min.   :1.00   Min.   : 9.00   Min.   :17.00   Min.   :25.00   Min.   :33.00  
 1st Qu.:2.75   1st Qu.:10.75   1st Qu.:18.75   1st Qu.:26.75   1st Qu.:34.75  
 Median :4.50   Median :12.50   Median :20.50   Median :28.50   Median :36.50  
 Mean   :4.50   Mean   :12.50   Mean   :20.50   Mean   :28.50   Mean   :36.50  
 3rd Qu.:6.25   3rd Qu.:14.25   3rd Qu.:22.25   3rd Qu.:30.25   3rd Qu.:38.25  
 Max.   :8.00   Max.   :16.00   Max.   :24.00   Max.   :32.00   Max.   :40.00  

# 查看数据框数据集的概况信息
> df<-data.frame(a=1:5,b=c('A','A','B','B','A'),c=rnorm(5))
> summary(df)
       a     b           c          
 Min.   :1   A:3   Min.   :-1.5638  
 1st Qu.:2   B:2   1st Qu.:-1.0656  
 Median :3         Median :-0.2273  
 Mean   :3         Mean   :-0.1736  
 3rd Qu.:4         3rd Qu.: 0.8320  
 Max.   :5         Max.   : 1.1565  

通过查看概况,可以帮助我们简单了解数据的一些统计特征。

2.3 数据合并

我们经常需要对于数据集,进行合并操作,让数据集满足处理的需求。对于不同类型的数据集,有不同的处理方法。

向量类型


> x<-1:5
> y<-11:15
> c(x,y)
 [1]  1  2  3  4  5 11 12 13 14 15

数据框类型的合并操作。


> df<-data.frame(a=1:5,b=c('A','A','B','B','A'),c=rnorm(5));df
  a b          c
1 1 A  1.1519118
2 2 A  0.9921604
3 3 B -0.4295131
4 4 B  1.2383041
5 5 A -0.2793463

# 合并新行
> rbind(df,c(11,'A',222))
   a b                  c
1  1 A    1.1519117540872
2  2 A  0.992160365445798
3  3 B -0.429513109491881
4  4 B   1.23830410085338
5  5 A -0.279346281854269
6 11 A                222

# 合并新列
> cbind(df,x=LETTERS[1:5])
  a b          c x
1 1 A  1.1519118 A
2 2 A  0.9921604 B
3 3 B -0.4295131 C
4 4 B  1.2383041 D
5 5 A -0.2793463 E

# 合并新列
> merge(df,LETTERS[3:5])
   a b          c y
1  1 A  1.1519118 C
2  2 A  0.9921604 C
3  3 B -0.4295131 C
4  4 B  1.2383041 C
5  5 A -0.2793463 C
6  1 A  1.1519118 D
7  2 A  0.9921604 D
8  3 B -0.4295131 D
9  4 B  1.2383041 D
10 5 A -0.2793463 D
11 1 A  1.1519118 E
12 2 A  0.9921604 E
13 3 B -0.4295131 E
14 4 B  1.2383041 E
15 5 A -0.2793463 E

2.4 累计计算

累计计算,是很常用的一种计算方法,就是把每个数值型的数据,累计求和或累计求积,从而反应数据的增长的一种特征。


# 向量x
> x<-1:10;x
 [1]  1  2  3  4  5  6  7  8  9 10

# 累计求和
> cum_sum<-cumsum(x)

# 累计求积
> cum_prod<-cumprod(x)

# 拼接成data.frame
> data.frame(x,cum_sum,cum_prod)
    x cum_sum cum_prod
1   1       1        1
2   2       3        2
3   3       6        6
4   4      10       24
5   5      15      120
6   6      21      720
7   7      28     5040
8   8      36    40320
9   9      45   362880
10 10      55  3628800

我们通常用累计计算,记录中间每一步的过程,看到的数据处理过程的特征。

2.5 差分计算

差分计算,是用向量的后一项减去前一项,所获得的差值,差分的结果反映了离散量之间的一种变化。


> x<-1:10;x
 [1]  1  2  3  4  5  6  7  8  9 10

# 计算1阶差分
> diff(x)
[1] 1 1 1 1 1 1 1 1 1

# 计算2阶差分
> diff(x,2)
[1] 2 2 2 2 2 2 2 2

# 计算2阶差分,迭代2次
> diff(x,2,2)
[1] 0 0 0 0 0 0

下面做一个稍微复杂一点的例子,通过差分来发现数据的规律。


# 对向量2次累积求和
> x <- cumsum(cumsum(1:10));x
 [1]   1   4  10  20  35  56  84 120 165 220

# 计算2阶差分
> diff(x, lag = 2)
[1]   9  16  25  36  49  64  81 100

# 计算1阶差分,迭代2次
> diff(x, differences = 2)
[1]  3  4  5  6  7  8  9 10

# 同上
> diff(diff(x))
[1]  3  4  5  6  7  8  9 10

差分其实是很常见数据的操作,但这种操作是SQL很难表达的,所以可能会被大家所忽视。

2.6 分组计算

分组是SQL中,支持的一种数据变换的操作,对应于group by的语法。

比如,我们写一个例子。创建一个数据框有a,b,c的3列,其中a,c列为数值型,b列为字符串,我们以b列分组,求出a列与c的均值。


# 创建数据框
> df<-data.frame(a=1:5,b=c('A','A','B','B','A'),c=rnorm(5));df
  a b           c
1 1 A  1.28505418
2 2 A -0.04687263
3 3 B  0.25383533
4 4 B  0.70145787
5 5 A -0.11470372

# 执行分组操作
> aggregate(. ~ b, data = df, mean)
  b        a         c
1 A 2.666667 0.3744926
2 B 3.500000 0.4776466

同样的数据集,以b列分组,对a列求和,对c列求均值。当对不同列,进行不同的操作时,我们同时也需要换其他函数来处理。


# 加载plyr库
> library(plyr)

# 执行分组操作
> ddply(df,.(b),summarise,
+       sum_a=sum(a),
+       mean_c=mean(c))
  b sum_a      mean_c
1 A     8 -0.05514761
2 B     7  0.82301276

生成的结果,就是按b列进行分组后,a列求和,c列求均值。

2.7 分裂计算

分裂计算,是把一个向量按照一列规则,拆分成多个向量的操作。

如果你想把1:10的向量,按照单双数,拆分成2个向量。


> split(1:10, 1:2)
$`1`
[1] 1 3 5 7 9

$`2`
[1]  2  4  6  8 10

另外,可以用因子类型来控制分裂。分成2步操作,第一步先分成与数据集同样长度的因子,第二步进行分裂,可以把一个大的向量拆分成多个小的向量。


# 生成因子规则
> n <- 3; size <- 5
> fat <- factor(round(n * runif(n * size)));fat
 [1] 2 3 2 1 1 0 0 2 0 1 2 3 1 1 1
Levels: 0 1 2 3

# 生成数据向量
> x <- rnorm(n * size);x
 [1]  0.68973936  0.02800216 -0.74327321  0.18879230 -1.80495863  1.46555486  0.15325334  2.17261167  0.47550953
[10] -0.70994643  0.61072635 -0.93409763 -1.25363340  0.29144624 -0.44329187

# 对向量以因子的规则进行拆分
> split(x, fat)
$`0`
[1] 1.4655549 0.1532533 0.4755095

$`1`
[1]  0.1887923 -1.8049586 -0.7099464 -1.2536334  0.2914462 -0.4432919

$`2`
[1]  0.6897394 -0.7432732  2.1726117  0.6107264

$`3`
[1]  0.02800216 -0.93409763

这种操作可以非常有效地,对数据集进行分类整理,比if..else的操作,有本质上的提升。

2.8 排序

排序是所有数据操作中,最常见一种需求了。在R语言中,你可以很方便的使用排序的功能,并不用考虑时间复杂度与空间复杂度的问题,除非你自己非要用for循环来实现。

对向量进行排序。


# 生成一个乱序的向量
> x<-sample(1:10);x
 [1]  6  2  5  1  9 10  8  3  7  4

# 对向量排序 
> x[order(x)]
 [1]  1  2  3  4  5  6  7  8  9 10

以数据框某一列进行排序。


> df<-data.frame(a=1:5,b=c('A','A','B','B','A'),c=rnorm(5));df
  a b          c
1 1 A  1.1780870
2 2 A -1.5235668
3 3 B  0.5939462
4 4 B  0.3329504
5 5 A  1.0630998

# 自定义排序函数 
> order_df<-function(df,col,decreasing=FALSE){
+     df[order(df[,c(col)],decreasing=decreasing),]
+ }

# 以c列倒序排序
> order_df(df,'c',decreasing=TRUE)
  a b          c
1 1 A  1.1780870
5 5 A  1.0630998
3 3 B  0.5939462
4 4 B  0.3329504
2 2 A -1.5235668

排序的操作,大多都是基于索引来完成的,用order()函数来生成索引,再匹配的数据的数值上面。

2.9 去重与找重

去重,是把向量中重复的元素过滤掉。找重,是把向量中重复的元素找出来。


> x<-c(3:6,5:8);x
[1] 3 4 5 6 5 6 7 8

# 去掉重复元素
> unique(x)
[1] 3 4 5 6 7 8

# 找到重复元素,索引位置
> duplicated(x)
[1] FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE

# 找到重复元素
> x[duplicated(x)]
[1] 5 6

2.10 转置

转置是一个数学名词,把行和列进行互换,一般用于对矩阵的操作。


# 创建一个3行5列的矩阵
> m<-matrix(1:15,ncol=5);m
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    4    7   10   13
[2,]    2    5    8   11   14
[3,]    3    6    9   12   15

# 转置后,变成5行3列的矩阵
> t(m)
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    4    5    6
[3,]    7    8    9
[4,]   10   11   12
[5,]   13   14   15

2.11 过滤

过滤,是对数据集按照某种规则进行筛选,去掉不符合条件的数据,保留符合条件的数据。对于NA值的操作,主要都集中在了过滤操作和填充操作中,因此就不在单独介绍NA值的处理了。


# 生成数据框
> df<-data.frame(a=c(1,NA,NA,2,NA),
+     b=c('B','A','B','B',NA),
+     c=c(rnorm(2),NA,NA,NA));df
   a    b          c
1  1    B -0.3041839
2 NA    A  0.3700188
3 NA    B         NA
4  2    B         NA
5 NA <NA>         NA

# 过滤有NA行的数据
> na.omit(df)
  a b          c
1 1 B -0.3041839

# 过滤,保留b列值为B的数据
> df[which(df$b=='B'),]
   a b          c
1  1 B -0.3041839
3 NA B         NA
4  2 B         NA

过滤,类似与SQL语句中的 WHERE 条件语句,如果你用100个以上的过滤条件,那么你的程序就会比较复杂了,最好想办法用一些巧妙的函数或者设计模式,来替换这些过滤条件。

2.12 填充

填充,是一个比较有意思的操作,你的原始数据有可能会有缺失值NA,在做各种计算时,就会出现有问题。一种方法是,你把NA值都去掉;另外一种方法是,你把NA值进行填充后再计算。那么在填充值时,就有一些讲究了。

把NA值进行填充。


# 生成数据框
> df<-data.frame(a=c(1,NA,NA,2,NA),
+      b=c('B','A','B','B',NA),
+      c=c(rnorm(2),NA,NA,NA));df
   a    b          c
1  1    B  0.2670988
2 NA    A -0.5425200
3 NA    B         NA
4  2    B         NA
5 NA <NA>         NA

# 把数据框a列的NA,用9进行填充
> na.fill(df$a,9)
[1] 1 9 9 2 9

# 把数据框中的NA,用1进行填充
> na.fill(df,1)
     a      b      c           
[1,] " 1"   "B"    " 0.2670988"
[2,] "TRUE" "A"    "-0.5425200"
[3,] "TRUE" "B"    "TRUE"      
[4,] " 2"   "B"    "TRUE"      
[5,] "TRUE" "TRUE" "TRUE"     

填充时,有时并不是用某个固定的值,而是需要基于某种规则去填充。


# 生成一个zoo类型的数据
> z <- zoo(c(2, NA, 1, 4, 5, 2), c(1, 3, 4, 6, 7, 8));z
 1  3  4  6  7  8 
 2 NA  1  4  5  2 

# 对NA进行线性插值
> na.approx(z) 
       1        3        4        6        7        8 
2.000000 1.333333 1.000000 4.000000 5.000000 2.000000 

# 对NA进行线性插值
> na.approx(z, 1:6)
  1   3   4   6   7   8 
2.0 1.5 1.0 4.0 5.0 2.0 

# 对NA进行样条插值
> na.spline(z)
        1         3         4         6         7         8 
2.0000000 0.1535948 1.0000000 4.0000000 5.0000000 2.0000000 

另外,我们可以针对NA的位置进行填充,比如用前值来填充或后值来填充。


> df
   a    b          c
1  1    B  0.2670988
2 NA    A -0.5425200
3 NA    B         NA
4  2    B         NA
5 NA <NA>         NA

# 用当前列中,NA的前值来填充
> na.locf(df)
   a b          c
1  1 B  0.2670988
2  1 A -0.5425200
3  1 B -0.5425200
4  2 B -0.5425200
5  2 B -0.5425200

# 用当前列中,NA的后值来填充
> na.locf(df,fromLast=TRUE)
   a b          c
1  1 B  0.2670988
2  2 A -0.5425200
3  2 B       <NA>
4  2 B       <NA>

2.13 计数

计数,是统计同一个值出现的次数。


# 生成30个随机数的向量
> set.seed(0)
> x<-round(rnorm(30)*5);x
 [1]  6 -2  7  6  2 -8 -5 -1  0 12  4 -4 -6 -1 -1 -2  1 -4  2 -6 -1  2  1  4  0  3  5 -3 -6  0

# 统计每个值出现的次数
> table(x)
x
-8 -6 -5 -4 -3 -2 -1  0  1  2  3  4  5  6  7 12 
 1  3  1  2  1  2  4  3  2  3  1  2  1  2  1  1 

用直方图画出。


> hist(x,xlim = c(-10,13),breaks=20)

2.14 统计分布

统计分布,是用来判断数据是否是满足某种统计学分布,如果能够验证了,那么我们就可以用到这种分布的特性来理解我们的数据集的情况了。常见的连续型的统计分布有9种,其中最常用的就是正态分布的假设。关于统计分布的详细介绍,请参考文章 常用连续型分布介绍及R语言实现

  • runif() :均匀分布
  • rnorm() :正态分布
  • rexp() :指数分布
  • rgamma() :伽马分布
  • rweibull() :韦伯分布
  • rchisq() :卡方分布
  • rf() :F分布
  • rt() :T分布
  • rbeta() :贝塔分布

统计模型定义的回归模型,就是基于正态分布的做的数据假设,如果残差满足正态分布,模型的指标再漂亮都是假的。如果你想进一步了解回归模型,请参考文章R语言解读一元线性回归模型

下面用正态分布,来举例说明一下。假设我们有一组数据,是人的身高信息,我们知道平均身高是170cm,然后我们算一下,这组身高数据是否满足正态分布。


# 生成身高数据
> set.seed(1)
> x<-round(rnorm(100,170,10))
> head(x,20)
 [1] 164 172 162 186 173 162 175 177 176 167 185 174 164 148 181 170 170 179 178 176

# 画出散点图 
> plot(x)

通过散点图来观察,发现数据是没有任何规律。接下来,我们进行正态分布的检验,Shapiro-Wilk进行正态分布检验。


> shapiro.test(x)
	Shapiro-Wilk normality test
data:  x
W = 0.99409, p-value = 0.9444

该检验原假设为H0:数据集符合正态分布,统计量W为。统计量W的最大值是1,越接近1,表示样本与正态分布越匹配。p值,如果p-value小于显著性水平α(0.05),则拒绝H0。检验结论: W接近1,p-value>0.05,不能拒绝原假设,所以数据集S符合正态分布!

同时,我们也可以用QQ图,来做正态分布的检验。


> qqnorm(x)
> qqline(x,col='red')

图中,散点均匀的分布在对角线,则说明这组数据符合正态分布。

为了,更直观地对正态分布的数据进行观察,我们可以用上文中计数操作时,使用的直方图进行观察。


> hist(x,breaks=10)

通过计数的方法,发现数据形状如钟型,中间高两边低,中间部分的数量占了95%,这就是正态的特征。当判断出,数据是符合正态分布后,那么才具备了可以使用一些的模型的基础。

2.15 数值分段

数值分段,就是把一个连续型的数值型数据,按区间分割为因子类型的离散型数据。


> x<-1:10;x
 [1]  1  2  3  4  5  6  7  8  9 10

# 把向量转换为3段因子,分别列出每个值对应因子
> cut(x, 3)
 [1] (0.991,4] (0.991,4] (0.991,4] (0.991,4] (4,7]     (4,7]     (4,7]     (7,10]    (7,10]    (7,10]   
Levels: (0.991,4] (4,7] (7,10]

# 对因子保留2位精度,并支持排序
> cut(x, 3, dig.lab = 2, ordered = TRUE)
 [1] (0.99,4] (0.99,4] (0.99,4] (0.99,4] (4,7]    (4,7]    (4,7]    (7,10]   (7,10]   (7,10]  
Levels: (0.99,4] < (4,7] < (7,10]

2.16 集合操作

集合操作,是对2个向量的操作,处理2个向量之间的数值的关系,找到包含关系、取交集、并集、差集等。


# 定义2个向量x,y
> x<-c(3:8,NA);x
[1]  3  4  5  6  7  8 NA
> y<-c(NA,6:10,NA);y
[1] NA  6  7  8  9 10 NA

# 判断x与y重复的元素的位置
> is.element(x, y)
[1] FALSE FALSE FALSE  TRUE  TRUE  TRUE  TRUE

# 判断y与x重复的元素的位置
> is.element(y, x)
[1]  TRUE  TRUE  TRUE  TRUE FALSE FALSE  TRUE

# 取并集
> union(x, y)
[1]  3  4  5  6  7  8 NA  9 10

# 取交集
> intersect(x, y)
[1]  6  7  8 NA

# 取x有,y没有元素
> setdiff(x, y)
[1] 3 4 5

# 取y有,x没有元素
> setdiff(y, x)
[1]  9 10

# 判断2个向量是否相等
> setequal(x, y)
[1] FALSE

2.17 移动窗口

移动窗口,是用来按时间周期观察数据的一种方法。移动平均,就是一种移动窗口的最常见的应用了。

在R语言的的TTR包中,支持多种的移动窗口的计算。

  • runMean(x) :移动均值
  • runSum(x) :移动求和
  • runSD(x) :移动标准差
  • runVar(x) :移动方差
  • runCor(x,y) :移动相关系数
  • runCov(x,y) :移动协方差
  • runMax(x) :移动最大值
  • runMin(x) :移动最小值
  • runMedian(x):移动中位数

下面我们用移动平均来举例说明一下,移动平均在股票交易使用的非常普遍,是最基础的趋势判断的根踪指标了。


# 生成50个随机数
> set.seed(0)
> x<-round(rnorm(50)*10);head(x,10)
 [1]  13  -3  13  13   4 -15  -9  -3   0  24

# 加载TTR包
> library(TTR)

# 计算周期为3的移动平均值
> m3<-SMA(x,3);head(m3,10)
 [1]         NA         NA  7.6666667  7.6666667 10.0000000  0.6666667 -6.6666667 -9.0000000 -4.0000000
[10]  7.0000000

# 计算周期为5的移动平均值
> m5<-SMA(x,5);head(m5,10)
 [1]   NA   NA   NA   NA  8.0  2.4  1.2 -2.0 -4.6 -0.6

当计算周期为3的移动平均值时,结果的前2个值是NA,计算的算法是


(第一个值 + 第二个值 + 第三个值)  /3 = 第三个值的移动平均值
(13      +    -3   +     13)    /3 = 7.6666667

画出图形


> plot(x,type='l')
> lines(m3,col='blue')
> lines(m5,col='red')

图中黑色线是原始数据,蓝色线是周期为3的移动平均值,红色线是周期为5的移动平均值。这3个线中,周期越大的越平滑,红色线波动是最小的,趋势性是越明显的。如果你想更深入的了解移动平均线在股票中的使用情况,请参考文章二条均线打天下

2.18 时间对齐

时间对齐,是在处理时间序列类型时常用到的操作。我们在做金融量化分析时,经常遇到时间不齐的情况,比如某支股票交易很活跃,每一秒都有交易,而其他不太活跃的股票,可能1分钟才有一笔交易,当我们要同时分析这2只股票的时候,就需要把他们的交易时间进行对齐。


# 生成数据,每秒一个值
> a<-as.POSIXct("2017-01-01 10:00:00")+0:300

# 生成数据,每59秒一个值
> b<-as.POSIXct("2017-01-01 10:00")+seq(1,300,59)

# 打印a
> head(a,10)
 [1] "2017-01-01 10:00:00 CST" "2017-01-01 10:00:01 CST" "2017-01-01 10:00:02 CST" "2017-01-01 10:00:03 CST"
 [5] "2017-01-01 10:00:04 CST" "2017-01-01 10:00:05 CST" "2017-01-01 10:00:06 CST" "2017-01-01 10:00:07 CST"
 [9] "2017-01-01 10:00:08 CST" "2017-01-01 10:00:09 CST"

# 打印b 
> head(b,10)
[1] "2017-01-01 10:00:01 CST" "2017-01-01 10:01:00 CST" "2017-01-01 10:01:59 CST" "2017-01-01 10:02:58 CST"
[5] "2017-01-01 10:03:57 CST" "2017-01-01 10:04:56 CST"

按分钟进行对齐,把时间都对齐到分钟线上。


# 按分钟对齐
> a1<-align.time(a, 1*60)
> b1<-align.time(b, 1*60)

# 查看对齐后的结果
> head(a1,10)
 [1] "2017-01-01 10:01:00 CST" "2017-01-01 10:01:00 CST" "2017-01-01 10:01:00 CST" "2017-01-01 10:01:00 CST"
 [5] "2017-01-01 10:01:00 CST" "2017-01-01 10:01:00 CST" "2017-01-01 10:01:00 CST" "2017-01-01 10:01:00 CST"
 [9] "2017-01-01 10:01:00 CST" "2017-01-01 10:01:00 CST"

> head(b1,10)
[1] "2017-01-01 10:01:00 CST" "2017-01-01 10:02:00 CST" "2017-01-01 10:02:00 CST" "2017-01-01 10:03:00 CST"
[5] "2017-01-01 10:04:00 CST" "2017-01-01 10:05:00 CST"

由于a1数据集,每分钟有多条数据,取每分钟的最后一条代表这分钟就行。


> a1[endpoints(a1,'minutes')]
[1] "2017-01-01 10:01:00 CST" "2017-01-01 10:02:00 CST" "2017-01-01 10:03:00 CST" "2017-01-01 10:04:00 CST"
[5] "2017-01-01 10:05:00 CST" "2017-01-01 10:06:00 CST"

这样子就完成了时间对齐,把不同时间的数据放到都一个维度中了。

3. 个性化的数据变换需求

我们上面已经介绍了,很多种的R语言数据处理的方法,大多都是基于R语言内置的函数或第三方包来完成的。在实际的工作中,实际还有再多的操作,完全是各性化的。

3.1 过滤数据框中,列数据全部为空的列

空值,通常都会给我们做数值计算,带来很多麻烦。有时候一列的数据都是空时,我们需要先把这一个过滤掉,再进行数据处理。

用R语言程序进行实现


# 判断哪列的值都是NA
na_col_del_df<-function(df){
  df[,which(!apply(df,2,function(x) all(is.na(x))))]  
} 

# 生成一个数据集
> df<-data.frame(a=c(1,NA,2,4),b=rep(NA,4),c=1:4);df
   a  b c
1  1 NA 1
2 NA NA 2
3  2 NA 3
4  4 NA 4

# 保留非NA的列
> na_col_del_df(df)
   a c
1  1 1
2 NA 2
3  2 3
4  4 4

3.2 替换数据框中某个区域的数据

我们想替换数据框中某个区域的数据,那么应该怎么做呢?

找到第一个数据框中,与第二个数据框中匹配的行的值作为条件,然后替换这一行的其他指定列的值。


> replace_df<-function(df1,df2,keys,vals){
+     row1<-which(apply(mapply(match,df1[,keys],df2[,keys])>0,1,all))
+     row2<-which(apply(mapply(match,df2[,keys],df1[,keys])>0,1,all))
+     df1[row1,vals]<-df2[row2,vals]
+     return(df1)
+ }

# 第一个数据框 
> df1<-data.frame(A=c(1,2,3,4),B=c('a','b','c','d'),C=c(0,4,0,4),D=1:4);df1
  A B C D
1 1 a 0 1
2 2 b 4 2
3 3 c 0 3
4 4 d 4 4

# 第二个数据框 
> df2<-data.frame(A=c(1,3),B=c('a','c'),C=c(9,9),D=rep(8,2));df2
  A B C D
1 1 a 9 8
2 3 c 9 8

# 定义匹配条件列 
> keys=c("A","B")

# 定义替换的列
> vals=c("C","D")

# 数据替换
> replace_df(df1,df2,keys,vals)
  A B C D
1 1 a 9 8
2 2 b 4 2
3 3 c 9 8
4 4 d 4 4

其实不管R语言中,各种内置的功能函数有多少,自己做在数据处理的时候,都要自己构建很多DIY的函数。

3.3 长表和宽表变换

长宽其实是一种类对于标准表格形状的描述,长表变宽表,是把一个行数很多的表,让其行数减少,列数增加,宽表变长表,是把一个表格列数减少行数增加。

长表变宽表,指定program列不动,用fun列的每一行,生成新的列,再用time列的每个值进行填充。


# 创建数据框
> df<-data.frame(
+     program=rep(c('R','Java','PHP','Python'),3),
+     fun=rep(c('fun1','fun2','fun3'),each = 4),
+     time=round(rnorm(12,10,3),2)
+ );df
   program  fun  time
1        R fun1 10.91
2     Java fun1  6.59
3      PHP fun1  9.26
4   Python fun1 11.17
5        R fun2 12.27
6     Java fun2  6.61
7      PHP fun2  7.28
8   Python fun2  9.39
9        R fun3  9.22
10    Java fun3 11.20
11     PHP fun3 13.40
12  Python fun3 10.67

# 加载reshape2库
> library(reshape2)

# 长表变宽表
> wide <- reshape(df,v.names="time",idvar="program",timevar="fun",direction = "wide");wide
  program time.fun1 time.fun2 time.fun3
1       R     10.91     12.27      9.22
2    Java      6.59      6.61     11.20
3     PHP      9.26      7.28     13.40
4  Python     11.17      9.39     10.67

接下来,进行反正操作,把宽表再转换为长表,还是使用reshape()函数。


# 宽表变为长表
> reshape(wide, direction = "long")
            program  fun  time
R.fun1            R fun1  8.31
Java.fun1      Java fun1  8.45
PHP.fun1        PHP fun1 10.49
Python.fun1  Python fun1 10.45
R.fun2            R fun2  8.72
Java.fun2      Java fun2  4.15
PHP.fun2        PHP fun2 11.47
Python.fun2  Python fun2 13.25
R.fun3            R fun3 10.10
Java.fun3      Java fun3 13.86
PHP.fun3        PHP fun3  9.96
Python.fun3  Python fun3 14.64

我们在宽表转换为长表时,可以指定想转换部分列,而不是所有列,这样就需要增加一个参数进行控制。比如,只变换time.fun2,time.fun3列到长表,而不变换time.fun1列。


> reshape(wide, direction = "long", varying =3:4)
       program time.fun1  time id
1.fun2       R      8.31  8.72  1
2.fun2    Java      8.45  4.15  2
3.fun2     PHP     10.49 11.47  3
4.fun2  Python     10.45 13.25  4
1.fun3       R      8.31 10.10  1
2.fun3    Java      8.45 13.86  2
3.fun3     PHP     10.49  9.96  3
4.fun3  Python     10.45 14.64  4

这样子的转换变形,是非常有利于我们从多角度来看数据的。

3.4 融化

融化,用于把以列进行分组的数据,转型为按行存储,对应数据表设计的概念为,属性表设计。

我们设计一下标准的二维表结构,然后按属性表的方式进行转换。


# 构建数据集
> df<-data.frame(
+   id=1:10,
+   x1=rnorm(10),
+   x2=runif(10,0,1)
+ );df
   id          x1          x2
1   1  1.78375335 0.639933473
2   2  0.26424700 0.250290845
3   3 -1.83138689 0.963861236
4   4 -1.77029220 0.451004465
5   5 -0.92149552 0.322621217
6   6  0.88499153 0.697954226
7   7  0.68905343 0.002045145
8   8  1.35269693 0.765777220
9   9  0.03673819 0.908817646
10 10  0.49682503 0.413977373

# 融合,以id列为固定列
> melt(df, id="id")
   id variable        value
1   1       x1  1.783753346
2   2       x1  0.264247003
3   3       x1 -1.831386887
4   4       x1 -1.770292202
5   5       x1 -0.921495517
6   6       x1  0.884991529
7   7       x1  0.689053430
8   8       x1  1.352696934
9   9       x1  0.036738187
10 10       x1  0.496825031
11  1       x2  0.639933473
12  2       x2  0.250290845
13  3       x2  0.963861236
14  4       x2  0.451004465
15  5       x2  0.322621217
16  6       x2  0.697954226
17  7       x2  0.002045145
18  8       x2  0.765777220
19  9       x2  0.908817646
20 10       x2  0.413977373

这个操作其实在使用ggplot2包画图时,会被经常用到。因为ggplot2做可视化时画多条曲线时,要求的输入的数据格式必须时属性表的格式。

3.5 周期分割

周期分割,是基于时间序列类型数据的处理。比如黄金的交易,你可以用1天为周期来观察,也可以用的1小时为周期来观察,也可以用1分钟为周期来看。

下面我们尝试先生成交易数据,再对交易数据进行周期的分割。本例仅为周期分割操作的示范,数据为随机生成的,请不要对数据的真实性较真。


# 加载xts包
> library(xts)

# 定义生成每日交易数据函数
> newTick<-function(date='2017-01-01',n=30){
+   newDate<-paste(date,'10:00:00')
+   xts(round(rnorm(n,10,2),2),order.by=as.POSIXct(newDate)+seq(0,(n-1)*60,60))
+ }

假设我们要生成1年的交易数据,先产生1年的日期向量,然后循环生成每日的数据。


# 设置交易日期
> dates<-as.Date("2017-01-01")+seq(0,360,1)
> head(dates)
[1] "2017-01-01" "2017-01-02" "2017-01-03" "2017-01-04" "2017-01-05" "2017-01-06"

# 生成交易数据
> xs<-lapply(dates,function(date){
+   newTick(date)
+ })

# 查看数据静态结构
> str(head(xs,2))
List of 2
 $ :An ‘xts’ object on 2017-01-01 10:00:00/2017-01-01 10:29:00 containing:
  Data: num [1:30, 1] 9.98 9.2 10.21 9.08 7.82 ...
  Indexed by objects of class: [POSIXct,POSIXt] TZ: 
  xts Attributes:  
 NULL
 $ :An ‘xts’ object on 2017-01-02 10:00:00/2017-01-02 10:29:00 containing:
  Data: num [1:30, 1] 9.41 13.15 6.07 10.12 10.37 ...
  Indexed by objects of class: [POSIXct,POSIXt] TZ: 
  xts Attributes:  
 NULL

# 转型为xts类型 
> df<-do.call(rbind.data.frame, xs)
> xdf<-as.xts(df)
> head(xdf)
                       V1
2017-01-01 10:00:00  9.98
2017-01-01 10:01:00  9.20
2017-01-01 10:02:00 10.21
2017-01-01 10:03:00  9.08
2017-01-01 10:04:00  7.82
2017-01-01 10:05:00 10.47

现在有了数据,那么我们可以对数据日期,按周期的分割了,从而生成开盘价、最高价、最低价、收盘价。这里一样会用到xts包的函数。关于xts类型的详细介绍,请参考文章 可扩展的时间序列xts


# 按日进行分割,对应高开低收的价格
> d1<-to.period(xdf,period='days');head(d1)
                    xdf.Open xdf.High xdf.Low xdf.Close
2017-01-01 10:29:00     9.98    13.74    5.35     13.34
2017-01-02 10:29:00     9.41    13.54    6.07      9.76
2017-01-03 10:29:00    12.11    13.91    7.16     10.75
2017-01-04 10:29:00    10.43    14.02    6.31     12.10
2017-01-05 10:29:00    11.51    13.97    6.67     13.97
2017-01-06 10:29:00    10.57    12.81    4.30      5.16

# 按月进行分割
> m1<-to.period(xdf,period='months');m1
                    xdf.Open xdf.High xdf.Low xdf.Close
2017-01-31 10:29:00     9.98    16.40    3.85     10.14
2017-02-28 10:29:00     8.25    16.82    4.17     11.76
2017-03-31 10:29:00    10.55    15.54    2.77      9.61
2017-04-30 10:29:00     9.40    16.13    3.84     11.77
2017-05-31 10:29:00    13.79    16.74    3.97     10.25
2017-06-30 10:29:00     9.29    16.15    4.38      7.92
2017-07-31 10:29:00     5.39    16.09    4.55      9.88
2017-08-31 10:29:00     5.76    16.34    3.27     10.86
2017-09-30 10:29:00     9.56    16.40    3.58     10.09
2017-10-31 10:29:00     8.64    15.50    3.23     10.26
2017-11-30 10:29:00     9.20    15.38    3.00     10.92
2017-12-27 10:29:00     6.99    16.22    3.87      8.87

# 按7日进行分割
> d7<-to.period(xdf,period='days',k=7);head(d7)
                    xdf.Open xdf.High xdf.Low xdf.Close
2017-01-07 10:29:00     9.98    15.54    4.30     10.42
2017-01-14 10:29:00    11.38    14.76    5.74      9.17
2017-01-21 10:29:00     9.57    16.40    3.85     11.91
2017-01-28 10:29:00    10.51    14.08    4.66     10.97
2017-02-04 10:29:00    10.43    16.69    4.53      6.09
2017-02-11 10:29:00    11.98    15.23    5.04     11.57

最后,通过可视化把不同周期的收盘价,画到一个图中。


> plot(d1$xdf.Close)
> lines(d7$xdf.Close,col='red',lwd=2)
> lines(m1$xdf.Close,col='blue',lwd=2)

从图中,可以看出切换为不同的周期,看到的形状是完全不一样的。黑色线表示以日为周期的,红色线表示以7日为周期的,蓝色线表示以月为周期的。

从本文的介绍来看,要做好数据处理是相当不容易的。你要知道数据是什么样的,业务逻辑是什么,怎么写程序以及数据变形,最后怎么进行BI展示,表达出正确的分析维度。试试R语言,忘掉程序员的思维,换成数据的思维,也许繁琐的数据处理工作会让你开心起来。

本文所介绍的数据处理的方法,及个性化的功能函数,我已经发布为一个github的开源项目,项目地址为:https://github.com/bsspirit/RTransform 欢迎大家试用,共同完善。

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

打赏作者

2017CDAS中国数据分析师行业峰会:用R语言解读股利贴现模型

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

关于作者:

  • 张丹(Conan), 程序员R,Nodejs,Java
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/meeting-cdas-20170729

前言

今年的数据分析师大会,国贸,中国大饭店,高规格,上档次!虽然只有1天,却吸引了3000+人的报名参会。11个分会场都是从数据角度来切入,包括了 大数据与生物医疗,大数据与云计算,互联网大数据,电商大数据,大数据与金融,大数据与人工智能,数据可视化与商业BI,大数据与交通旅游,大数据与智慧投资,数据库技术与实战,CDA数据分析师专场。

我的分享在 大数据与金融专场,见到了很多熟悉的朋友,同时也认识不少的新朋友。希望大家能够学到知识,并真正地落地到实际的工作中来。

我已经连续参加了3年的CDAS中国数据分析师行业峰会,祝这个数据的大会越办越好。前2年数据分析师大会会议纪要:2016数据分析师大会2015数据分析师大会

目录

  1. 我的演讲主题:用R语言解读股利贴现模型
  2. 会议体验和照片分享

1. 我的演讲主题:用R语言解读股利贴现模型

用R语言解读股利贴现模型,PPT下载,主要内容来自我的一篇博文:用R语言解读股利贴现模型(未发布)

本次分享我详细讲述了,股利贴现模型的原理和方法,并用这个模型分析招商银行(600036.SH)股票,最后用程序来实现。如果你按照我的思路去操作,相信也能很快找到被低估的股票,从而赚到靠能力可以赚到的钱。

本次分享的目录:

  1. 发现错误的定价
  2. 股利贴现模型
  3. 投资机会
  4. A股市场案例分析
  5. 用R语言实现

为了本次的分享,我花了2周的时间进行准备。希望能够给大家分享一个,实用的模型,这样听完了就可以回去动手实验了。由于分享时间比较短,而且又有不少的金融专业知识,要在30分内给大家讲一个新东西,确实很难啊,我也是挑战了一下自己。

我一直延续了一贯的演讲风格,有内容,有图片,有代码,有互动。从方法理论的思路开始,到市场特征检验,再到数学公式,R语言建模,把知识和市场操作联系起来,听完我的分享,你回去把上就可以动手实践。利用IT人的技术优势,可以真正地与实际操作结合起来,实现从IT技术到价值的转变。

2. 会议体验和照片分享

这次的大会虽然只有1天,也能看出来主办方准备充分。不得不说一句,所有的工作人员辛苦了!

“跨界互联,数聚未来”是本次会议的主题,会议主页:http://cdas.cda.cn/。以数据为题,研习技术,比拼创意,交流思想,探寻未来,打造一场大数据与大思维的盛筵。

2.1 大数据与金融场,我是第4位分享嘉宾。

  • 李峰,IBM Analytics LBS首席数据科学家,主题:人工智能助力银行审计管理
  • 于晓松,诸葛io产品VP,主题:深入金融场景的数据驱动与应用
  • 郑志勇,集思录副总裁,主题:资产配置与数据分析
  • 张丹,《R的极客理想》系列图书作者,主题:用R语言实现量化交易策略
  • 雷涛,天云大数据CEO,主题:Fintech实践:从BI到AI的演进路径
  • 赵刚,北京赛智时代信息技术咨询有限公司CEO,主题:“双创”大数据金融分析服务

我在分享的照片

其他嘉宾的照片

李峰

于晓松

郑志勇

金融会场照片

2.2 会议相关照片

大会开幕式

精彩瞬间

工作人员

最后,感谢CDAS工作人员的辛苦劳动,希望保持高水平会议越办越好!

转载请注明出处:
http://blog.fens.me/meeting-cdas-20170729

打赏作者

在AWS上部署免费的Shiny应用

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

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

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

关于作者:

  • 张丹(Conan), 程序员Java,R,PHP,Javascript
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

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

前言

无意中发现了AWS的提供免费的服务器资源,必须要大赞一下,写篇文章大大的推广。

Shiny是R语言中一个神级的应用,唯一的缺点就是不支持并发。所以,我们很多时候都是做本地Shiny应用,用于展示各种报表的效果。但有时候也需要把报表上传到互联网上,其他人也能看到。这样就需要一个互联网的解决方案,刚好AWS有了免费的服务器支持。简直是完美!!

目录

  1. Shiny是什么?
  2. 本地开发一个Shiny小应用
  3. 申请AWS免费服务器
  4. 在AWS上安装R语言环境
  5. 在AWS上安装Shiny Server
  6. 在AWS上部署自己的Shiny应用

1. Shiny是什么?

Shiny是RStudio公司开发的,一个用于R语言的Web应用程序框架,可以轻松开发交互式web应用,不需要了解HTML, CSS, JS等前端知识。

Shiny构建出应用的惊艳程度,远远超过了说明的文字。一定要学学,下面是一个Shiny小程序的截图。

Shiny的主页:http://shiny.rstudio.com/

我们安装Shiny可以直接从CRAN获取,通过一行R程序就可以安装了。


~ R
> install.packages("shiny")

2. 本地开发一个Shiny小应用

下面我们用Shiny开发一个小应用的实例,主要是为介绍Shiny的用法,包括网页的界面UI和后端程序,数据源使用R语言自带的一个数据集。

数据集是faithful,统计的是美国黄石国家公园的泉水(Old Faithful geyser) 喷发的持续时间和喷发等待时间 。

包括2列,eruptions为喷发持续时间,waiting为喷发的等待时间。


> head(faithful,20)
   eruptions waiting
1      3.600      79
2      1.800      54
3      3.333      74
4      2.283      62
5      4.533      85
6      2.883      55
7      4.700      88
8      3.600      85
9      1.950      51
10     4.350      85
11     1.833      54
12     3.917      84
13     4.200      78
14     1.750      47
15     4.700      83
16     2.167      52
17     1.750      62
18     4.800      84
19     1.600      52
20     4.250      79

开发环境所使用的系统环境

  • Win10 64bit
  • R: 3.2.3 x86_64-w64-mingw32/x64 b4bit

Shiny应用,分为定义了客户端程序ui.R,和服务器端程序server.R,这2个文件默认要求放同一个目录中。另外,我们还需要一个启动文件run.R,用于启动Shiny的应用。当然,如果在RStudio中开发,就不需要run.R的文件,直接点Shiny应用的启动按钮就行了。

开始创建项目目录和文件。


~ cd D:\workspace\dash
~ mkdir demo1
~ notepad run.R
~ cd demo1
~ notepad server.R
~ notepad ui.R

目录结构如下:


D:\workspace\dash
|--run.R
|--demo1
   |--server.R
   |--ui.R

编辑文件:run.R


library("shiny")
runApp("./demo1",host='127.0.0.1',port=3840)

编辑文件:server.R


library(shiny)

shinyServer(function(input, output) {

  # 输出到UI的main_plot
  output$main_plot <- renderPlot({
    
    # 直方图
    hist(faithful$eruptions,
         probability = TRUE,
         breaks = as.numeric(input$n_breaks),
         xlab = "持续时间",
         main = "喷发持续时间")
    
    # 是否显示individual_obs
    if (input$individual_obs) {
      rug(faithful$eruptions)
    }
    
    # 是否显示conditionalPanel
    if (input$density) {
      dens <- density(faithful$eruptions, adjust = input$bw_adjust)
      lines(dens, col = "blue")
    }
    
  })
})

编辑文件:ui.R


library(shiny)
shinyUI(bootstrapPage(
  
  headerPanel("第一个Shiny应用"),
  
  # 左侧布局
  sidebarPanel(
    
    # 下拉框
    selectInput(inputId = "n_breaks",label = "直方图中的分隔数",choices = c(10, 20, 35, 50),selected = 20),
    
    # 单选框
    checkboxInput(inputId = "individual_obs",label = strong("实际观察点"),value = FALSE),
    
    # 单选框
    checkboxInput(inputId = "density",label = strong("密度估计曲线"),value = FALSE)
  ),
  
  # 主布局
  mainPanel(
    plotOutput(outputId = "main_plot", height = "300px"),
    
    conditionalPanel(condition = "input.density == true",
                     sliderInput(inputId = "bw_adjust",label = "带宽调整", min = 0.2, max = 2, value = 1, step = 0.2)
    )
  )
))

启动Shiny应用时,本地的3840端口,就被打开了。


~ D:\workspace\dash>R -f run.r
R version 3.2.3 (2015-12-10) -- "Wooden Christmas-Tree"
Copyright (C) 2015 The R Foundation for Statistical Computing
Platform: x86_64-w64-mingw32/x64 (64-bit)
R
'license()''licence()'
R.
'contributors()'
'citation()'RR
'demo()''help()'
'help.start()'HTML
'q()'R.

> library("shiny")
> runApp("./demo1",host='127.0.0.1',port=3840)

Listening on http://127.0.0.1:3840

我们可以用浏览器,来访问本地的服务 http://127.0.0.1:3840 。

这是一个标准的Web网页,如果我们操作网页上的表单元素,对应该的数据也会发生变化了。这样我们就完成了一个本的Shiny的小应用的开发,接下来就是把这个程序部署到AWS上面了。

3. 申请AWS免费服务器

AWS是Amazon提供的一个云服务平台,利用亚马逊 AWS,软件开发人员可以轻松购买计算、存储、数据库和其他基于 Internet 的服务来支持其应用程序。开发人员能够灵活选择任何开发平台或编程环境,以便于其尝试解决问题。由于开发人员只需按使用量付费,无需前期资本支出,亚马逊 AWS 是向最终用户交付计算资源、保存的数据和其他应用程序的一种最经济划算的方式。

AWS有一个免费的套餐,让互联网用户可以免费的使用他的资源,包括了服务器,数据库,CDN,负载均衡等服务。我们为了部署自己的Shiny应用,可以申请免费的服务器资源,先跑一下,看看效果。

首先,你需要注册一个AWS账号,然后登录进去,选择地区,申请免费的服务器。目前免费开放的区域不包括中国区,我选择了一个日本东京的服务器,Ubuntu Linux 64bit。

免费的资源,有一些限制,只能1核心CPU,1G内存,最大30G存储等。当然了,有免费的资源,已经是很棒的了,而且是AWS的服务。

接下来,就是做资源配置,然后就可以启动服务器了。

大概等3分钟,服务器启动完成,然后就可以通过SSH进行访问了。

AWS为了保证安全性,建议使用秘钥访问,而不是直接的用户名和密码的方式,所以你需要创建一个秘钥对,下载一个xx.pem的私钥,然后配到Putty或XShell等用于远程登录的客户端里。

然后,就可以用XShell连接,免费创建的EC2的服务器了。在这里,如果是Ubuntu的Linux服务器,用户名需要使用ubuntu,不能直接设置root。

4. 安装R语言环境

登录后,我们就可以安装R语言的环境了。安装过程比较简单,如果你需要装指定版本的R软件,那么你需要参考文章,R的历史版本安装。如果是安装默认版本的R语言环境,直接使用是apt-get命令就是最方便的。

服务器所使用的系统环境

  • Linux Ubuntu 16.04.2 LTS 64-bit
  • R: 3.2.3 x86_64-pc-linux-gnu (64-bit)

我们先更新apt的软件源,安装必备的系统软件,包括r-base和git,以及的Shiny应用的依赖库libcurl4-openssl-dev,libxml2-dev。如果你忘了装了,后面再装也都不影响。


~ sudo apt-get update
~ sudo apt-get install r-base
~ sudo apt-get install git
~ sudo apt-get install libcurl4-openssl-dev
~ sudo apt-get install libxml2-dev

接下来,让我们安装R语言的依赖包。这里有一个小技巧,就是在R语言的环境中安装第三方R包,并切换成root用户。


~ sudo -i  # 切换成root用户
~ R        # 启动R语言环境

我们需要预装的包,主要就是shiny,当然如果你还有依赖其他的包,都可以一块安装。安装时,R会让我们选择软件源,如果用https协议的镜像列表,你需要配置一下curl进行下载。你依然可以选择用http协议的镜像列表,选61之后,会出现http的镜像列表。

我们的服务器在Tokyo,所以也选择Tokyo的镜像列表,然后开始下载R的第三方软件包。


# 安装包
> install.packages("shiny")

Installing package into ‘/usr/local/lib/R/site-library’
(as ‘lib’ is unspecified)
--- Please select a CRAN mirror for use in this session ---
HTTPS CRAN mirror 

 1: 0-Cloud [https]                 2: Algeria [https]              
 3: Australia (Canberra) [https]    4: Australia (Melbourne) [https]
 5: Australia (Perth) [https]       6: Austria [https]              
25: France (Montpellier) [https]   26: France (Paris 2) [https]     
27: Germany (Göttingen) [https]    28: Germany (Münster) [https]    
29: Greece [https]                 30: Iceland [https]              
31: India [https]                  32: Indonesia (Jakarta) [https]  
33: Ireland [https]                34: Italy (Padua) [https]        
35: Japan (Tokyo) [https]          36: Malaysia [https]             
61: (HTTP mirrors)                 

Selection: 61

HTTP CRAN mirror 

 1: 0-Cloud                       2: Algeria                    
 3: Argentina (La Plata)          4: Australia (Canberra)       
 5: Austria                       6: Belgium (Antwerp)          
 7: Belgium (Ghent)               8: Brazil (BA)                
 9: Brazil (PR)                  10: Brazil (RJ)                
11: Brazil (SP 1)                12: Brazil (SP 2)              
13: Bulgaria                     14: Canada (BC)                
37: Hungary                      38: Iceland                    
39: India                        40: Iran                       
41: Ireland                      42: Italy (Milano)             
43: Italy (Padua)                44: Italy (Palermo)            
45: Japan (Tokyo)                46: Korea (Seoul 1)            
47: Korea (Seoul 2)              48: Korea (Ulsan)                          
89: USA (TX 1)                   90: Venezuela   

Selection: 45

顺利安装完R的依赖包,接下来就是要安装Shiny Server了。Shiny Server是一个单独的软件,目前还不支持通过apt-get或R本身进行安装,需要下载安装。

5. 安装Shiny Server

在开发时,我们其实只是用到了shiny的R语言第三方包,可以在本地的开发环境,运行Shiny的程序。那么,如果把一个Shiny放到公司内网或外网给其他人用呢?这时就是需要Shiny Server了。

Shiny Server提供一个稳定的Shiny应用在线的运行环境,Shiny Server分成开源版本和企业版本。开源版本,提供了基本的Shiny功能,数据、可视化、运行环境,对于个人来说已经足够用了,而且非常友好。企业版本,提供安全和管理功能添加到基本的开源版本中。RStudio公司出品,必属精品!!

Shiny Server是一个单独的软件,我们需要下载进行安装,下载地址:https://www.rstudio.com/products/shiny/shiny-server/

在Ubuntu的环境中,我们可以通过下面的命令,进行下载和安装。


~ sudo apt-get install gdebi-core
~ wget https://download3.rstudio.org/ubuntu-12.04/x86_64/shiny-server-1.5.3.838-amd64.deb
~ sudo gdebi shiny-server-1.5.3.838-amd64.deb

Reading package lists... Done
Building dependency tree        
Reading state information... Done
Reading state information... Done

Shiny Server
 Shiny Server is a server program from RStudio, Inc. that makes Shiny applications available over the web. Shiny is a web application framework for the R statistical computation language.
Do you want to install the software package? [y/N]:y
Selecting previously unselected package shiny-server.
(Reading database ... 63176 files and directories currently installed.)
Preparing to unpack shiny-server-1.5.3.838-amd64.deb ...
Unpacking shiny-server (1.5.3.838) ...
Setting up shiny-server (1.5.3.838) ...
Creating user shiny
Adding LANG to /etc/systemd/system/shiny-server.service, setting to en_US.UTF-8
Created symlink from /etc/systemd/system/multi-user.target.wants/shiny-server.service to /etc/systemd/system/shiny-server.service.
● shiny-server.service - ShinyServer
   Loaded: loaded (/etc/systemd/system/shiny-server.service; enabled; vendor preset: enabled)
   Active: active (running) since Thu 2017-07-06 07:40:24 UTC; 6ms ago
  Process: 25322 ExecStartPost=/bin/sleep 3 (code=exited, status=0/SUCCESS)
 Main PID: 25325 (shiny-server)
    Tasks: 12
   Memory: 35.4M
      CPU: 411ms
   CGroup: /system.slice/shiny-server.service
           ├─25321 /bin/bash -c /opt/shiny-server/bin/shiny-server --pidfile=/var/run/shiny-server.pid >> /var/log...
           └─25325 /opt/shiny-server/ext/node/bin/shiny-server /opt/shiny-server/lib/main.js --pidfile=/var/run/sh...

Jul 06 07:40:21 ip-172-31-31-236 systemd[1]: Starting ShinyServer...
Jul 06 07:40:24 ip-172-31-31-236 systemd[1]: shiny-server.service: Supervising process 25325 which is not our...xits.
Jul 06 07:40:24 ip-172-31-31-236 systemd[1]: Started ShinyServer.
Hint: Some lines were ellipsized, use -l to show in full.

运行完安装的命令,默认情况Shiny Server会被直接启动起来,其中3838的端口被打开。

检查启动端口


~ netstat -nlpt
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -               
tcp        0      0 0.0.0.0:3838            0.0.0.0:*               LISTEN      -               
tcp6       0      0 :::22                   :::*                    LISTEN      -               

我们可以通过浏览器,直接基于IP和端口进行Shiny Server的访问了。

打开的页面是默认的Shiny Server的网页,如果和上面的截图一样,说明你的Shiny Server安装成功了。

提醒一下,AWS的EC2的主机,一定要配置网络访问策略,打开3838端口,允许外部访问,不然一直都是无法访问此网站的错误。

6. 部署自己的Shiny应用

还差最后一步,就是把我们自己开发的Shiny应用,部署到AWS的EC2上面。代码上传的过程,我们可以基于github来完成。

操作过程如下:

  1. 在github上面,新建一个项目,名为shiny-demo。
  2. 把本地开发的代码,上传到github的shiny-demo项目中。
  3. 在AWS的EC2上,从github的shiny-demo项目中,下载代码。
  4. 在AWS的EC2上,修改Shiny Server的配置,加载项目代码。
  5. 在AWS的EC2上,重启Shiny Server,发现错误。
  6. 在AWS的EC2上,查看日志修复错误。
  7. 在浏览器上访问,自己的Shiny应用。

6.1. 在github上面创建项目,名为shiny-demo。

github操作过程省略。项目地址 https://github.com/bsspirit/shiny-demo

6.2 把本地开发的代码,上传到github的shiny-demo项目中。

切换到本地开发的环境。


~ cd d:\workspace\dash
~ git init
~ git add .
~ git commit -m 'init'
~ git remote add origin https://github.com/bsspirit/shiny-demo.git
~ git push -u origin master  

6.3 在AWS的EC2上,从github的shiny-demo项目中,下载代码。

切换到服务器环境。


# 查看当前目录
~ pwd
/home/ubuntu

# 下载github项目
~ git clone https://github.com/bsspirit/shiny-demo
Cloning into 'shiny-demo'...
remote: Counting objects: 12, done.
remote: Compressing objects: 100% (11/11), done.
remote: Total 12 (delta 0), reused 12 (delta 0), pack-reused 0
Unpacking objects: 100% (12/12), done.
Checking connectivity... done.

# 查看项目文件
~ cd shiny-demo
~ tree
.
├── demo1
│   ├── server.R
│   └── ui.R
├── README.md
└── run.r

1 directory, 4 files

6.4 在AWS的EC2上,修改Shiny Server的配置,加载项目代码。

编辑shiny-server的配置文件shiny-server.conf。


~ sudo vi /etc/shiny-server/shiny-server.conf

# Instruct Shiny Server to run applications as the user "shiny"
run_as shiny;

# Define a server that listens on port 3838
server {
  listen 3838;

  # Define a location at the base URL
  location / {

    # Host the directory of Shiny Apps stored in this directory
    site_dir /srv/shiny-server;

    # Log all Shiny output to files in this directory
    log_dir /var/log/shiny-server;

    # When a user visits the base URL rather than a particular application,
    # an index of the applications available in this directory will be shown.
    directory_index on;
  }

  # 新增加指向github的代码位置
  location /demo1 {
    app_dir /home/ubuntu/shiny-demo/demo1;
    log_dir /var/log/shiny-server/demo1;
  }
}

增加 location /demo1 { } 的配置部分,用来把自己的Shiny的应用,在Shiny Server中进行注册。当然,对于不熟悉Shiny Server的配置的人,可以参考Shiny Server的管理员使用手册 http://docs.rstudio.com/shiny-server/

6.5 在AWS的EC2上,重启Shiny Server,发现错误。

重启Shiny Server,虽然只是重启,但经常出现错误。


~ sudo service shiny-server restart

# 对于Ubuntu 15.04+的系统,推荐用下面的命令。
~ sudo systemctl restart shiny-server

重启后,就可以在浏览器上,访问自己的Shiny应用了。

打开以后,出现了一个错误页面,这种情况太正常了。会出现各种各样的异常的情况,然后就需要我们反复进行调试了。

6.6 在AWS的EC2上,查看日志,修复错误。

主要的调试的方法,就是检查Shiny Server的日志。日志在设置上,有一个很大的坑,我也是在挠头搞了3个小时后才发现的。

由于Shiny Server为了保证性能,所以非敏感性的错误日志被设置了自动清除,每当你出现了错误,要去看日志定位问题的时候,这个日志就刚好被自动清除了。坑很大!!都开始怀疑人生了。

所以,你在调试时需要修改一个参数,保证日志不会被自动清除。

修改文件shiny-server.conf


~ sudo vi /etc/shiny-server/shiny-server.conf

run_as shiny;

access_log /var/log/shiny-server/access.log default;  # 增加记录访问
preserve_logs true;                                   # 禁止自动清除日志

# Define a server that listens on port 3838
server {
  listen 3838;

# 省略
}

再次重启Shiny Server。


~ sudo systemctl restart shiny-server

发现问题,检查日志,然后,我们对应日志的解决问题。


~ sudo cat /var/log/shiny-server/demo1/demo1-shiny-20170706-081120-42749.log 

Listening on http://127.0.0.1:42749
Warning: Error in if: argument is of length zero
Stack trace (innermost first):
    101: density.default
    100: density
     99: renderPlot [/home/ubuntu/shiny-demo/demo1/server.R#22]
     89: 
     78: plotObj
     77: origRenderFunc
     76: output$main_plot
      1: runApp

很多情况下,诡异的错误都是缺少第三方包造成的,当你程序中使用了第三包的时候,一直要记得在Shiny的服务器上面安装好。记得用root用户!!

总结一下,我们利用免费的AWS的EC2服务器资源,发布了自己的Shiny应用,是多么的开心啊!这样以后就可以大胆地去开发自己喜欢的Shiny应用了,当然不只是Shiny应用,你可以利用AWS的免费资源,做更多的事情。老司机,都明白的!!

原本是准备把一个基于赌场原型的Shiny应用放到互联网,考虑服务器位置和选型的问题,无意中发现了AWS的免费资源,这样就有了这样的一篇Shiny与AWS结合的文章。下一篇,要不要分析一下赌场的模型呢。

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

打赏作者