1. 程式人生 > >第1章-資料探索(3)-資料預處理之R實現

第1章-資料探索(3)-資料預處理之R實現

簡介

R語言中,自身已經帶有了強大的資料處理、資料計算等方面的函式。
雖然,對於大規模的資料集合,處理過程可能會不如Python快,但是小規模的資料處理,R語言使用起來仍然會更方便。

值得注意的是,為了執行效率,我們要儘量避免在R語言中,使用迴圈函式,而是要運用向量化的處理函式,即R語言Base基礎包中,apply家族的函式—參見R語言基礎知識(四、3)

當然,記得apply家族的那麼多函式以及不同的用法是一件麻煩的事情,於是類似plyr,dplyr,tidyr,reshape2等包相繼出現,能夠運用更加簡化的函式來進行資料處理,同時又不會降低R語言的處理效率。

接下來,我們先分別介紹一下這個幾個包的用途,然後再按照

資料探索(1)中的內容進行資料處理。

正文

一,資料處理中的基本函式

參見R語言基礎知識

二,第三方包的介紹-plyr

plyr包,是用來實現Splitting, Applying and Combining Data的.
即,把原始資料經過條件拆分成資料子集—>子集經過函式邏輯的處理—>子集結果的合併的過程。

其動機在與提供超越for迴圈和內建的apply函式族的一個一攬子解決方案,高效的替代很多複雜的迴圈;

plyr包提供的*ply() 函式族如下:

在這裡插入圖片描述

一般情況下,我們做模型分析,需要輸出的格式是DataFrame,所以,此處以
adply函式來講解。

adply(.data, .margins, .fun = NULL, …, .expand = TRUE,
.progress = “none”, .inform = FALSE, .parallel = FALSE,
.paropts = NULL, .id = NA)

df = data.frame(
                lot1 = rep_len(2,6),
                lot2 = seq(6)
                )
df1=within(df,{
    lot3 = lot1 + lot2
    lot4 = lot1 * lot2
     })

> adply(df1,1,'sum')      # 不是array的自動轉成array再進行計算了
  lot1 lot2 lot4 lot3 sum
1    2    1    2    3   8
2    2    2    4    4  12
3    2    3    6    5  16
4    2    4    8    6  20
5    2    5   10    7  24
6    2    6   12    8  28

> adply(df1,2,'sum')
    X1 sum
1 lot1  12
2 lot2  21
3 lot4  42
4 lot3  33

> ldply(df1,'sum')
   .id sum
1 lot1  12
2 lot2  21
3 lot4  42
4 lot3  33

但是,雖然操作上看,plyr更容易,但是效率上還是R原生的函式快一些。

> system.time(ldply(df1,'sum'))
 使用者  系統  流逝 
0.003 0.000 0.002 
> system.time(adply(df1,1,'sum'))
 使用者  系統  流逝 
0.005 0.000 0.004 
> system.time(adply(df1,2,'sum'))
 使用者  系統  流逝 
0.005 0.000 0.005 
> system.time(lapply(df1,sum))
使用者 系統 流逝 
   0    0    0 

三,第三方包的介紹-dplyr

在R中,dplyr起到的作用如同資料庫中的SQL。主要用來實現增刪改查,以及聚合彙總的資料操作。

1, 變數篩選(列操作)

在這裡插入圖片描述

2, 條件過濾(行操作)

在這裡插入圖片描述

3,新增變數或重新命名

在這裡插入圖片描述

4,變數排序

arrange(mtcars, cyl, disp)
arrange(mtcars, desc(disp))

5,資料聚合與彙總
在這裡插入圖片描述

6,資料集合並

在這裡插入圖片描述

四,第三方包的介紹-tidyr

寬資料與長資料之間進行變換時,tidyr與reshape2都有相同的功能。
只不過除此之外,reshape2的dcast()還有部分資料聚合彙總的相關功能。

1,gather()寬資料轉為長資料
在這裡插入圖片描述
最主要的是key的生成,在…中選擇的列,變換為key,以實現寬資料轉為長資料。

例子:

stocks <- data_frame(
  time = as.Date('2009-01-01') + 0:9,
  X = rnorm(10, 0, 1),
  Y = rnorm(10, 0, 2),
  Z = rnorm(10, 0, 4)
)

gather(stocks, stock, price, -time)
stocks %>% gather(stock, price, -time)  #表示 time列保持原樣,根據stock,price 重新建立k-v對關係的變量表示

在這裡插入圖片描述

2,spread()長資料轉為寬資料

類似於reshape2包中的cast函式,與 gather正好相反
在這裡插入圖片描述

