【Tidyverse優(yōu)雅編程】?jī)?yōu)雅數(shù)據(jù)編程思維(實(shí)例闡述)

編程思想的重要性:
編程語(yǔ)法是散落在各處的知識(shí)碎片,編程思想將它們串在一起形成清晰脈絡(luò);
編程知識(shí)碎片,學(xué)的死知識(shí),用的時(shí)候不知道選哪片,只能是瞎套用;
融入編程思想的編程語(yǔ)法,能夠
引領(lǐng)你更快地、真正地理解和學(xué)會(huì)編程語(yǔ)法;
讓你遇到問(wèn)題知道從哪里開(kāi)始思考,往哪個(gè)方向去思考;
我首創(chuàng)提出的?tidyverse
優(yōu)雅數(shù)據(jù)編程思想:

下面分別加以介紹,先設(shè)置數(shù)據(jù)文件根路徑、加載包。
1. 向量化
向量化,就是同時(shí)操作一個(gè)向量/矩陣的所有數(shù)據(jù),對(duì)每個(gè)元素同時(shí)做相同操作
關(guān)鍵是要用整體考量的思維來(lái)思考、來(lái)表示運(yùn)算
比如考慮 n 元一次線性方程組:

若從整體的角度來(lái)考量,引入矩陣和向量:

則可以向量化表示為:

例1 實(shí)現(xiàn)歸一化算法
歸一化,是將數(shù)值向量線性放縮到[0,1], 一般還同時(shí)考慮指標(biāo)一致化,將正向指標(biāo)(值越大越好)和負(fù)向指標(biāo)(值越小越好)都變成正向。
正向指標(biāo):

負(fù)向指標(biāo):

借助簡(jiǎn)單實(shí)例?x=[4,1,5,8]
, 問(wèn)題已足夠簡(jiǎn)單不需分解。
若是正向指標(biāo),按第 1 個(gè)公式,按編程語(yǔ)法翻譯成代碼(要有意識(shí)地摒棄
for i in 1:4
的逐元素迭代想法):

這里用到兩種常用的向量化計(jì)算:
min(x)
: 函數(shù)作用在向量上(幾乎所有自帶R函數(shù)都是這么設(shè)計(jì)的)x - min(x)
: 向量減去標(biāo)量(向量的每個(gè)元素都減去該標(biāo)量)
另外,還有一種常用,比如:x * y
(每個(gè)元素分別做乘法)
還有,若是負(fù)向指標(biāo),需要分支結(jié)構(gòu):
2. 自定義函數(shù)
要做一件事,拿一個(gè)簡(jiǎn)單實(shí)例,調(diào)試通過(guò);再改寫成一個(gè)函數(shù),就可以一步到位、反復(fù)用、批量用、給別人用。
R中函數(shù)一般形式為:
你只要把輸入給它,它就能在內(nèi)部進(jìn)行相應(yīng)處理,把你想要的返回值給你。
定義函數(shù)就好比創(chuàng)造一個(gè)模具,調(diào)用函數(shù)就好比用模具批量生成產(chǎn)品。
例2 將歸一化封裝為函數(shù)

3. 泛函式循環(huán)迭代
循環(huán)迭代,就是依次對(duì)序列的每個(gè)元素,重復(fù)做同樣的事情,并保存結(jié)果。
做一件事情,就是一個(gè)代碼段,封裝起來(lái)就是自定義一個(gè)函數(shù)
循環(huán)迭代,本質(zhì)上就是將一個(gè)函數(shù)依次應(yīng)用(映射)到序列的每一個(gè)元素上,表示出來(lái)即map(x, f)
兩點(diǎn)說(shuō)明:
序列:由一系列可以根據(jù)位置索引的元素構(gòu)成,元素可以很復(fù)雜和不同類型;向量、列表、數(shù)據(jù)框都是序列
將
x
作為第一個(gè)參數(shù),是便于使用管道
purrr
泛函式編程解決循環(huán)迭代問(wèn)題的邏輯:
針對(duì)序列每個(gè)單獨(dú)的元素,怎么處理它得到正確的結(jié)果,將之定義為函數(shù),再
map
到序列中的每一個(gè)元素,將得到的多個(gè)結(jié)果(每個(gè)元素作用后返回一個(gè)結(jié)果)打包到一起返回,并且可以根據(jù)想讓結(jié)果返回什么類型選用 map 后綴。
循環(huán)迭代返回類型的控制:
map_chr, map_lgl, map_dbl, map_int
: 返回相應(yīng)類型向量map_dfr, map_dfc
: 返回?cái)?shù)據(jù)框列表,再按行、按列合并為一個(gè)數(shù)據(jù)框
purrr
風(fēng)格公式(匿名函數(shù)):函數(shù)參數(shù).f
的一種簡(jiǎn)寫;只需要寫清楚它是如何操作序列參數(shù).x
(代表一個(gè)元素)的
一元函數(shù)序列參數(shù)為
.x
, 例如

