2017年9月22日 星期五

R等差數列(前後二個元素相減)與條件式計算


主題: 如何計算數列的前後二個元素相減產生的新等差數列與不同條件式數值計算

# diff
# c
# lapply
# <<

分析:
  • 感謝R友 Bic Ton提供此問題
  • 考慮 x <- c(2,5,6,1,3,8,4,5,6), 計算數列中後面減前面之結果, 例: 5-2=3, 6-5=1,...., 此時可以使用 diff(x).  diff 函數預設會計算數列中第2個元素減第1個元素的結果. 參閱線上說明 ?diff 
  • 如果希望計算前面減後面之結果, 例: 2-5=33, 5-6=-1,...., 此時可以使用 -diff(x)
  • 本例考慮3種不同解法:
    (1). 使用 for 迴圈判斷等差數列每個元素為正數或負數, 再加總結果.
    (2). 使用 for 迴圈判斷等差數列每個元素為正數或負數, 將結果用 c() 向量組合成新向量, 此方法可儲存正數與負數之結果. 注意: 本方法因為要儲存新等差數列的結果, 因此執行時間須較久.
    (3). 使用 lapply 函數以進行不同條件式數值計算, 本例使用全域變數指派符號 << 以利回傳計算結果.
  • 本例第2種方法 c() 分別儲存正/負數結果, 如果資料量較大時, 例: 100萬筆資料以上, 其執行時間須很久, 此時須考量其他儲存方式, 例: 使用 list串列 以加速程式之進行.
R程式碼:

x <- c(2,5,6,1,3,8,4,5,6)
x.diff <- -diff(x)
x.diff
# method 1 using for loop
gain1 <- 0
loss1 <- 0
for (i in 1:length(x.diff)) {
 if (x.diff[i] >= 0) gain1 <- gain1 + x.diff[i]
 else if (x.diff[i] < 0) loss1 <- loss1 + x.diff[i]
}
gain1
loss1

# method 2 using for loop with c()
gain2 <- c()
loss2 <- c()
for (i in 1:length(x.diff)) {
 if (x.diff[i] >= 0) gain2 <- c(gain2, x.diff[i])
 else if (x.diff[i] < 0) loss2 <- c(loss2, x.diff[i])
}
sum(gain2)
sum(loss2)

# method 3
gain3 <- 0
loss3 <- 0
lapply(x.diff, function(x) {
 ifelse(x > 0, gain3 <<- gain3 + x, loss3 <<- loss3 + x)
 #return(c(gain, loss))
 })
gain3
loss3
# end

2017年9月10日 星期日

R讀取中文檔案產生亂碼等錯誤問題

主題: R讀取中文檔案產生亂碼等錯誤問題

說明:

# 2022.08.16 更新

感謝R友-HJ提供此問題.

問題: 使用 Windows 10 企業版, 匯入資料有亂碼或是有問題.
分析: 

# Windows 10 企業版
# R-4.2.1
# RStudio Desktop 2022.07.1+554

# gfc.csv download
# https://github.com/rwepa/DataDemo/blob/master/gfc.csv

Sys.getlocale()
# "LC_COLLATE=Chinese (Traditional)_Taiwan.1252 ...

# method 1 default locale
gfc1 <- read.table("gfc.csv", header = TRUE, sep = ",")
head(gfc1, n=3)
# 亂碼
# ï..orderdate supplier amount
# 1     2009/1/3   æ—¥äºž    266
# 2     2009/1/4 廣é\u0081”    123
# 3     2009/1/5 廣é\u0081”     66

# method 2 set locale
Sys.setlocale(category="LC_ALL", locale = "English_United States.1252")
Sys.getlocale()
# "LC_COLLATE=English_United States.125 ...

gfc2 <- read.table("gfc.csv", header = TRUE, sep = ",", encoding = "UTF-8")
head(gfc2, n=3)
# 亂碼
# X.U.FEFF.orderdate         supplier amount
# 1           2009/1/3 <U+65E5><U+4E9E>    266
# 2           2009/1/4 <U+5EE3><U+9054>    123
# 3           2009/1/5 <U+5EE3><U+9054>     66