stocks <- data.frame(
  time = as.Date('2009-01-01') + 0:9,
  X = rnorm(10, 0, 1),
  Y = rnorm(10, 0, 2),
  Z = rnorm(10, 0, 4)
)
stocksm <- stocks %>% gather(stock, price, -time)
stocksm %>% spread(stock, price)

在這裡插入圖片描述

3,unite() 多列合併為一列
在這裡插入圖片描述

#直接合並
unite_(mtcars, "vs_am", c("vs","am"))
#把vs的值、am的值合,並且命名為 vs_am

在這裡插入圖片描述

4,separate() 將一列分離為多列
在這裡插入圖片描述

> df
     x
1 <NA>
2  a.b
3  a.d
4  b.c
> separate(df,x,c('a','b'))
     a    b
1 <NA> <NA>
2    a    b
3    a    d
4    b    c

五,第三方包的介紹-reshape2

reshape2 與 tidyr的功能類似,都是重塑資料變數的。
不過解決問題的思路缺失不同,reshape2首先更加melt處理原始資料,再運用cast來產生任意組合的資料模型,如下圖所示。
在這裡插入圖片描述

1,melt(data, …, na.rm = FALSE, value.name = “value”)

2,cast()

在這裡插入圖片描述

3,tidyr vs reshape2

# 測試資料
set.seed(2018)
stocks <- data.frame(time = as.Date('2009-01-01') + 1:100000,
                     X = rnorm(100000, 0, 1),
                     Y = rnorm(100000, 0, 2),
                     Z = rnorm(100000, 0, 4))

stocksm <- gather(stocks, stock, price, -time)
head(stocksm)
  time         stock  price
1 2009-01-02     X   -0.42298398
2 2009-01-03     X   -1.54987816
3 2009-01-04     X   -0.06442932
4 2009-01-05     X    0.27088135
5 2009-01-06     X    1.73528367
6 2009-01-07     X   -0.26471121

# reshape2耗時
system.time(
  dat <-melt(stocksm, id=c("time", "stock"), na.rm=FALSE) 
  ) 
使用者  系統  流逝 
0.004 0.001 0.005

system.time(
acast(dat, time ~ stock)
)
 使用者  系統  流逝 
0.460 0.065 0.535

# tidyr耗時
system.time(
spread(stocksm, stock, price)
)
使用者  系統  流逝 
0.135 0.030 0.166 

綜述,從效率上看,tidyr更高。不過reshape2靈活性更高,而且可以新增聚合函式。不過就像reshape2是reshape的簡化版一樣,tidyr也是reshape2的簡化版。tidyr用來專注的解決一件事情,沒有聚合函式混在其中。如果想對資料進行聚合,可以通過通道函式%>%來使用dplyr的聚合函式。

六,利用R語言進行資料預處理

前一節講過,預處理的四個步驟,接下來演示相應的操作:

資料清洗:SINCE原則處理資料
資料整合:R自帶函式與第三方包plyr,dplyr等
資料變換:R自帶函式與第三方包plyr,dplyr等
資料規約:會放到第五章進行講解

1,資料清洗

  • Simple原則: 發現重複與冗餘
    在這裡插入圖片描述
# 建立冗餘資料
dat <- data_frame(x=rep(1,6),
                  y=seq(1,6)
                  )
dat1 <- rbind(dat,dat)[c(-1,-5,-6,-8),]
dat1
# 檢視重複資料條數
sum(duplicated(dat1))
2
# 檢視重複的資料是哪些
dat1[duplicated(dat1),]
    x     y
    1     3
    1     4

對於冗餘,與資料規約的步驟是基本一致的,會合併到資料規約的步驟來計算。

  • Integral原則:處理缺失處理

處理缺失值的具體理論方法,參見第六章,此處僅介紹一下,如何發現以及填充缺失值。

在這裡插入圖片描述
檢視缺失值

# 建立缺失資料
dat <- data.frame(x=rep(1,6),
                  y=seq(1,6)
                  )
dat[c(3,6),c(1,2)] <- NA

# 有多少個缺失值
sum(is.na(dat))
4

統一填充缺失值

# 將缺失值統一填充為0

# 用賦值的方法
dat[is.na(dat)] <- 0

# 用替換的方法
replace(dat,is.na(dat),0)

# 用dplyr中的方法
dplyr::replace_na(dat,list(x=0,y=0))
mutate_at(dat,vars(x,y),funs(ifelse(is.na(.), 0, .)))  # 等價於
mutate_all(dat,funs(ifelse(is.na(.), 0, .))) 

對於不同的列,動態指定不同列的填充值

