第二章 數(shù)據(jù)操作
前面章節(jié)已涵蓋了R 語言基本語法终议,特別是讓讀者訓練了向量化編程思維(同時操作一堆數(shù)據(jù))秤朗、函數(shù)式編程思維(自定義函數(shù)解決問題+ 泛函式循環(huán)迭代)蹲盘。
R 語言更多的是與數(shù)據(jù)打交道颜矿,本章正式進入tidyverse 系列述寡,將全面講解‘‘管道流污淋、整潔流’’操作數(shù)據(jù)的基本語法顶滩,包括:數(shù)據(jù)讀寫、數(shù)據(jù)連接寸爆、數(shù)據(jù)重塑礁鲁,以及各種數(shù)據(jù)操作。
本章最核心的目的是訓練讀者的數(shù)據(jù)思維赁豆,那么什么是數(shù)據(jù)思維仅醇?
我的理解最關鍵的三點是:
(1) 更進一步,將向量化編程思維和函數(shù)式編程思維魔种,納入到數(shù)據(jù)框或更高級的數(shù)據(jù)結構中來
比如析二,向量化編程同時操作一個向量的數(shù)據(jù),變成在數(shù)據(jù)框中操作一列的數(shù)據(jù),或者同時操作數(shù)據(jù)框的多列叶摄,甚至分別操作數(shù)據(jù)框每個分組的多列属韧;函數(shù)式編程變成為想做的操作自定義函數(shù)(或現(xiàn)成函數(shù)),再依次應用到數(shù)據(jù)框的多個列上蛤吓,以修改列或做匯總宵喂。
(2) 將復雜數(shù)據(jù)操作分解為若干基本數(shù)據(jù)操作的能力
復雜數(shù)據(jù)操作都可以分解為若干簡單的基本數(shù)據(jù)操作:數(shù)據(jù)連接、數(shù)據(jù)重塑(長寬變換/拆分合并列)会傲、篩選行锅棕、排序行、選擇列淌山、修改列裸燎、分組匯總。一旦完成問題的梳理和分解艾岂,又熟悉每個基本數(shù)據(jù)操作顺少,用‘‘管道’’ 流依次對數(shù)據(jù)做操作即可朋其。
(3) 接受數(shù)據(jù)分解的操作思維
比如王浴,想對數(shù)據(jù)框進行分組,分別對每組數(shù)據(jù)做操作梅猿,整體來想這是不容易想透的復雜事情氓辣,實際上只需做group_by() 分組,然后把你要對一組數(shù)據(jù)做的操作實現(xiàn)袱蚓;再比如钞啸,across() 同時操作多列,實際上只需把對一列要做的操作實現(xiàn)喇潘。這就是數(shù)據(jù)分解的操作思維体斩,這些函數(shù)會幫你分解+ 分別操作+ 合并結果,你只需要關心分別操作的分颖低,它就是一件簡單的事情絮吵。
很多從C 語言等過來的編程新手,有著根深蒂固地逐個元素for 循環(huán)操作忱屑、每個計算都得‘‘眼見為實’’ 的習慣蹬敲,這都是訓練數(shù)據(jù)思維的大忌,是最應該首先摒棄的惡習莺戒。
2.1 tidyverse 簡介與管道
2.1.1 tidyverse 包簡介
tidyverse 包是Hadley Wickham 及團隊的集大成之作伴嗡,是專為數(shù)據(jù)科學而開發(fā)的一系列包的合集,基于整潔數(shù)據(jù)从铲,提供了一致的底層設計哲學瘪校、語法、數(shù)據(jù)結構名段。
tidyverse 用” 現(xiàn)代的’’渣淤、“優(yōu)雅的’’ 方式赏寇,以管道式、泛函式編程技術實現(xiàn)了數(shù)據(jù)科學的整個流程:數(shù)據(jù)導入价认、數(shù)據(jù)清洗嗅定、數(shù)據(jù)操作、數(shù)據(jù)可視化用踩、數(shù)據(jù)建模渠退、可重現(xiàn)與交互報告。
tidyverse 操作數(shù)據(jù)的優(yōu)雅脐彩,就體現(xiàn)在:
- 每一步要‘‘做什么’’碎乃,就寫‘‘做什么’’,用管道依次做下去惠奸,得到最終結果
- 代碼讀起來梅誓,就像是在讀文字敘述一樣,順暢自然佛南,毫無滯澀
在tidyverse 包的引領下梗掰,近年來涌現(xiàn)出一系列具體研究領域的tidy* 版本的包:tidymodels(統(tǒng)計與機器學習)、mlr3verse(機器學習)嗅回、rstatix(應用統(tǒng)計)及穗、tidybayes(貝葉斯模型)、tidyquant(金融)绵载、fpp3(時間序列)埂陆、tidytext(文本挖掘)、tidygraph(網絡圖)娃豹、sf(空間數(shù)據(jù)分析)焚虱、tidybulk(生信)、sparklyr(大數(shù)據(jù))等懂版。
tidyverse 與data.table
tidyverse 操作數(shù)據(jù)語法優(yōu)雅鹃栽、容易上手,但效率與主打高效的data.table 包不可同日而語定续,處理幾G 甚至十幾G 的數(shù)據(jù)谍咆,需要用data.table.
但data.table 的語法高度抽象、不容易上手私股。本書不對data.table 做過多展開摹察,只講一下基本使用。另一種不錯的方案是使用專門的轉化包:有不少包嘗試底層用data.table倡鲸,上層用tidyverse語法包裝(轉化)供嚎,如dtplyr, tidyfst 等。
2.1.2 管道操作
- 什么是管道操作?
magrittr 包引入了管道操作克滴,能夠通過管道將數(shù)據(jù)從一個函數(shù)傳給另一個函數(shù)逼争,從而用若干函數(shù)構成的管道依次變換你的數(shù)據(jù)。
例如劝赔,對數(shù)據(jù)集mtcars誓焦,先按分類變量cyl 分組,再對連續(xù)變量mpg 做分組匯總計算均值:
library(tidyverse)
mtcars %>%
group_by(cyl) %>%
summarise(mpg_avg = mean(mpg))
## # A tibble: 3 x 2
## cyl mpg_avg
## <dbl> <dbl>
## 1 4 26.7
## 2 6 19.7
## 3 8 15.1
管道運算符%>% (Windows 快捷鍵:Shift+Ctrl+M) 的意思是:將左邊的運算結果着帽,以輸入的方式傳給右邊函數(shù)杂伟。若干個函數(shù)通過管道鏈接起來,叫作管道(pipeline)仍翰。
x %>% f() %>% g() # 等同于g(f(x))
對該管道示例應該這樣理解:依次對數(shù)據(jù)進行若干操作:先對x 進行f 操作, 接著對結果數(shù)據(jù)進行g 操作赫粥。
管道,也支持base R 函數(shù):
month.abb %>% # 內置月份名縮寫字符向量
sample(6) %>%
tolower() %>%
str_c(collapse = "|")
## [1] "jul|apr|jun|aug|jan|mar"
注:R 4.1 增加了同樣功能的管道運算符:|>.
使用管道的好處是:
避免使用過多的中間變量予借;
程序可讀性大大增強:
管道操作的過程越平,讀起來就是對原數(shù)據(jù)集依次進行一系列操作的過程。而非管道操作灵迫,讀起來與操作的過程是相反的秦叛,比如同樣實現(xiàn)上例:
str_c(tolower(sample(month.abb, 6)), collapse="|")
- 常用管道操作
- 管道默認將數(shù)據(jù)傳給下一個函數(shù)的第1 個參數(shù),且它可以省略
c(1, 3, 4, 5, NA) %>%
mean(., na.rm = TRUE) # "." 可以省略
c(1, 3, 4, 5, NA) %>%
mean(na.rm = TRUE) # 建議寫法
這種機制使得管道代碼看起來就是:從數(shù)據(jù)開始龟再,依次用函數(shù)對數(shù)據(jù)施加一系列的操作(變換數(shù)據(jù))书闸,各個函數(shù)都直接從非數(shù)據(jù)參數(shù)開始寫即可尼变,而不用再額外操心數(shù)據(jù)的事情利凑,數(shù)據(jù)會自己沿管道向前‘‘流動’’。
所以嫌术,tidyverse 中的函數(shù)都設計為數(shù)據(jù)作為第1 個參數(shù)哀澈,自定義的函數(shù)也建議這樣做。
- 數(shù)據(jù)可以在下一個函數(shù)中使用多次
數(shù)據(jù)經過管道默認傳遞給函數(shù)的第1 個參數(shù)(通常直接省略)度气;若在非第1 個參數(shù)處使用該數(shù)據(jù)割按,必須用“.” 代替(絕對不能省略),這使得管道作用更加強大和靈活磷籍。下面看一些具體實例:
#數(shù)據(jù)傳遞給plot 第一個參數(shù)作為繪圖數(shù)據(jù)(. 省略),
# 同時用于拼接成字符串給main 參數(shù)用于圖形標題
c(1, 3, 4, 5) %>%
plot(main = str_c(., collapse=","))
# 數(shù)據(jù)傳遞給第二個參數(shù)data
mtcars %>% plot(mpg ~ disp, data = .)
# 選擇列
iris %>% .$Species # 選擇Species 列內容
iris %>% .[1:3] # 選擇1-3 列子集
再來看一個更復雜的例子:分組批量建模
mtcars %>%
group_split(cyl) %>% # . 相當于mtcars
map(~ lm(mpg ~ wt, data = .x))
split() 是將數(shù)據(jù)框mtcars 根據(jù)其cyl 列(包含3 個水平的分類變量)分組适荣,得到包含3 個成分的列表;列表接著傳遞給map(.x, .f) 的第一個參數(shù)(直接省略)院领,~ lm(mpg ~ wt, data = .x) 是第二參數(shù).f弛矛,為purrr 風格公式寫法。
整體來看比然,實現(xiàn)的是分組建模:將數(shù)據(jù)框根據(jù)分類變量分組丈氓,再用map 循環(huán)機制依次對每組數(shù)據(jù)建立線性回歸模型。
建議進行區(qū)分:. 用于管道操作中代替數(shù)據(jù);.x 用于purrr 風格公式(匿名函數(shù))万俗。
2.2 數(shù)據(jù)讀寫
2.2.1 數(shù)據(jù)讀寫的包與函數(shù)
先來羅列一下讀寫常見數(shù)據(jù)文件的包和函數(shù)湾笛,具體使用可查閱其幫助文檔。
- readr 包
讀寫帶分隔符的文本文件闰歪,如csv 和tsv; 也能讀寫序列化的R 對象rds嚎研,若想保存數(shù)據(jù)集后續(xù)再加載回來,rds 將保存元數(shù)據(jù)和該對象的狀態(tài)库倘,如分組和數(shù)據(jù)類型嘉赎。
readr 2.0 版本發(fā)布,read_csv() 采用vroom 引擎讀取性能大大提升于樟,同時支持批量讀取文件公条。
- 讀入數(shù)據(jù)到數(shù)據(jù)框:read_csv() 和read_tsv()
- 讀入歐式格式數(shù)據(jù)1:read_csv2() 和read_tsv2()
- 讀寫rds 數(shù)據(jù):read_rds() 和write_rds()
- 寫出數(shù)據(jù)到文件:write_csv(), write_tsv(), write_csv2(), write_tsv2()
- 轉化數(shù)據(jù)類型:parse_number(), parse_logical(), parse_factor() 等
- readxl 包
專門讀取Excel 文件,包括同一個工作簿中的不同工作表:
- read_excel(): 自動檢測xls或xlsx文件
- read_xls(): 讀取xls 文件
- read_xlsx(): 讀取xlsx 文件
讀寫Excel 文件好用的包迂曲,還有openxlsx
- haven 包
讀寫SPSS, Stata, SAS 數(shù)據(jù):
- 讀:read_spss(), read_dta(), read_sas()
- 寫:write_spss(), write_stata(), write_sas()
- jsonlite 包
讀寫JSON 數(shù)據(jù)靶橱,與R 數(shù)據(jù)結構相互轉換:
- 讀:read_json(), fromJSON()
- 寫:write_json(), toJSON()
- readtext 包
讀取全部文本文件的內容到數(shù)據(jù)框,每個文件變成一行路捧,常用于文本挖掘或數(shù)據(jù)收集关霸;readtext包還支持讀取csv, tab, json, xml, html, pdf, doc, docx, rtf, xls, xlsx 等。
readtext(): 返回數(shù)據(jù)框杰扫,doc_id 列為文檔標識队寇,text 列為讀取的全部文本內容(1 個字符串)。
library(readtext)
document = readtext("datas/十年一覺.txt")
document
## readtext object consisting of 1 document and 0 docvars.
## # Description: df [1 x 2]
## doc_id text
## <chr> <chr>
## 1 十年一覺.txt "\" “這位公子爺\"..."
2.2.2 數(shù)據(jù)讀寫實例
以讀取csv 和Excel 文件為例演示章姓,讀取其他類型的數(shù)據(jù)文件佳遣,換成其他讀取函數(shù)即可。
read_csv(file, col_names, col_types, locale, skip, na, n_max, ...)
- file: 數(shù)據(jù)文件所在相對或絕對路徑凡伊、網址零渐、壓縮包、批量文件路徑等
- col_names: 第一行是否作為列名
- skip: 開頭跳過的行數(shù)
- na: 設置什么值解讀為缺失值
- n_max: 讀取的最大行數(shù)
- col_select: 支持dplyr 選擇列語法選擇要讀取的列
- col_types: 設置列類型3系忙,默認NULL(全部猜測)诵盼,可為每列單獨設置,例如設置3 列的列類型(縮寫):coltypes="cnd"
- locale: 設置區(qū)域語言環(huán)境(時區(qū), 編碼方式, 小數(shù)標記银还、日期格式)风宁,主要是用來設置所讀取數(shù)據(jù)文件的編碼方式,如從默認"UTF-8" 編碼改為"GBK" 編碼:locale = locale(encoding = "GBK")
- 還有參數(shù)comment(忽略的注釋標記)蛹疯,skip_empty_rows 等戒财。
read_xlsx(path, sheet, range, col_names, col_types, skip, na, n_max, ...)
- path: 數(shù)據(jù)文件所在相對或絕對路徑
- sheet: 要讀取的工作表
- range: 要讀取的單元格范圍
- col_names: 第一行是否作為列名
- col_types: 設置列類型4,可總體設置一種類型(循環(huán)使用)或為每列單獨設置苍苞,默認NULL (全部猜測)
- 也有參數(shù):skip, na, n_max.
readr 包讀取數(shù)據(jù)的函數(shù)固翰,默認會保守猜測各列的列類型狼纬。若在讀取數(shù)據(jù)時部分列有丟失信息,則建議先將數(shù)據(jù)以文本(字符)型讀取進來骂际,再用dplyr 修改列類型疗琉。
- 讀取csv 文件
df = read_csv("datas/六1班學生成績.csv")
df
## # A tibble: 4 x 6
## 班級 姓名 性別 語文 數(shù)學 英語
## <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 六1班 何娜 女 87 92 79
## 2 六1班 黃才菊 女 95 77 75
## 3 六1班 陳芳妹 女 79 87 66
## 4 六1班 陳學勤 男 82 79 66
- 批量讀取Excel 文件
批量讀取的數(shù)據(jù)文件往往具有相同的列結構(列名、列類型)歉铝,讀入后緊接著需要按行合并為一個數(shù)據(jù)框盈简。批量讀取并合并,道理很簡單太示,總共分三步:
- 獲取批量數(shù)據(jù)文件的路徑
- 循環(huán)機制批量讀取
- 合并成一個數(shù)據(jù)文件
強大的purrr 包柠贤,使得后兩步可以同時做,即借助
map_dfr(.x, .f, .id)
將函數(shù).f 依次應用到序列.x 的每個元素返回數(shù)據(jù)框类缤,再bind_rows 按行合并為一個數(shù)據(jù)框臼勉,.id 可用來增加新列描述來源。
比如餐弱,在read_datas 文件夾下有5 個xlsx 文件宴霸,每個文件的列名都是相同的:
首先要得到要導入的全部Excel 文件的完整路徑,可以任意嵌套膏蚓,只需將參數(shù)recurse 設為TRUE:
files = fs::dir_ls("datas/read_datas", recurse = TRUE, glob = "*.xlsx")
files
## datas/read_datas/六1班學生成績.xlsx
## datas/read_datas/六3班學生成績.xlsx
## datas/read_datas/六4班學生成績.xlsx
## datas/read_datas/六5班學生成績.xlsx
## datas/read_datas/新建文件夾/六2班學生成績.xlsx
接著瓢谢,用map_dfr() 在該路徑向量上做迭代,應用read_xlsx() 到每個文件路徑驮瞧,再按行合并氓扛。
另外,再多做一步:用set_names() 將文件路徑字符向量創(chuàng)建為命名向量论笔,再結合參數(shù).id 將路徑值作為數(shù)據(jù)來源列采郎。
library(readxl)
df = map_dfr(files, read_xlsx)
head(df)
## # A tibble: 6 x 6
## 班級 姓名 性別 語文 數(shù)學 英語
## <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 六1班 何娜 女 87 92 79
## 2 六1班 黃才菊 女 95 77 75
## 3 六1班 陳芳妹 女 79 87 66
## 4 六1班 陳學勤 男 82 79 66
## 5 六3班 江佳欣 女 80 69 75
## 6 六3班 何詩婷 女 76 53 72
# 若想增加一列表明數(shù)據(jù)來自哪個文件
df = map_dfr(set_names(files), read_xlsx, .id = " 來源")
head(df)
## # A tibble: 6 x 7
## 來源 班級 姓名 性別 語文 數(shù)學 英語
## <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 datas/read_datas/六1班學生成績~ 六1班 何娜 女 87 92 79
## 2 datas/read_datas/六1班學生成績~ 六1班 黃才~ 女 95 77 75
## 3 datas/read_datas/六1班學生成績~ 六1班 陳芳~ 女 79 87 66
## 4 datas/read_datas/六1班學生成績~ 六1班 陳學~ 男 82 79 66
## 5 datas/read_datas/六3班學生成績~ 六3班 江佳~ 女 80 69 75
## 6 datas/read_datas/六3班學生成績~ 六3班 何詩~ 女 76 53 72
files 是文件路徑構成的字符向量(未命名,只有索引訪問)翅楼,set_names(files) 是將該字符向量尉剩,變成命名字符向量真慢,名字就用元素值毅臊;參數(shù).id 定義的新列’’ 來源’’,將使用這些名字黑界。
函數(shù)read_xlsx() 的其他控制讀取的參數(shù)管嬉,可直接‘‘作為” map_dfr 參數(shù)在后面添加,或改用purrr風格公式形式:
map_dfr(set_names(files), read_xlsx, sheet = 1, .id = " 來源") # 或者
map_dfr(set_names(files), ~ read_xlsx(.x, sheet = 1), .id = " 來源")
若批量Excel 數(shù)據(jù)是來自同一xlsx 的多個sheet朗鸠,比如還是上述數(shù)據(jù)蚯撩,只是在‘‘學生成績.xlsx” 的5個sheet 中:
path = "datas/學生成績.xlsx" # Excel 文件路徑
df = map_dfr(set_names(excel_sheets(path)),
~ read_xlsx(path, sheet = .x), .id = "sheet")
head(df)
## # A tibble: 6 x 7
## sheet 班級 姓名 性別 語文 數(shù)學 英語
## <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 六1班 六1班 何娜 女 87 92 79
## 2 六1班 六1班 黃才菊 女 95 77 75
## 3 六1班 六1班 陳芳妹 女 79 87 66
## 4 六1班 六1班 陳學勤 男 82 79 66
## 5 六2班 六2班 黃祖娜 女 94 88 75
## 6 六2班 六2班 徐雅琦 女 92 86 72
excel_sheets() 函數(shù)作用在該Excel 文件上,提取各個sheet 名字烛占,得到字符向量胎挎;然后同樣是實現(xiàn)批量讀取沟启,只是這次是在sheet 名字的字符向量上循環(huán)而已。
另外犹菇,readr 2.0 提供了非常簡單方法實現(xiàn)批量讀取+ 合并csv 文件(列名/列類型相同):
files = fs::dir_ls("datas/read_datas", recurse = TRUE, glob = "*.csv")
df = read_csv(files)
- 寫出到一個Excel 文件
用readr 包中的write_csv() 和write_rds()德迹,或writexl 包中的write_xlsx() 可以保存數(shù)據(jù)到文件。
以寫出到Excel 文件為例:
library(writexl)
write_xlsx(df, "datas/output_file.xlsx")
- 批量寫出到Excel 文件
比如有多個數(shù)據(jù)框揭芍,存在一個列表中胳搞,依次將它們寫入文件,需要準備好文件名称杨;在該數(shù)據(jù)框列表和文件名上肌毅,依次應用寫出函數(shù)write_xlsx(),又不需要返回值姑原,故適合用purrr 包中的walk2()函數(shù):
df = iris %>%
group_split(Species) # 鳶尾花按組分割, 得到數(shù)據(jù)框列表
files = str_c("datas/", levels(iris$Species), ".xlsx") # 準備文件名
walk2(df, files, write_xlsx)
若要多個數(shù)據(jù)框分別寫入一個Excel 文件的多個sheet悬而,先將多個數(shù)據(jù)框創(chuàng)建為命名列表(名字將作為sheet 名) ,再用write_xlsx() 寫出即可:
df = df %>%
set_names(levels(iris$Species))
write_xlsx(df, "datas/iris.xlsx")
- 保存與載入rds 數(shù)據(jù)
除了save() 和load() 函數(shù)外锭汛,下面以導出數(shù)據(jù)到.rds 文件為例摊滔,因為它能保存數(shù)據(jù)框及其元數(shù)據(jù),如數(shù)據(jù)類型和分組等店乐。
write_rds(iris, "my_iris.rds")
dat = read_rds("my_iris.rds") # 導入.rds 數(shù)據(jù)
2.2.3 連接數(shù)據(jù)庫
R 操作數(shù)據(jù)是先將數(shù)據(jù)載入內存艰躺,當數(shù)據(jù)超過內存限制時,可能會讓您束手無策眨八。一種解決辦法是腺兴,將大數(shù)據(jù)存放在遠程數(shù)據(jù)庫(遠程服務器或本地硬盤),然后建立與R 的連接廉侧,再從R 中執(zhí)行查詢页响、探索、建模等段誊。
注意闰蚕,內存可以應付的數(shù)據(jù)集,是沒有必要這樣操作的连舍。
dplyr 是tidyverse 操作數(shù)據(jù)的最核心包没陡,而dbplyr 包是用于數(shù)據(jù)庫的dplyr 后端,讓您能夠操作遠程數(shù)據(jù)庫中的數(shù)據(jù)表索赏,就像它們是內存中的數(shù)據(jù)框一樣盼玄。安裝dbplyr 包時,還會自動安裝DBI包潜腻,它提供了通用的接口埃儿,使得能夠使用相同的代碼與許多不同的數(shù)據(jù)庫連用。
常見的主流數(shù)據(jù)庫軟件:SQL Server, MySQL, Oracle 等都能支持融涣,但還需要為其安裝特定的驅動童番,比如
- RMariaDB 包:連接到MySQL 和MariaDB
- RPostgres 包:連接到Postgres 和Redshift
- RSQLite 包:嵌入SQLite 數(shù)據(jù)庫
- odbc 包:通過開放數(shù)據(jù)庫連接協(xié)議連接到許多商業(yè)數(shù)據(jù)庫
- bigrquery 包:連接到谷歌的BigQuery
下面以R 連接MySQL 數(shù)據(jù)庫為例精钮,用小數(shù)據(jù)集演示基本操作。連接其他數(shù)據(jù)庫也是類似的剃斧。
(1) 配置MySQL 開發(fā)環(huán)境
我這里是用的MySQL zip 版+ Navicat(數(shù)據(jù)庫管理工具), 具體操作可參閱(知乎)八咫鏡:mysql安裝及配置杂拨。
(2) 新建MySQL 連接和數(shù)據(jù)庫
在Navicat 新建MySQL 連接,輸入連接名(隨便起名)和配置MySQL 時設好的用戶名及相應密碼:
打開該連接悯衬,右鍵新建數(shù)據(jù)庫弹沽,數(shù)據(jù)庫名為mydb(隨便起名),選擇字符編碼和排序規(guī)則:
(3) 建立R 與MySQL 的連接
先加載RMariaDB 包筋粗,再用dbConnect() 函數(shù)來建立連接策橘,需要提供數(shù)據(jù)庫后端、用戶名娜亿、密碼丽已、數(shù)據(jù)庫名、主機:
library(RMariaDB)
con = dbConnect(MariaDB(), user = "root", password = "123456",
dbname = "mydb", host = "localhost")
dbListTables(con) # 查看con 連接下的數(shù)據(jù)表
## [1] "exam"
這表明該連接下還沒有數(shù)據(jù)表买决。
(4) 創(chuàng)建數(shù)據(jù)表
在該連接下沛婴,若已有MySQL 數(shù)據(jù)表則直接進入下一步,否則有兩種方法創(chuàng)建數(shù)據(jù)表:
- 在MySQL 端督赤,從Navicat 創(chuàng)建表嘁灯,可從外部數(shù)據(jù)文件導入到數(shù)據(jù)表
- 在R 端,讀取數(shù)據(jù)躲舌,再通過函數(shù)dbWriteTable() 寫入到數(shù)據(jù)表丑婿;若是大數(shù)據(jù),可以借助循環(huán)逐塊地讀取和追加寫入没卸。
datas = read_xlsx("datas/ExamDatas.xlsx")
dbWriteTable(con, name = "exam", value = datas, overwrite = TRUE)
dbListTables(con)
## [1] "exam"
(5) 數(shù)據(jù)表引用
用函數(shù)tbl() 獲取數(shù)據(jù)表的引用羹奉,引用是一種淺拷貝機制,能夠不做物理拷貝而使用數(shù)據(jù)约计,一般處理大數(shù)據(jù)都采用該策略诀拭。
df = tbl(con, "exam")
df
## # Source: table<exam> [?? x 8]
## # Database: mysql [root@localhost:NA/mydb]
## class name sex chinese math english moral science
## <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 六1班 何娜 女 87 92 79 9 10
## 2 六1班 黃才菊 女 95 77 75 8 9
## 3 六1班 陳芳妹 女 79 87 66 9 10
## 4 六1班 陳學勤 男 82 79 66 9 10
## 5 六1班 陳祝貞 女 76 79 67 8 10
## 6 六1班 何小薇 女 83 73 65 8 9
## # ... with more rows
輸出數(shù)據(jù)表引用,看起來和tibble 幾乎一樣煤蚌,主要區(qū)別就是它是來自遠程MySQL 數(shù)據(jù)庫耕挨。
(6) 數(shù)據(jù)表查詢
與數(shù)據(jù)庫交互,通常是用SQL(結構化查詢語言)铺然,幾乎所有的數(shù)據(jù)庫都在使用SQL.
dbplyr 包讓R 用戶用dplyr 語法就能執(zhí)行SQL 查詢俗孝,就像用在R 中操作數(shù)據(jù)框一樣:
df %>%
group_by(sex) %>%
summarise(avg = mean(math, na.rm = TRUE))
## # Source: lazy query [?? x 2]
## # Database: mysql [root@localhost:NA/mydb]
## sex avg
## <chr> <dbl>
## 1 女69.1
## 2 男65.2
普通數(shù)據(jù)框與遠程數(shù)據(jù)庫查詢之間最重要的區(qū)別是,您的R 代碼被翻譯成SQL 并在遠程服務器上的數(shù)據(jù)庫中執(zhí)行魄健,而不是在本地機器上的R 中執(zhí)行。當與數(shù)據(jù)庫一起工作時插勤,dplyr 試圖盡可能地懶惰:
- 除非明確要求(接collect())沽瘦,否則它不會把數(shù)據(jù)拉到R 中革骨;
- 它把任何工作都盡可能地推遲到最后一刻:把您想做的所有事情合在一起,然后一步送到數(shù)據(jù)庫中析恋。
dbplyr 包還提供了將dplyr 代碼翻譯成SQL 查詢代碼的函數(shù)show_query(). 可以進一步用于MySQL, 或dbSendQuery(), dbGetQuery():
df %>%
group_by(sex) %>%
summarise(avg = mean(math, na.rm = TRUE)) %>%
show_query()
## <SQL>
## SELECT `sex`, AVG(`math`) AS `avg`
## FROM `exam`
## GROUP BY `sex`
dbGetQuery(con, "SELECT `sex`, AVG(`math`) AS `avg`
FROM `exam`
GROUP BY `sex`")
## sex avg
## 1 女 69.11538
## 2 男 65.20833
最后良哲,關閉R 與MySQL 的連接:
dbDisconnect(con)
2.2.4 關于中文編碼
中文亂碼是讓很多編程者頭痛的問題。
- 什么是編碼助隧?
文字符號在計算機中是用0 和1 的字節(jié)序列表示的筑凫,編碼就是將字節(jié)序列與所要表示的文字符號建立起映射。
要把各個國家不同的所有文字符號(字符集)正常顯示和使用并村,需要做兩件事情:
- 各個國家不同的所有文字符號一一對應地建立數(shù)字編碼
- 數(shù)字編碼按一定編碼規(guī)則用0-1 表示出來
第一件事情已有一種Unicode 編碼(萬國碼)來解決:它給全世界所有語言的所有文字符號規(guī)定了獨一無二的數(shù)字編碼巍实,字符間分隔的方式是用固定長度字節(jié)數(shù)。
這樣各個國家只需要做第二件事情:為自己國家的所有文字符號設計一種編碼規(guī)則來表示對應的Unicode 編碼哩牍。
從Unicode 到各國具體編碼棚潦,稱為編碼過程;從各國具體編碼到Unicode膝昆,稱為解碼過程丸边。
再來說中國的第二件事情:漢字符號(中文)編碼。歷史原因產生了多種中文編碼荚孵,從圖來看更直觀:
所謂兼容性妹窖,可以理解為子集,同時存在也不沖突收叶。由圖2.8 可見嘱吗,ASCII(128 個字母和符號,英文夠用)被所有編碼兼容滔驾,而最常見的UTF-8 與GBK 之間除了ASCII 部分之外沒有交集谒麦。
文件采用什么編碼方式,就用什么編碼方式打開哆致。只要是用不兼容的編碼方式打開文件绕德,就會出現(xiàn)亂碼,日常最容易導致亂碼場景就是:
用UTF-8(GBK)編碼方式去讀取GBK(UTF-8)編碼的文字摊阀,就會出現(xiàn)各種亂碼
GBK(國標擴展)系列耻蛇,根據(jù)包含漢字符號從少到多嚣鄙,依次是
GB2312: 只包含6763 個漢字
GBK: 包含20902 個漢字胶台,基本足夠用
GB18030: 又分GB1830-2000 和GB1830-2005,包含七萬多個漢字
GBK 編碼的漢字基本是2 字節(jié)谱醇,節(jié)省空間漱牵,但只適合國內中文環(huán)境夺蛇。
UTF-8 編碼(Unicode 轉換格式),是Unicode 的再表示酣胀,支持各個國家的文字符號刁赦,兼容性非常好娶聘。所以,目前UTF-8 有一統(tǒng)天下的趨勢甚脉。
UTF-8 是一種變長編碼丸升,解決字符間分隔的方式是通過對二進制中最高位連續(xù)1 的個數(shù)來決定該字是幾字節(jié)編碼。所有常用漢字的Unicode 值均可用3 字節(jié)的UTF-8 表示出來牺氨。
- 中文亂碼的解決辦法
首先狡耻,查看并確認你的windows 系統(tǒng)的默認編碼方式:
Sys.getlocale("LC_CTYPE") # 查看系統(tǒng)默認字符集類型
## [1] "Chinese (Simplified)_China.936"
代碼936 就表明是‘‘中國- 簡體中文(GB2312)’’。
注意:不建議修改系統(tǒng)的默認編碼方式猴凹,因為可能會導致一些軟件夷狰、文件亂碼精堕。
大多數(shù)中文亂碼都是GBK 與UTF-8 不兼容導致的孵淘,常見的有兩種情形。
R 文件中的中文亂碼
在你的電腦不中文亂碼的R 腳本歹篓、Rmarkdown 等瘫证,拷貝到另一臺電腦上時出現(xiàn)中文亂碼。
解決辦法:前文在配置Rstudio 時已講到庄撮,設置code – saving 的Default text encoding 為兼容性更好的UTF-8背捌。
讀寫數(shù)據(jù)文件中文亂碼
數(shù)據(jù)文件采用什么編碼方式,就用什么編碼方式打開或讀取洞斯。采用了不兼容的另一種編碼打開或讀取毡庆,肯定出現(xiàn)中文亂碼。
R 自帶函數(shù)讀取GBK 或UTF-8
- 與所用操作系統(tǒng)默認編碼相同的數(shù)據(jù)文件烙如,即GBK么抗,R 自帶的函數(shù)read.csv()、read.table()亚铁、readLines() 都可以正常讀取但不能直接讀取UTF-8
- 但在read.csv() 和read.table() 中設置參數(shù)fileEncoding = "UTF-8"蝇刀,可以讀取UTF-8,但無論如何不能讀取BOM UTF-8
- 在readLines() 中設置參數(shù)encoding = "UTF-8"徘溢,可以讀取UTF-8 和BOM UTF-8
read.csv("datas/bp-gbk.csv") # GBK, 直接讀取
read.csv("datas/bp-utf8nobom.csv", # UTF-8, 設置參數(shù)讀取
fileEncoding = "UTF-8")
readLines("datas/bp-gbk.csv") # GBK, 直接讀取
# UTF-8 和BOM UTF-8, 設置參數(shù)讀取
readLines("datas/bp-utf8nobom.csv", encoding = "UTF-8")
readLines("datas/bp-utf8bom.csv", encoding = "UTF-8")
readr 包讀取GBK 或UTF-8
- readr 包中的read_csv()吞琐、read_table2()、read_lines() 默認讀取UTF-8 和BOM UTF-8然爆;
- 但不能直接讀取GBK, 需要設置參數(shù)locale = locale(encoding="GBK")
read_csv("datas/bp-utf8nobom.csv") # UTF-8, 直接讀取
read_csv("datas/bp-utf8bom.csv") # BOM UTF-8, 直接讀取
read_csv("datas/bp-gbk.csv",
locale = locale(encoding="GBK")) # GBK, 設置參數(shù)讀取
寫入GBK 或UTF-8 文件
- R 自帶的write.csv(), writeLines() 仍是跟隨操作系統(tǒng)默認編碼站粟,即默認寫出為GBK 文件;設置參數(shù)fileEncoding = "UTF-8" 可寫為UTF-8
- readr 包中的write_csv(), write_lines() 默認寫為UTF-8, 但不能被Excel 軟件正確打開
- readr::write_excel_csv() 可以寫為BOM UTF-8, Excel 軟件能正確打開
write.csv(df, "file-GBK.csv") # 寫出為GBK 文件
write.csv(df, "file-UTF8.csv",
fileEncoding = "UTF-8") # 寫出為UTF-8 文件
write_csv(df, "file-UTF8.csv") # 寫出為UTF-8 文件
write_excel_csv(df, "file-BOM-UTF8.csv") # 寫出為BOM UTF-8 文件
不局限于上述編碼曾雕,一個數(shù)據(jù)文件只要知道了其編碼方式奴烙,就可以通過在讀寫時指定該編碼而避免亂碼。那么關鍵的問題就是:怎么確定一個數(shù)據(jù)文件的編碼?
AkelPad 是一款優(yōu)秀開源小巧的文本編輯器缸沃,用它打開數(shù)據(jù)文件恰起,自動在窗口下方顯示文件的編碼:
若要轉換編碼修械,只需要點文件另存為趾牧,在代碼頁下拉框選擇想要的編碼方式,保存即可肯污。
另外翘单,readr 包和rvest 包(爬蟲)都提供了函數(shù)guess_encoding(),可檢測文本和網頁的編碼方式蹦渣;python 有一個chardet 庫在檢測文件編碼方面更強大哄芜。