head(as.character(gfc2$supplier)) # 中文正常
# "日亞" "廣達" "廣達" "日亞" "廣達" "科銳"
# end

# 2022.5.4 更新

R-4.2.0版本直接支援CSV檔案為 UTF-8-BOM 編碼,
將檔案另存為ANSI編碼時, read.table 會有錯誤
myfile <- "gfc.csv"
gfc <- read.table(myfile, header = TRUE, sep = ",")
head(gfc)

# 2021.11.03 更新
# 直接讀取網路中文檔案
urls <- "https://raw.githubusercontent.com/rwepa/DataDemo/master/gfc.csv"
gfc <- read.table(urls, header = TRUE, sep = ",", fileEncoding = "UTF-8-BOM")
head(gfc)

# 2017.9.10
# read.table
# encoding="UTF-8-BOM"
# ANSI
  • 感謝R友-阿賢提供 encoding="UTF-8-BOM"解決亂碼問題.
  • 使用R讀取文字檔時, 有時會遇到資料匯入有錯誤訊息或中文亂碼問題.
  • 資料來源: https://data.gov.tw/dataset/35131, 匯入 open data 空氣品質監測小時值(一般污染物,每日更新) 所產生的問題與解決方式.
  • 匯入資料 read.table {utils} 常用參數:
    (1). fill = TRUE --> 使用時機: 錯誤訊息為 line x did not have xxx elements.
    (2). encoding --> 結果為亂碼.
    (3). fileEncoding  --> 結果為亂碼.
  • 考慮 Windows 執行環境, 如果有亂碼問題, 最簡單的解決方式之一是使用記事本開啟檔案, 另存新檔 畫面中, 編碼改為 ANSI.


  • 使用 Notepad++ [https://notepad-plus-plus.org/zh/] 開啟檔案, 在視窗右下角狀態列會有"UTF-8-BOM"編碼, 此時可加上 encoding 或 fileEncoding 參數, 本例使用 fileEncoding = "UTF-8-BOM" 參數即可完成匯入資料.


  • 另可參考: 資料集為CSV檔,打開來為亂碼,怎麼辦? https://data.gov.tw/node/18765
  • 結論: 使用另存ANSI編碼或加入fileEncoding="UTF-8-BOM"參數應該可以解決亂碼問題.
# 執行畫面:



# R程式碼:
x1 <- read.table("ATM00626_20170910170405.csv", header=TRUE, sep=",") # line 1 did not have 31 elements
x2 <- read.table("ATM00626_20170910170405.csv", header=TRUE, sep=",", encoding="UTF-8") # line 1 did not have 31 elements
x3 <- read.table("ATM00626_20170910170405.csv", header=TRUE, sep=",", encoding="UTF-8", fill=TRUE) # 欄位錯誤
x3[1:3, 1:6]
x4 <- read.table("ATM00626_20170910170405.csv", header=TRUE, sep=",", encoding="UTF-8-BOM") # line 1 did not have 31 elements
x5 <- read.table("ATM00626_20170910170405.csv", header=TRUE, sep=",", encoding="UTF-8-BOM", fill=TRUE) # 亂碼
x5[1:3, 1:6]
x6 <- read.table("ATM00626_20170910170405.csv", header=TRUE, sep=",", fileEncoding = "UTF-8-BOM", fill=TRUE) # OK
x6[1:3, 1:6]
x7 <- read.table("ATM00626_20170829185638-ansi.csv", header=TRUE, sep=",") # OK
x7[1:3, 1:6]
x8 <- read.csv("ATM00626_20170910170405.csv", header=TRUE) # 亂碼
x8[1:3, 1:6]
x9 <- read.csv("ATM00626_20170910170405.csv", header=TRUE, fileEncoding = "UTF-8") # invalid input found
x10 <- read.csv("ATM00626_20170910170405.csv", header=TRUE, encoding = "UTF-8-BOM") # 亂碼
x10[1:3, 1:6]
x11 <- read.csv("ATM00626_20170910170405.csv", header=TRUE, fileEncoding = "UTF-8-BOM") # OK
x11[1:3, 1:6]
# end

2017年9月9日 星期六

網路抓取 R CRAN 套件清單, 使用 ggplot2 套件繪圖, 建立第2個y軸座標.

主題: 網路抓取 R CRAN 套件清單, 使用 ggplot2 套件繪圖, 建立第2個y軸座標.
說明:

# ggplot2
# packages list
# XML
# geom_col
# goem_line
# geom_point
# scale_y_continuous



  • [#1-2] 首先載入 XML, ggplot2 套件.
  • [#3] 使用CRAN網站-依日期排列抓取現有1萬多個套件清單,
    例: http://cran.csie.ntu.edu.tw/web/packages/available_packages_by_date.html
  • [#4] 使用 readHTMLTable {XML} 函數以讀取網站中的套件清單表格, 將結果儲存為mydf資料物件.
  • [#7] 使用 trimws {base} 函數以刪除欄位名稱空白字元.
  • [#8] 原匯入第1欄 Date為字串資料型態, 使用 as.Date {base} 轉換為 日期(Date) 資料型態.
  • [#9] 使用 format {base}並取出套件更新年, 使用 table {base} 以計算各年套件個數.
  • [#10] 使用 cumsum {base}計算累計套件數並新增為 AccumulatedPAckages 欄位.
  • [#11] 使用 names {base} 設定前二欄名稱為 Year, Packages.
  • [#14] 使用 geom_col {ggplot2} 繪製"套件數(年)"長條圖, 另可使用 geom_bar {ggplot2} 繪製.
  • [#15-18] 設定主標題, x軸標題, y軸標題. theme {ggplot2} 可設定標題左右置中.
  • [#19] 使用 scale_y_continuous {ggplot2}可在繪圖區之右側建立y軸第2座標軸. 右側y軸對應長條圖的刻度. ggplot2 採用資料轉換概念, 因此左側y軸第1座標軸的刻度,對應至累計套件數, 其中最大值約11405, 右側最大值約4502, 11405/4502=2.5, 考慮以2倍計算, 即將左側刻度除以2, 轉換為右側刻度, 一般使用 trans = ~. /2 表示.
  • [#21] 使用 annotate {ggplot2} 可加上文字標題.
  • [#22-24] 使用 goem_line {ggplot2} 繪製累計套件數線圖.
  • [#25-27] 使用 goem_point {ggplot2} 繪製累計套件數點圖.

library(XML)
library(ggplot2)
urls <- "http://cran.csie.ntu.edu.tw/web/packages/available_packages_by_date.html"
mydf <- readHTMLTable(urls, stringsAsFactors = FALSE)
str(mydf)
mydf <- mydf[[1]]
names(mydf) <- trimws(names(mydf))
mydf$Date <- as.Date(mydf$Date)
mydf <- data.frame(table(format(mydf$Date, "%Y")), stringsAsFactors=FALSE)
mydf$AccumulatedPAckages <- cumsum(mydf$Freq)
names(mydf)[1:2] <- c("Year", "Packages")
mydf
ggplot(mydf, aes(x = Year)) +
geom_col(aes(y = Packages*2), fill="grey") + 
theme(plot.title = element_text(hjust = 0.5)) + 
xlab("年") + 
ylab("累計套件數") +
ggtitle("CRAN-套件統計圖 - by RWEPA") + 
scale_y_continuous(sec.axis = sec_axis(trans = ~. /2)) + 
theme(axis.title.y = element_text(colour = "blue")) + 
annotate("text", x=13, y=5500, label= "套件數(年)", angle = -90) + 
geom_line(aes(y = AccumulatedPAckages, group = 1), linetype = 2, color = "blue") +
theme(axis.text.x = element_text(face="bold", angle=45),
axis.text.y = element_text(face="bold", color="black", size=10)) +
geom_point(aes(y = AccumulatedPAckages, group = 1), color = "blue") +
geom_text(aes(y = AccumulatedPAckages, label = AccumulatedPAckages), 
vjust = -0.3, size = 4, color = "blue")
# end