表示為
.f = ~ .x ^ 2 + 1
注:?這也是分解的思維,循環(huán)迭代要依次對(duì)序列中每個(gè)元素做某操作,只需要把對(duì)一個(gè)元素做的操作寫清楚(即.f
),剩下的交給map_*()
就行了。
map_*(.x, .f, ...)
: 依次應(yīng)用一元函數(shù).f
到一個(gè)序列.x
的每個(gè)元素,...
可設(shè)置.f
的其它參數(shù)
例3 在外面對(duì)數(shù)據(jù)框各列做歸一化
數(shù)據(jù)框是序列,第1個(gè)元素是第1列
df[[1]]
, 第2個(gè)元素是第2列df[[2]]
, ......

這是都當(dāng)成正向指標(biāo),若+, -, -, +
怎么辦?

例4 批量讀取數(shù)據(jù)并按行合并

獲取文件路徑
循環(huán)迭代讀取,同時(shí)合并結(jié)果

4. 管道
管道運(yùn)算符%>%
(magrittr
包)或|>
(R 4.1以來(lái)),通過(guò)管道將數(shù)據(jù)依次向前傳遞(從一個(gè)函數(shù)傳給另一個(gè)函數(shù)),從而依次變換你的數(shù)據(jù):
表示依次對(duì)數(shù)據(jù)進(jìn)行若干操作:先對(duì)數(shù)據(jù)x
進(jìn)行f
操作, 接著對(duì)結(jié)果數(shù)據(jù)進(jìn)行g
操作
使用管道的好處:
避免使用過(guò)多的中間變量
程序可讀性大大增強(qiáng):對(duì)數(shù)據(jù)集依次進(jìn)行一系列操作
數(shù)據(jù)默認(rèn)傳遞給下一個(gè)函數(shù)的第一參數(shù),否則需用.
代替數(shù)據(jù)
5. 數(shù)據(jù)思維I: 操作數(shù)據(jù)框的思維
將向量化和函數(shù)式(自定義函數(shù)+泛函式循環(huán)迭代)編程思維,納入到數(shù)據(jù)框中來(lái):
向量化編程同時(shí)操作一個(gè)向量的數(shù)據(jù),變成在數(shù)據(jù)框中操作一列的數(shù)據(jù),或者同時(shí)操作數(shù)據(jù)框的多列,甚至分別操作數(shù)據(jù)框每個(gè)分組的多列;
函數(shù)式編程變成為想做的操作自定義函數(shù)(或現(xiàn)成函數(shù)),再依次應(yīng)用到數(shù)據(jù)框的多個(gè)列上,以修改列或做匯總
記?。?strong>每次至少操作一列數(shù)據(jù)!
例5 在數(shù)據(jù)框中做歸一化
同時(shí)操作多列:across()
同時(shí)操作多列,只需要選擇要操作的列,對(duì)(每)一列上要做的操作,寫成函數(shù)

6. 數(shù)據(jù)思維II: 操作分解的思維
復(fù)雜數(shù)據(jù)操作都可以分解為若干簡(jiǎn)單的基本數(shù)據(jù)操作:
數(shù)據(jù)連接
數(shù)據(jù)重塑(變成整潔數(shù)據(jù):長(zhǎng)寬轉(zhuǎn)換、拆分/合并列等)
篩選行
排序行
選擇列
修改列
分組匯總
一旦完成問(wèn)題的梳理和分解,又熟悉每個(gè)基本數(shù)據(jù)操作,用“管道”流依次對(duì)數(shù)據(jù)做操作即可
例6 管道分解操作


問(wèn)題:查詢平均成績(jī)≥60分的學(xué)生學(xué)號(hào)、姓名和平均成績(jī)。
分解問(wèn)題:
先按學(xué)號(hào)分組匯總計(jì)算平均成績(jī)
然后根據(jù)判斷條件篩選行
再根據(jù)學(xué)號(hào)連接學(xué)生信息
最后選擇想要的列

7. 數(shù)據(jù)思維III: 數(shù)據(jù)分解的思維
分組操作:匯總/修改/篩選:想對(duì)數(shù)據(jù)框進(jìn)行分組,分別對(duì)每組數(shù)據(jù)做操作,整體來(lái)想這是不容易想透的復(fù)雜事情,實(shí)際上只需做group_by()
分組,然后把你要對(duì)一組數(shù)據(jù)做的操作實(shí)現(xiàn)
group_by + summarise
: 分組匯總,結(jié)果是“有幾個(gè)分組就有幾個(gè)觀測(cè)”group_by + mutate
: 分組修改,結(jié)果是“原來(lái)幾個(gè)樣本還是幾個(gè)觀測(cè)”group_by + filter/slice
: 分組篩選/切片,結(jié)果是“分別對(duì)每組取子集合一起”
例7 分組修改數(shù)據(jù)