# 使用迴圈與自定義函式
sapply(dat,function(x) ifelse(is.na(x),x[is.na(x)] <- mean(x,na.rm = TRUE),x))

剔除缺失值

# 方法一
na.omit(dat)

# 方法二
dat[complete.cases(dat),]
  • Normal原則:統一格式並標準化
    統一格式,基本就是修改並賦值的過程,此處不再討論。

對於數值型變數,我們有兩種方式,標準化:

歸一化公式
X n e w = x x m i n x m a x x m i n X_{new} = \frac{x-x_{min}}{x_{max}-x_{min}}

# 運用歸一化化公式,自己算值
(dat-min(dat)) / (max(dat)-min(dat))

標準化公式
X n e w = x u σ X_{new} = \frac{x-u}{σ}

# center—表示是否進行中心化,所有值減去均值
# scale—表示是否進行標準化,所有值除標準差

dat<-c(1, 2, 3, 4, 5, 6, 7)
# 使用scale標準化
scale(dat,center = TRUE, scale = TRUE)
         [,1]
[1,] -1.3887301
[2,] -0.9258201
[3,] -0.4629100
[4,]  0.0000000
[5,]  0.4629100
[6,]  0.9258201
[7,]  1.3887301
attr(,"scaled:center")
[1] 4
attr(,"scaled:scale")
[1] 2.160247

# 運用標準化公式,自己算值
(dat-mean(dat))/sd(dat)
-1.3887301 -0.9258201 -0.4629100  0.0000000  0.4629100  0.9258201  1.3887301

#結果一致
  • Consistent一致性原則:就要以業務與經驗為主來判斷
  • Effective有效原則:錯誤與異常
    對於異常值,我們最簡單的方法是檢視樣本資料的分佈來得到結果。根據箱線圖的三個重要的分位數,在Q3+1.5IQR和Q1-1.5IQR的範圍以外的,認為是異常值,或者叫做離群點。
#用 quantile() 函式來計算
q <- quantile(dat,c(1/4,1/2,3/4))
IQR = IQR(x) = quantile(x, 3/4) - quantile(x, 1/4) = q[[3]]-q[[1]]
O1 = q[[1]] - 1.5*IQR
O2 = q[[3]] + 1.5*IQR
dat[which(dat>O1 & dat<O2)]   # 去除異常點

此外,還有專門研究異常檢測的方法論,可以參照第十八章異常檢測,來進行學習。

2,資料整合:

系統自帶函式

cbind()  #列合併
rbind()  #行合併
merge() #根據條件合併

# 建立資料
dat1 <- data.frame(x=c('a','b','c','d'),y=seq(1,4))
dat2 <- data.frame(x=c('a','c','d','e'),y=seq(5,8))

# 把dat2新增到dat2下方
rbind(dat1,dat2)
  x y
1 a 1
2 b 2
3 c 3
4 d 4
5 a 5
6 c 6
7 d 7
8 e 8

# 把dat2新增到da1右側
 cbind(dat1,dat2)
  x y x y
1 a 1 a 5
2 b 2 c 6
3 c 3 d 7
4 d 4 e 8

# 類似於 inner join
merge(dat1,dat2,by=c('x','x'),suffixes = c('_dat1','_dat2'))
  x y_dat1 y_dat2
1 a      1      5
2 c      3      6
3 d      4      7

# left join
merge(dat1,dat2,by=c('x','x'),suffixes = c('_dat1','_dat2'),all.x = TRUE)

# right join
merge(dat1,dat2,by=c('x','x'),suffixes = c('_dat1','_dat2'),all.y = TRUE)

# full join
merge(dat1,dat2,by=c('x','x'),suffixes = c('_dat1','_dat2'),all = TRUE)

dplyr函式

bind_rows()   # 等價於 rbind() 
bind_cols()   # 等價於 cbind() 

left_join()
例子
left_join(dat1,dat2,by=c('x'='x'),suffix = c('_dat1','_dat2'))

right_join()  # 略
inner_join()  # 略
full_join()   # 略

3,資料變換:

將數值屬性拆分成分類屬性

dat = data.frame(x=c(12,3,5,3,5,6,7,8,2,5,8,1,4,8))

#將資料分為三組,並且命名組1、2、3; 
a = cut(dat$x,3,labels = c('組1','組2','組3'))

#將分組變數dummy化 
# 利用包
library(dummies)
dum=dummy(a)

#利用model.matrix()
b = factor(a)
dum = model.matrix(~b-1)  # 此處記得減1,處理截距項

#合併這些結果 
cbind(dat,a,dum)

上一節:第1章-資料探索(2)-資料預處理之Python實現
下一節:第1章-資料探索(4)-資料的統計分析