總結第一次自己寫R進行資料處理和練習實際使用正則表達式, 分享一些自己實際碰到問題的心得。
(還有非常非常多要改進的地方ΩДΩ。)

目錄

目的

2008~2017年全臺一般空氣品質測站資料整理成openair套件可以用的格式。

方法

環境

windows version 1803 (10.0.17134) , R version 3.5.1 , RStudio version 1.1.453

主要使用套件

utils , XML , magrittr , readr , xlsx , plyr , tidyr , lubridate

2.解壓縮

取得壓縮檔所在路徑

  • setwd(“自己存壓縮檔的路徑”):設定工作目錄。
  • getwd():測試工作目錄位置。
  • list.files(“工作目錄”,“正則表達式含意:取得zip檔”,full.names =T):取得zip檔的完整路徑。

正則表達式
代表.zip的字串出現一次以上,
注意.本身為正則表達式的指令,需要兩個反斜線(\)以指定此符號而非使用此指令。

setwd("D:/r/taqmn/zip")
getwd()
zipfiles<-list.files(getwd(),"\\.zip{1,}",full.names =T)

建立輸出檔案的資料夾

dir.create("D:/r/taqmn/unzip")#解壓縮的原始檔案的資料夾
dir.create("D:/r/taqmn/raw")#輸出csv和xls檔案的資料夾

取得輸出檔案路徑

  • 使用lapply輸出所有zip檔路徑名,而解壓縮所有檔案。
  • utils::uzip:只取出utils套件中的unzip函數。
  • path.list為解壓縮後的檔名路徑。
path.list<-lapply(zipfiles,function(i){
  utils::unzip(i,exdir="D:/r/taqmn/unzip")  
})

3.留下csv和xls檔

  • 方法一(慢)

  • 流程:使用lapply以刪除D:/r/taqmn/unzip下的ods,odt,doc和txt檔,再把剩下的csv和xls檔複製到raw資料夾。

  • 用正則表達式篩出解壓縮後的檔名路徑(path.list)中不是csv或xls的檔案。

正則表達式
總共判別三個條件,使用或(|)連接,符合其中一個條件就取出字串,
皆設定條件字尾前方有任何字串(其實只是.*的意思)。
第一個條件:結尾不要是csv的。
第二個條件:結尾是doc的。
第三個條件:結尾是ods中,以三個字母出現的任意兩個字母的組合。

  • fname:非csv和xls檔的完整路徑
  1. regexpr():判別字串是否符合正則表達式。

  2. regmatches():取出符合正則表達式的字串。

perl=TRUE 是為了加快讀取正則表達式的運算速度

  • lapply(fname,file.remove):使用lapply把所有非csv和xls檔利用file.remove刪除。

  • file.copy(path.list[[i]],“D:/r/taqmn/raw”)
    複製unzip資料夾剩下的檔案(csv和xls檔)到 D:/r/taqmn/raw。

#辨別檔名,以刪除doc,odt,ods,txt檔,而剩下csv和xls檔
lapply(1:length(path.list),function(i){
  fname<-regmatches(path.list[[i]],regexpr(".*([^csv]{1}$)|.*([doc]$)|.*([ods]{2,}$)",path.list[[i]],perl=TRUE))
  lapply(fname,file.remove)
  file.copy(path.list[[i]],"D:/r/taqmn/raw")
})
  • 方法二(快)

  • 流程:使用lapply取得D:/r/taqmn/unzip下的csv和xls檔後,再複製到raw資料夾。

  • 用正則表達式篩出解壓縮後的檔名路徑(path.list)中是csv或xls的檔案。

正則表達式
總共判別兩個條件,使用或(|)連接,符合其中一個條件就取出字串,
皆設定條件字尾前方有任何字串(其實只是.*的意思)。
第一個條件:結尾是csv的。
第二個條件:結尾是xls的。

  • fname:是csv和xls檔的完整路徑
  1. regexpr():判別字串是否符合正則表達式。

  2. regmatches():取出符合正則表達式的字串。

perl=TRUE 是為了加快讀取正則表達式的運算速度

  • file.copy(fname,“D:/r/taqmn/raw”)
    複製unzip資料夾剩下的檔案(csv和xls檔)到 D:/r/taqmn/raw。
#辨別檔名,只取得csv和xls檔
lapply(1:length(zipfiles),function(i){
  fname<-regmatches(path.list[[i]],regexpr(".*(csv|xls){1}$",path.list[[i]],perl=TRUE))
  # unzip的xls csv copy 到raw
  file.copy(fname,"D:/r/taqmn/raw")
})