問(wèn)題:分別計(jì)算每支股票的收盤價(jià)與前一天的差價(jià)。
分解的邏輯:只要對(duì)Stock分組,對(duì)一組數(shù)據(jù)(一支股票)怎么計(jì)算收盤價(jià)與前一天的差價(jià),就怎么寫代碼

例8 分組t檢驗(yàn)

8. 批量建模/計(jì)算
對(duì)數(shù)據(jù)做分組,批量地對(duì)每個(gè)分組建立同樣模型/做同樣計(jì)算,并提取和使用批量的模型/計(jì)算結(jié)果,這就是批量建模/計(jì)算。
“笨方法”手動(dòng)分割數(shù)據(jù)、寫for
循環(huán)實(shí)現(xiàn),再提取、合并結(jié)果也能實(shí)現(xiàn)。
數(shù)據(jù)思維做法更簡(jiǎn)潔優(yōu)雅,仍是納入到數(shù)據(jù)框中來(lái)做,用到嵌套數(shù)據(jù)框(列表列)+?mutate
修改列 +?map
迭代。
你只需要解決:一個(gè)數(shù)據(jù)上的建模/計(jì)算。
例9 批量線性回歸建模
分組嵌套,到嵌套數(shù)據(jù)框

查看每一個(gè)數(shù)據(jù), 經(jīng)常取出來(lái)調(diào)試用

調(diào)試在一個(gè)數(shù)據(jù)上做的事情:


放到數(shù)據(jù)框里面去做:

展開(kāi)嵌套

總結(jié)一下貫穿始終的分解思維:
解決無(wú)從下手的復(fù)雜問(wèn)題:分解為若干可上手的簡(jiǎn)單問(wèn)題
循環(huán)迭代:分解為把解決一個(gè)元素的過(guò)程寫成函數(shù),再
map
到一系列的元素復(fù)雜的數(shù)據(jù)操作:分解為若干簡(jiǎn)單數(shù)據(jù)操作,再用管道連接
操作多組數(shù)據(jù):分解為
group_by
分組+操作明白一組數(shù)據(jù)修改多列:分解為
across
選擇列+操作明白一列數(shù)據(jù)批量建模/計(jì)算:分組嵌套數(shù)據(jù)或重抽樣嵌套數(shù)據(jù)+調(diào)試明白對(duì)一個(gè)數(shù)據(jù)如何建模/計(jì)算
更多來(lái)自實(shí)際問(wèn)題的案例:https://zhuanlan.zhihu.com/p/467134727
關(guān)于base R
與tidyverse
我的觀點(diǎn):
沒(méi)有必要先學(xué)習(xí)
base R
, 強(qiáng)烈建議直接從tidyverse
入門R
語(yǔ)言編程,沒(méi)有base R
基礎(chǔ)效果更佳!我的R
書很少涉及base R
, 照樣涵蓋所有基本編程語(yǔ)法,不影響解決各種各樣的R
編程問(wèn)題。將
base R
當(dāng)作是一個(gè)R
包來(lái)看待,其中的某些好用函數(shù),完全可以使用。沒(méi)有必要搞
base R
與tidyverse
對(duì)立,Python
用戶從來(lái)都是熱烈擁抱numpy, pandas, matplotlib, sklearn
, 更易學(xué)、易用干嘛要排斥。近年來(lái),國(guó)內(nèi)不思進(jìn)取、墨守成規(guī)的堅(jiān)守
base R
, 已經(jīng)嚴(yán)重阻礙了國(guó)內(nèi)R
語(yǔ)言的發(fā)展,越來(lái)越小眾化、邊緣化,我希望更多的人特別是高校教師,能夠加入使用和推廣以tidyverse
為代表的R
新技術(shù)的行列!
附免費(fèi)學(xué)習(xí)資源:
我的R新書完整課件,免費(fèi)可在線運(yùn)行版(和鯨網(wǎng)):
第一部分 -?R基礎(chǔ)語(yǔ)法與tidyverse優(yōu)雅數(shù)據(jù)編程:
https://www.heywhale.com/mw/project/641dc0d4feb4fe02b2ce72f7
第二部分 -?R應(yīng)用統(tǒng)計(jì)與探索性數(shù)據(jù)分析:
https://www.heywhale.com/mw/project/6434d65f381d60467de08121