R語言作為統計學一門語言,一直在小眾領域閃耀著光芒。直到大數據的爆發,R語言變成了一門炙手可熱的數據分析的利器。隨著越來越多的工程背景的人的加入,R語言的社區在迅速擴大成長。現在已不僅僅是統計領域,教育,銀行,電商,互聯網…都在使用R語言。要成為有理想的極客,我們不能停留在語法上,要掌握牢固的數學,概率,統計知識,同時還要有創新精神,把R語言發揮到各個領域。讓我們一起動起來吧,開始R的極客理想。
作為數據分析師,每天都有大量的數據需要處理,我們會根據業務的要求做各種復雜的報表,包括了分組、排序、過濾、轉置、差分、填充、移動、合并、分裂、分布、去重、找重、填充 等等的操作。
有時為了計算一個業務指標,你的SQL怎么寫都不會少于10行時,另外你可能也會抱怨Excel功能不夠強大,這個時候R語言絕對是不二的選擇了。用R語言可以高效地、優雅地解決數據處理的問題,讓R來幫你打開面向數據的思維模式。
R語言是非常適合做數據處理的編程語言,因為R語言的設計理念,就是面向數據的,為了解決數據問題。讀完本文,相信你就能明白,什么是面向數據的設計了。
一個BI工程師每天的任務,都是非常繁瑣的數據處理,如果用Java來做簡直就是折磨,但是換成R語言來做,你會找到樂趣的。
當接到一個數據處理的任務后,我們可以把任務拆解為很多小的操作,包括了分組、排序、過濾、轉置、差分、填充、移動、合并、分裂、分布、去重、找重等等的操作。對于實際應用的復雜的操作來說,就是把這些小的零碎的操作,拼裝起來就好了。
在開始之前,我們要先了解一下R語言支持的數據類型,以及這些常用類型的特點。對于BI的數據處理的工作來說,可能有4種類型是常用的,分別是向量、矩陣、數據框、時間序列。
我主要是用R語言來做量化投資,很多的時候,都是和時間序列類型數據打交道,所以我把時間序列,也定義為R語言常用的數據處理的類型。時間序列類型,使用的是第三方包xts中定義的類型。
本機的系統環境:
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語言實現。
統計模型定義的回歸模型,就是基于正態分布的做的數據假設,如果殘差滿足正態分布,模型的指標再漂亮都是假的。如果你想進一步了解回歸模型,請參考文章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包中,支持多種的移動窗口的計算。
下面我們用移動平均來舉例說明一下,移動平均在股票交易使用的非常普遍,是基礎的趨勢判斷的根蹤指標了。
# 生成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"
這樣子就完成了時間對齊,把不同時間的數據放到都一個維度中了。
我們上面已經介紹了,很多種的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 15.01 2 Java fun1 7.17 3 PHP fun1 10.84 4 Python fun1 8.96 5 R fun2 10.30 6 Java fun2 9.45 7 PHP fun2 8.87 8 Python fun2 8.18 9 R fun3 6.30 10 Java fun3 9.70 11 PHP fun3 8.89 12 Python fun3 5.19 # 加載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 8.31 8.72 10.10 2 Java 8.45 4.15 13.86 3 PHP 10.49 11.47 9.96 4 Python 10.45 13.25 14.64
接下來,進行反正操作,把寬表再轉換為長表,還是使用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語言,忘掉程序員的思維,換成數據的思維,也許繁瑣的數據處理工作會讓你開心起來。
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。