4.取新檔名

原因:中文檔名會造成一堆問題(套件讀不懂,讀取速度變慢…等)。

  • 取出D:/r/taqmn/raw下的所有檔名,放到raw.filename。

  • 取出raw.filename檔名中 , , .字串的位置。

原因:例如要把97年大里站_20090301.xls改為西元年+英譯地名四字縮寫+副檔名。

  • 得到西元年

取出檔名民國年份字串轉為數字,加上1911後,轉為字串。

raw.filename<-list.files("D:/r/taqmn/raw")
year.place<-regexpr("年",raw.filename)
site.place<-regexpr("站",raw.filename)
fexten.place<-regexpr("\\.",raw.filename)
distinct.year<-sapply(1:length(raw.filename),function(i){as.character(1911+as.numeric(substring(raw.filename[i],1,year.place[i]-1)))})
  • 英譯地名四字縮寫

1.由opendata空氣品質監測站基本資料網址以二進位模式下載xml檔
(中文地名和英文翻##譯,緯度等等資料),放入D:/r/taqmn/zip/,取名為distinct.xml。
2.讀取xml檔,轉為dataframed而放入distinct1。
3.取得distinct1中,中文地名(dc)與英譯地名(da)。
- 字串轉為向量,原英文字串縮寫為四個字母,並設定both.sides避免相同縮寫產生。
4.取得D:/r/taqmn/raw下的所有檔案名中地區名稱EX:97年大里站_20090301.xls中的大里。
5.匯出逗點分隔csv檔,check.csv(地區中文,地區英文,地區英文簡稱)和distinct.csv(空氣品質監測站基本資料)

download.file("http://opendata.epa.gov.tw/ws/Data/AQXSite/?$format=xml","D:/r/taqmn/zip/distinct.xml",mode="wb")
library("XML")
distinct<- xmlParse("D:/r/taqmn/zip/distinct.xml")
distinct1<-xmlToDataFrame(nodes=getNodeSet(distinct,"//Data"))
library("magrittr")
dc<-unlist(distinct1[1], use.names=FALSE) %>% as.vector()
da<-unlist(distinct1[2], use.names=FALSE) %>% as.vector()
dabb<-toupper(abbreviate(da,4,method="both.sides"))%>% unlist() %>% as.vector()

files.c<-sapply(1:length(raw.filename),function(x){
  substring(raw.filename[x],year.place[x]+1,site.place[x]-1)})
check.df<-data.frame(dc,da,dabb)
colnames(check.df)<-c("地區中文","地區英文","地區英文簡稱")
#.....匯出以儲存檔案紀錄.......
write.table(check.df,file="check.csv",sep=",",row.names =FALSE)
write.table(distinct1,file="distinct.csv",sep=",",row.names =FALSE)
  • 取代中文檔名至英文檔名

1.比較檔名和空氣品質監測站基本資料中的中文地區名稱,如果為NA(代表無對應名稱),則顯示檔名中的中文地區名稱。
2.刪除阿里山或崇倫或泰山的檔案。
3.進行檔名除錯:台改臺。
4.取出檔名中的副檔名。
5.取得舊檔名的完整路徑。
6.結合西元年和大寫英文縮寫地名檔名為新檔名的完整路徑。
7.重新命名檔名。

na.name<-data.frame(ifelse(is.na(match(files.c,dc)),files.c ,""))
delet.filename<-paste("D:/r/taqmn/raw",list.files("D:/r/taqmn/raw","阿里山|崇倫|泰山"),sep="/")
file.remove(delet.filename)
files.csub<-gsub("台","臺",files.c)

file.extension<-sapply(1:length(raw.filename),function(x){substring(raw.filename[x],fexten.place[x],fexten.place[x]+4)})
old.filename<-paste0("D:/r/taqmn/raw/",raw.filename)
new.filename<-paste0("D:/r/taqmn/raw/",distinct.year,dabb[match(files.csub,dc)],file.extension)
file.rename(old.filename,new.filename)

5.讀取指定地區的xls和csv檔自建函數(choose.files)

由於讀xls檔的xlsx套件是使用java撰寫,而xlsx::read.xlsx2一次讀取好幾個xls檔時,需要提高jvm的記憶體,
不然無法使用套件(須Restart R 後設定)。

讀取中文字時的locale=readr::locale(encoding=“BIG5”),非常重要不然會變成亂碼

  • 轉換年份數字為字串
  • 讀取中文對應英文縮寫的check.csv,所輸入的中文轉換至對應之英文縮寫

注意取縮寫四個字母名稱的函數設定為both.sides,可能因四字縮寫名稱相同而出現五個字的英文縮寫,
所以需要加上.符號於取出的字母縮寫之後,確定英文縮寫長度,不然使用正則表達式時,以為只選擇一個地區但取出兩個地區檔案就慘了。

  • 取出raw資料夾下對應地區的檔案路徑

注意2008年檔案的日期一定需要以Date屬性讀入,不然會出現神秘的數字。
其他年份檔案之所有欄位皆以字串屬性讀入,後續欄位資料處理會比較方便。

  • 使用xlsx::read.xlsx2讀取xls檔,readr::read_csv讀取csv檔
options(java.parameters = "-Xmx1024m")

choose.files<-function(choose.year,choose.distinct){

  choose.year<-as.character(choose.year)

  distinct.read<-readr::read_csv("D:/r/taqmn/zip/check.csv",col_types = "ccc",locale=readr::locale(encoding="BIG5"))
  distinct.read1<-unlist(distinct.read[1])
  choose.distinct<-as.vector(choose.distinct)
  distinct<-distinct.read[match(choose.distinct,distinct.read1),3] %>% unlist()
  distinct<-paste0(choose.year,distinct,"\\.")

    file.list<-list.files("D:/r/taqmn/raw",distinct, full.names = TRUE)
    if (substring(file.list,16,19)=="2008"){
      coltype<-c("Date", rep("character",26))
    }else{
      coltype<-c(rep("character",27))
    }
   if (grepl("xls",file.list)){
      lapply(1:length(file.list),function(i){
        cat(file.list[i],"\n")
        xlsx::read.xlsx2(file.list[i],1,colClasses =coltype )
       })
    }else{
       col.type<-"ccccccccccccccccccccccccccc"
       lapply(1:length(file.list),function(i){
          cat(file.list[i],"\n")
         readr::read_csv(file.list[[i]],col_types=col.type,locale=readr::locale(encoding="BIG5"))})
   }
}

6.合併與轉置檔案自建函數(data.arrange)

1.list轉為data.frame。
2.將所有欄位重新命名為英文與統一時間為0~23點。
3.將時間欄位轉置為橫列。
4.合併日期與時間。
5.轉置item(物質)為欄位,並將英文名稱轉為小寫。
6.將rainfall中NR值轉為零。
7.將物質欄位由文字轉為數字。
8.日期欄位由字串轉為Date屬性。
9.取代欄位名稱pm2.5為pm25,wind_direc為wd,wind_speed為ws。
10.最終傳回轉置完成的dataframe。

data.arrange<-function(yfile){
 
  yfile.df<-plyr::ldply(yfile,data.frame)
  col.newname<-c("date","site","item",paste(0:23,"00",sep=":"))
  colnames(yfile.df)<-col.newname
  yfile.tran<-tidyr::gather(yfile.df,paste(0:23,"00",sep=":"),key="time",value="conc")
  yfile.tran$date<-paste(yfile.tran$date,yfile.tran$time)
  yfile.spread<-tidyr::spread(yfile.tran[-c(4)],item,conc)
  colnames(yfile.spread) %<>% tolower()
  yfile.spread$rainfall[yfile.spread$rainfall=="NR"]<-0
  yfile.spread[,3:length(colnames(yfile.spread))]<-sapply(yfile.spread[,3:length(colnames(yfile.spread))],as.numeric)
  yfile.spread$date<-lubridate::ymd_hm(yfile.spread$date,tz=Sys.timezone())
  colnames(yfile.spread)[grep("pm2|wind_",colnames(yfile.spread))]<- c("pm25","wd","ws")
  return(yfile.spread)
     }

7.實際使用讀取檔案的自建函數

  • 以台中地區一般空氣品質測站的大里,西屯,沙鹿,忠明和豐原為例。
want.area<-c("大里","西屯","沙鹿","忠明","豐原")
library("magrittr")
file.raw<-lapply(1:length(want.area),function(i){choose.files(2008,choose.distinct=want.area[i])})
## D:/r/taqmn/raw/2008DALI.xls 
## D:/r/taqmn/raw/2008XITN.xls 
## D:/r/taqmn/raw/2008SHAL.xls 
## D:/r/taqmn/raw/2008ZHNG.xls 
## D:/r/taqmn/raw/2008FNGY.xls
file.raw1<-lapply(1:length(want.area),function(i){choose.files(2009,choose.distinct=want.area[i])})
## D:/r/taqmn/raw/2009DALI.csv 
## D:/r/taqmn/raw/2009XITN.csv 
## D:/r/taqmn/raw/2009SHAL.csv 
## D:/r/taqmn/raw/2009ZHNG.csv 
## D:/r/taqmn/raw/2009FNGY.csv
file.raw2<-lapply(1:length(want.area),function(i){choose.files(2010,choose.distinct=want.area[i])})
## D:/r/taqmn/raw/2010DALI.csv 
## D:/r/taqmn/raw/2010XITN.csv 
## D:/r/taqmn/raw/2010SHAL.csv 
## D:/r/taqmn/raw/2010ZHNG.csv 
## D:/r/taqmn/raw/2010FNGY.csv
file.raw3<-lapply(1:length(want.area),function(i){choose.files(2011,choose.distinct=want.area[i])})
## D:/r/taqmn/raw/2011DALI.csv 
## D:/r/taqmn/raw/2011XITN.csv 
## D:/r/taqmn/raw/2011SHAL.csv 
## D:/r/taqmn/raw/2011ZHNG.csv 
## D:/r/taqmn/raw/2011FNGY.csv
file.raw4<-lapply(1:length(want.area),function(i){choose.files(2012,choose.distinct=want.area[i])})
## D:/r/taqmn/raw/2012DALI.xls 
## D:/r/taqmn/raw/2012XITN.xls 
## D:/r/taqmn/raw/2012SHAL.xls 
## D:/r/taqmn/raw/2012ZHNG.xls 
## D:/r/taqmn/raw/2012FNGY.xls
file.raw5<-lapply(1:length(want.area),function(i){choose.files(2013,choose.distinct=want.area[i])})
## D:/r/taqmn/raw/2013DALI.xls 
## D:/r/taqmn/raw/2013XITN.xls 
## D:/r/taqmn/raw/2013SHAL.xls 
## D:/r/taqmn/raw/2013ZHNG.xls 
## D:/r/taqmn/raw/2013FNGY.xls
file.raw6<-lapply(1:length(want.area),function(i){choose.files(2014,choose.distinct=want.area[i])})
## D:/r/taqmn/raw/2014DALI.xls 
## D:/r/taqmn/raw/2014XITN.xls 
## D:/r/taqmn/raw/2014SHAL.xls 
## D:/r/taqmn/raw/2014ZHNG.xls 
## D:/r/taqmn/raw/2014FNGY.xls
file.raw7<-lapply(1:length(want.area),function(i){choose.files(2015,choose.distinct=want.area[i])})
## D:/r/taqmn/raw/2015DALI.xls 
## D:/r/taqmn/raw/2015XITN.xls 
## D:/r/taqmn/raw/2015SHAL.xls 
## D:/r/taqmn/raw/2015ZHNG.xls 
## D:/r/taqmn/raw/2015FNGY.xls
file.raw8<-lapply(1:length(want.area),function(i){choose.files(2016,choose.distinct=want.area[i])})
## D:/r/taqmn/raw/2016DALI.xls 
## D:/r/taqmn/raw/2016XITN.xls 
## D:/r/taqmn/raw/2016SHAL.xls 
## D:/r/taqmn/raw/2016ZHNG.xls 
## D:/r/taqmn/raw/2016FNGY.xls
file.raw9<-lapply(1:length(want.area),function(i){choose.files(2017,choose.distinct=want.area[i])})
## D:/r/taqmn/raw/2017DALI.xls 
## D:/r/taqmn/raw/2017XITN.xls 
## D:/r/taqmn/raw/2017SHAL.xls 
## D:/r/taqmn/raw/2017ZHNG.xls 
## D:/r/taqmn/raw/2017FNGY.xls

8.實際使用合併檔案的自建函數

  • 最後將合併好的檔案(mydata)匯出為RData(以備不時之需),就可以開始使用openair了~
mydata9<-data.arrange(file.raw9)
 mydata8<-data.arrange(file.raw8)
 mydata7<-data.arrange(file.raw7)
 mydata6<-data.arrange(file.raw6)
 mydata5<-data.arrange(file.raw5)
 mydata4<-data.arrange(file.raw4)
 mydata3<-data.arrange(file.raw3)
 mydata2<-data.arrange(file.raw2)
 mydata1<-data.arrange(file.raw1)
 mydata<-data.arrange(file.raw)
mydata<-plyr::rbind.fill(mydata,mydata1,mydata2,mydata3,mydata4,mydata5,mydata6,mydata7,mydata8,mydata9)
save(mydata,file="mydata.RData")