《R語(yǔ)言實(shí)戰(zhàn)》學(xué)習(xí)筆記 -- 第五章 高級(jí)數(shù)據(jù)管理

本章內(nèi)容
? 數(shù)學(xué)和統(tǒng)計(jì)函數(shù)
? 字符處理函數(shù)
? 循環(huán)和條件執(zhí)行
? 自編函數(shù)
? 數(shù)據(jù)整合與重塑

在第4章脾还,我們審視了R中基本的數(shù)據(jù)集處理方法伴箩,本章我們將關(guān)注一些高級(jí)話題。本章分為三個(gè)基本部分鄙漏。在第一部分中嗤谚,我們將快速瀏覽R中的多種數(shù)學(xué)、統(tǒng)計(jì)和字符處理函數(shù)泥张。為了讓這一部分的內(nèi)容相互關(guān)聯(lián)呵恢,我們先引入一個(gè)能夠使用這些函數(shù)解決的數(shù)據(jù)處理問題。在講解過這些函數(shù)以后媚创,再為這個(gè)數(shù)據(jù)處理問題提供一個(gè)可能的解決方案渗钉。

接下來,我們將講解如何自己編寫函數(shù)來完成數(shù)據(jù)處理和分析任務(wù)。首先鳄橘,我們將探索控制程序流程的多種方式声离,包括循環(huán)和條件執(zhí)行語(yǔ)句。然后瘫怜,我們將研究用戶自編函數(shù)的結(jié)構(gòu)术徊,以及在編寫完成如何調(diào)用它們。

最后鲸湃,我們將了解數(shù)據(jù)的整合和概述方法赠涮,以及數(shù)據(jù)集的重塑和重構(gòu)方法。在整合數(shù)據(jù)時(shí)暗挑,你可以使用任何內(nèi)建或自編函數(shù)來獲取數(shù)據(jù)的概述笋除,所以你在本章前兩部分中學(xué)習(xí)的內(nèi)容將會(huì)派上用場(chǎng)。

一個(gè)數(shù)據(jù)處理難題

要討論數(shù)值和字符處理函數(shù)炸裆,讓我們首先考慮一個(gè)數(shù)據(jù)處理問題垃它。一組學(xué)生參加了數(shù)學(xué)、科學(xué)和英語(yǔ)考試烹看。為了給所有學(xué)生確定一個(gè)單一的成績(jī)衡量指標(biāo)国拇,需要將這些科目的成績(jī)組合起來。

另外惯殊,你還想將前20%的學(xué)生評(píng)定為A酱吝,接下來20%的學(xué)生評(píng)定為B,依次類推靠胜。最后掉瞳,你希望按字母順序?qū)W(xué)生排序。數(shù)據(jù)如表5-1所示浪漠。

觀察此數(shù)據(jù)集陕习,馬上可以發(fā)現(xiàn)一些明顯的障礙。首先址愿,三科考試的成績(jī)是無法比較的该镣。由于它們的均值和標(biāo)準(zhǔn)差相去甚遠(yuǎn),所以對(duì)它們求平均值是沒有意義的响谓。你在組合這些考試成績(jī)之前损合,必須將其變換為可比較的單元。其次娘纷,為了評(píng)定等級(jí)嫁审,你需要一種方法來確定某個(gè)學(xué)生在前述得分上百分比排名。再次赖晶,表示姓名的字段只有一個(gè)律适,這讓排序任務(wù)復(fù)雜化了辐烂。為了正確地將其排序,需要將姓和名拆開捂贿。

以上每一個(gè)任務(wù)都可以巧妙地利用R中的數(shù)值和字符處理函數(shù)完成纠修。在講解完下一節(jié)中的各種函數(shù)之后,我們將考慮一套可行的解決方案厂僧,以解決這項(xiàng)數(shù)據(jù)處理難題扣草。

數(shù)值和字符處理函數(shù)

本節(jié)我們將綜述R中作為數(shù)據(jù)處理基石的函數(shù),它們可分為數(shù)值(數(shù)學(xué)颜屠、統(tǒng)計(jì)辰妙、概率)函數(shù)和字符處理函數(shù)。在闡述過每一類函數(shù)以后汽纤,我將為你展示如何將函數(shù)應(yīng)用到矩陣和數(shù)據(jù)框的列(變量)和行(觀測(cè))上(參見5.2.6節(jié))上岗。

數(shù)學(xué)函數(shù)

表5-2列出了常用的數(shù)學(xué)函數(shù)和簡(jiǎn)短的用例。

image.png

image.png

對(duì)數(shù)據(jù)做變換是這些函數(shù)的一個(gè)主要用途蕴坪。例如,你經(jīng)常會(huì)在進(jìn)一步分析之前將收入這種存在明顯偏倚的變量取對(duì)數(shù)敬锐。數(shù)學(xué)函數(shù)也被用作公式中的一部分背传,用于繪圖函數(shù)(例如 x 對(duì) sin(x) )和在輸出結(jié)果之前對(duì)數(shù)值做格式化。

表5-2中的示例將數(shù)學(xué)函數(shù)應(yīng)用到了標(biāo)量(單獨(dú)的數(shù)值)上台夺。當(dāng)這些函數(shù)被應(yīng)用于數(shù)值向量径玖、矩陣或數(shù)據(jù)框時(shí),它們會(huì)作用于每一個(gè)獨(dú)立的值颤介。例如梳星, sqrt(c(4, 16, 25)) 的返回值為 c(2,4, 5) 。

統(tǒng)計(jì)函數(shù)

常用的統(tǒng)計(jì)函數(shù)如表5-3所示滚朵,其中許多函數(shù)都擁有可以影響輸出結(jié)果的可選參數(shù)冤灾。舉例來說:y <- mean(x)提供了對(duì)象 x 中元素的算術(shù)平均數(shù),而:z <- mean(x, trim =0.05, na.rm=TRUE則提供了截尾平均數(shù)辕近,即丟棄了最大5%和最小5%的數(shù)據(jù)和所有缺失值后的算術(shù)平均數(shù)韵吨。請(qǐng)使用help() 了解以上每個(gè)函數(shù)和其參數(shù)的用法。

image.png

要了解這些函數(shù)的實(shí)戰(zhàn)應(yīng)用移宅,請(qǐng)參考代碼清單5-1归粉。這段代碼演示了計(jì)算某個(gè)數(shù)值向量的均值和標(biāo)準(zhǔn)差的兩種方式。

x <- c(1, 2, 3, 4, 5, 6, 7, 8)
mean(x)
sd(x)

n <- length(x)
meanx <- sum(x)/n
css <- sum((x - meanx)^2)
sdx <- sqrt(css / (n-1))
meanx
sdx

第二種方式中修正平方和( css )的計(jì)算過程是很有啟發(fā)性的:

(1) x 等于 c(1, 2, 3, 4, 5, 6, 7, 8) 漏峰, x 的平均值等于4.5( length(x) 返回了 x 中元素的數(shù)量)糠悼;

(2) (x – meanx) 從 x 的每個(gè)元素中減去了4.5,結(jié)果為 c(-3.5, -2.5, -1.5, -0.5, 0.5,1.5, 2.5, 3.5) 浅乔;

(3) (x – meanx)^2 將 (x - meanx) 的每個(gè)元素求平方倔喂,結(jié)果為 c(12.25, 6.25, 2.25,0.25, 0.25, 2.25, 6.25, 12.25) ;

(4) sum((x - meanx)^2) 對(duì) (x - meanx)^2) 的所有元素求和,結(jié)果為42滴劲。

R中公式的寫法和類似MATLAB的矩陣運(yùn)算語(yǔ)言有著許多共同之處攻晒。(我們將在附錄E中具體關(guān)注解決矩陣代數(shù)問題的方法。)

數(shù)據(jù)的標(biāo)準(zhǔn)化

默認(rèn)情況下班挖,函數(shù) scale() 對(duì)矩陣或數(shù)據(jù)框的指定列進(jìn)行均值為0鲁捏、標(biāo)準(zhǔn)差為1的標(biāo)準(zhǔn)化:

newdata <- scale(mydata)

要對(duì)每一列進(jìn)行任意均值和標(biāo)準(zhǔn)差的標(biāo)準(zhǔn)化,可以使用如下的代碼:

newdata <- scale(mydata)*SD + M

其中的 M 是想要的均值萧芙, SD 為想要的標(biāo)準(zhǔn)差给梅。在非數(shù)值型的列上使用 scale() 函數(shù)將會(huì)報(bào)錯(cuò)。

要對(duì)指定列而不是整個(gè)矩陣或數(shù)據(jù)框進(jìn)行標(biāo)準(zhǔn)化双揪,你可以使用這樣的代碼:

newdata <- transform(mydata, myvar = scale(myvar)*10+50)

此句將變量 myvar 標(biāo)準(zhǔn)化為均值50动羽、標(biāo)準(zhǔn)差為10的變量。你將在5.3節(jié)數(shù)據(jù)處理問題的解決方法中用到 scale() 函數(shù)渔期。

概率函數(shù)

你可能在疑惑為何概率函數(shù)未和統(tǒng)計(jì)函數(shù)列在一起运吓。(你真的對(duì)此有些困惑,對(duì)吧疯趟?)雖然根據(jù)定義拘哨,概率函數(shù)也屬于統(tǒng)計(jì)類,但是它們非常獨(dú)特信峻,應(yīng)獨(dú)立設(shè)一節(jié)進(jìn)行講解倦青。概率函數(shù)通常用來生成特征已知的模擬數(shù)據(jù),以及在用戶編寫的統(tǒng)計(jì)函數(shù)中計(jì)算概率值盹舞。

在R中产镐,概率函數(shù)形如 :

[dpqr]distribution_abbreviation()

其中第一個(gè)字母表示其所指分布的某一方面:

d = 密度函數(shù)(density)
p = 分布函數(shù)(distribution function)
q = 分位數(shù)函數(shù)(quantile function)
r = 生成隨機(jī)數(shù)(隨機(jī)偏差)

常用的概率函數(shù)列于表5-4中。

我們不妨先看看正態(tài)分布的有關(guān)函數(shù)踢步,以了解這些函數(shù)的使用方法癣亚。如果不指定一個(gè)均值和一個(gè)標(biāo)準(zhǔn)差,則函數(shù)將假定其為標(biāo)準(zhǔn)正態(tài)分布(均值為0贾虽,標(biāo)準(zhǔn)差為1)逃糟。密度函數(shù)( dnorm )、分布函數(shù)( pnorm )蓬豁、分位數(shù)函數(shù)( qnorm )和隨機(jī)數(shù)生成函數(shù)( rnorm )的使用示例見表5-5绰咽。

x <- pretty(c(-3, 3), 30)
y <- dnorm(x)
plot(x, y, type = "l", xlab = "NormalDeviate", ylab = "Density", yaxs = "i")
pnorm(1.96)
qnorm(0.9, mean = 500, sd = 100)
rnorm(50, mean = 50, sd = 10)

[圖片上傳失敗...(image-69f4c8-1577187687248)]

如果讀者對(duì) plot() 函數(shù)的選項(xiàng)不熟悉,請(qǐng)不要擔(dān)心地粪。這些選項(xiàng)在第11章中有詳述取募。 pretty()在本章稍后的表5-7中進(jìn)行了解釋。

設(shè)定隨機(jī)數(shù)種子

在每次生成偽隨機(jī)數(shù)的時(shí)候蟆技,函數(shù)都會(huì)使用一個(gè)不同的種子玩敏,因此也會(huì)產(chǎn)生不同的結(jié)果斗忌。你可以通過函數(shù) set.seed() 顯式指定這個(gè)種子,讓結(jié)果可以重現(xiàn)(reproducible)旺聚。代碼清單5-2給出了一個(gè)示例织阳。這里的函數(shù) runif() 用來生成0到1區(qū)間上服從均勻分布的偽隨機(jī)數(shù)。

> runif(5)
> [1] 0.03119642 0.51657814 0.86438179 0.74237510 0.69981268
> runif(5)
> [1] 0.07797611 0.90215220 0.46832530 0.28086833 0.86071339

通過手動(dòng)設(shè)定種子砰粹,就可以重現(xiàn)你的結(jié)果了唧躲。這種能力有助于我們創(chuàng)建會(huì)在未來取用的,以及可與他人分享的示例碱璃。

生成多元正態(tài)數(shù)據(jù)

在模擬研究和蒙特卡洛方法中弄痹,你經(jīng)常需要獲取來自給定均值向量和協(xié)方差陣的多元正態(tài)分布的數(shù)據(jù)。 MASS 包中的 mvrnorm() 函數(shù)可以讓這個(gè)問題變得很容易嵌器。其調(diào)用格式為:

mvrnorm(n, mean, sigma)

其中 n 是你想要的樣本大小肛真, mean 為均值向量,而 sigma 是方差?協(xié)方差矩陣(或相關(guān)矩陣)爽航。代
碼清單5-3從一個(gè)參數(shù)如下所示的三元正態(tài)分布中抽取500個(gè)觀測(cè)蚓让。

image.png

...

字符處理函數(shù)

數(shù)學(xué)和統(tǒng)計(jì)函數(shù)是用來處理數(shù)值型數(shù)據(jù)的,而字符處理函數(shù)可以從文本型數(shù)據(jù)中抽取信息岳掐,或者為打印輸出和生成報(bào)告重設(shè)文本的格式凭疮。舉例來說,你可能希望將某人的姓和名連接在一起串述,并保證姓和名的首字母大寫,抑或想統(tǒng)計(jì)可自由回答的調(diào)查反饋信息中含有穢語(yǔ)的實(shí)例

(instance)數(shù)量寞肖。一些最有用的字符處理函數(shù)見表5-6纲酗。

請(qǐng)注意,函數(shù) grep() 新蟆、 sub() 和 strsplit() 能夠搜索某個(gè)文本字符串( fixed=TRUE )或某個(gè)正則表達(dá)式( fixed=FALSE 觅赊,默認(rèn)值為 FALSE )。正則表達(dá)式為文本模式的匹配提供了一套清晰而簡(jiǎn)練的語(yǔ)法琼稻。例如吮螺,正則表達(dá)式:
^[hc]?at
可匹配任意以0個(gè)或1個(gè) h 或 c 開頭、后接 at 的字符串帕翻。因此鸠补,此表達(dá)式可以匹配hat、cat和at嘀掸,但不會(huì)匹配bat紫岩。要了解更多,請(qǐng)參考維基百科的regular expression(正則表達(dá)式)條目睬塌。

其他實(shí)用函數(shù)

表5-7中的函數(shù)對(duì)于數(shù)據(jù)管理和處理同樣非常實(shí)用泉蝌,只是它們無法清楚地劃入其他分類中歇万。

[圖片上傳失敗...(image-caf3a5-1577187687248)]

表中的最后一個(gè)例子演示了在輸出時(shí)轉(zhuǎn)義字符的使用方法。 \n 表示新行勋陪, \t 為制表符贪磺, \'為單引號(hào), \b 為退格诅愚,等等寒锚。(鍵入 ?Quotes 以了解更多。)例如呻粹,代碼:

name <- "Bob"
cat( "Hello", name, "\b.\n", "Isn\'t R", "\t", "GREAT?\n")

可生成:

Hello Bob.
Isn't R GREAT?

請(qǐng)注意第二行縮進(jìn)了一個(gè)空格壕曼。當(dāng) cat 輸出連接后的對(duì)象時(shí),它會(huì)將每一個(gè)對(duì)象都用空格分開等浊。這就是在句號(hào)之前使用退格轉(zhuǎn)義字符( \b )的原因腮郊。不然,生成的結(jié)果將是“Hello Bob .”筹燕。

在數(shù)值轧飞、字符串和向量上使用我們最近學(xué)習(xí)的函數(shù)是直觀而明確的,但是如何將它們應(yīng)用到矩陣和數(shù)據(jù)框上呢撒踪?這就是下一節(jié)的主題过咬。

將函數(shù)應(yīng)用于矩陣和數(shù)據(jù)框

R函數(shù)的諸多有趣特性之一,就是它們可以應(yīng)用到一系列的數(shù)據(jù)對(duì)象上制妄,包括標(biāo)量掸绞、向量、
矩陣耕捞、數(shù)組和數(shù)據(jù)框衔掸。代碼清單5-4提供了一個(gè)示例。

> a <- 5
> sqrt(a)
[1] 2.236068
> b <- c(1.243, 5.654, 2.99)
> round(b)
[1] 1 6 3
> c <- matrix(runif(12), nrow=3)
> c
[,1] [,2] [,3] [,4]
[1,] 0.4205 0.355 0.699 0.323
[2,] 0.0270 0.601 0.181 0.926
[3,] 0.6682 0.319 0.599 0.215
> log(c)
[,1] [,2] [,3] [,4]
[1,] -0.866 -1.036 -0.358 -1.130
[2,] -3.614 -0.508 -1.711 -0.077
[3,] -0.403 -1.144 -0.513 -1.538
> mean(c)
[1] 0.444

請(qǐng)注意俺抽,在代碼清單5-4中對(duì)矩陣 c 求均值的結(jié)果為一個(gè)標(biāo)量(0.444)敞映。函數(shù) mean() 求得的是矩陣中全部12個(gè)元素的均值。但如果希望求的是各行的均值或各列的均值呢磷斧?

R中提供了一個(gè) apply() 函數(shù)振愿,可將一個(gè)任意函數(shù)“應(yīng)用”到矩陣、數(shù)組弛饭、數(shù)據(jù)框的任何維度上冕末。 apply() 函數(shù)的使用格式為:

apply(x, MARGIN, FUN, ...)

其中, x 為數(shù)據(jù)對(duì)象孩哑, MARGIN 是維度的下標(biāo)栓霜, FUN 是由你指定的函數(shù),而 ... 則包括了任何想傳遞給 FUN 的參數(shù)横蜒。在矩陣或數(shù)據(jù)框中胳蛮, MARGIN=1 表示行销凑, MARGIN=2 表示列。請(qǐng)看以下例子仅炊。

> mydata <- matrix(rnorm(30), nrow=6)
> mydata
[,1] [,2] [,3] [,4] [,5]
[1,] 0.71298 1.368 -0.8320 -1.234 -0.790
[2,] -0.15096 -1.149 -1.0001 -0.725 0.506
[3,] -1.77770 0.519 -0.6675 0.721 -1.350
[4,] -0.00132 -0.308 0.9117 -1.391 1.558
[5,] -0.00543 0.378 -0.0906 -1.485 -0.350
[6,] -0.52178 -0.539 -1.7347 2.050 1.569
> apply(mydata, 1, mean)
[1] -0.155 -0.504 -0.511 0.154 -0.310 0.165
> apply(mydata, 2, mean)
[1] -0.2907 0.0449 -0.5688 -0.3442 0.1906
> apply(mydata, 2, mean, trim=0.2)
[1] -0.1699 0.0127 -0.6475 -0.6575 0.2312

首先生成了一個(gè)包含正態(tài)隨機(jī)數(shù)的6×5矩陣?斗幼。然后你計(jì)算了6行的均值?,以及5列的均值?抚垄。最后蜕窿,你計(jì)算了每列的截尾均值(在本例中,截尾均值基于中間60%的數(shù)據(jù)呆馁,最高和最低20%的值均被忽略)?桐经。FUN 可為任意R函數(shù),這也包括你自行編寫的函數(shù)(參見5.4節(jié))浙滤,所以 apply() 是一種很強(qiáng)大的機(jī)制阴挣。 apply() 可把函數(shù)應(yīng)用到數(shù)組的某個(gè)維度上,而 lapply() 和 sapply() 則可將函數(shù)應(yīng)用到列表(list)上纺腊。你將在下一節(jié)中看到 sapply() (它是 lapply() 的更好用的版本)的一個(gè)示例畔咧。

你已經(jīng)擁有了解決5.1節(jié)中數(shù)據(jù)處理問題所需的所有工具,現(xiàn)在揖膜,讓我們小試身手誓沸。

數(shù)據(jù)處理難題的一套解決方案

看起來有點(diǎn)繁瑣,但還是一步步地仔細(xì)看吧

5.1節(jié)中提出的問題是:將學(xué)生的各科考試成績(jī)組合為單一的成績(jī)衡量指標(biāo)壹粟,基于相對(duì)名次(前20%拜隧、下20%、等等)給出從A到F的評(píng)分趁仙,根據(jù)學(xué)生姓氏和名字的首字母對(duì)花名冊(cè)進(jìn)行排序虹蓄。

代碼清單5-6給出了一種解決方案。

# 原始的學(xué)生花名冊(cè)已經(jīng)給出了幸撕,options(digits=2) 
# 限定了輸出小數(shù)點(diǎn)后數(shù)字的位數(shù),并且讓輸出更容易閱讀
options(digits = 2)

# 創(chuàng)建數(shù)據(jù)
Student <- c("John Davis", "Angela Williams", "Bullwinkle Moose", "David Jones", 
             "Janice Markhammer", "Cheryl Cushing", "Reuven Ytzrhak", "Greg Knox", "Joel England",
             "Mary Rayburn")
Math <- c(502, 600, 412, 358, 495, 512, 410, 625, 573, 522)
Science <- c(95, 99, 80, 82, 75, 85, 80, 95, 89, 86)
English <- c(25, 22, 18, 15, 20, 28, 15, 30, 37, 18)
roster <- data.frame(Student, Math, Science, English, stringsAsFactors = FALSE)
> roster
             Student Math Science English
1         John Davis  502      95      25
2    Angela Williams  600      99      22
3   Bullwinkle Moose  412      80      18
4        David Jones  358      82      15
5  Janice Markhammer  495      75      20
6     Cheryl Cushing  512      85      28
7     Reuven Ytzrhak  410      80      15
8          Greg Knox  625      95      30
9       Joel England  573      89      37
10      Mary Rayburn  522      86      18

# scale()標(biāo)準(zhǔn)化數(shù)據(jù)
z <- scale(roster[ , 2:4])
> z
        Math Science English
 [1,]  0.013   1.078    0.31
 [2,]  1.143   1.591   -0.11
 [3,] -1.026  -0.847   -0.67
 [4,] -1.649  -0.590   -1.09
 [5,] -0.068  -1.489   -0.39
 [6,]  0.128  -0.205    0.73
 [7,] -1.049  -0.847   -1.09
 [8,]  1.432   1.078    1.01
 [9,]  0.832   0.308    1.98
[10,]  0.243  -0.077   -0.67

# mean()來計(jì)算各行的均值以獲得綜合得分
score <- apply(z, 1, mean)
> score
 [1]  0.47  0.87 -0.85 -1.11 -0.65  0.22 -0.99  1.17  1.04 -0.17

# cbind()將均值列添加到花名冊(cè)中
roster <- cbind(roster, score)
> roster
             Student Math Science English score
1         John Davis  502      95      25  0.47
2    Angela Williams  600      99      22  0.87
3   Bullwinkle Moose  412      80      18 -0.85
4        David Jones  358      82      15 -1.11
5  Janice Markhammer  495      75      20 -0.65
6     Cheryl Cushing  512      85      28  0.22
7     Reuven Ytzrhak  410      80      15 -0.99
8          Greg Knox  625      95      30  1.17
9       Joel England  573      89      37  1.04
10      Mary Rayburn  522      86      18 -0.17

# quantile()求學(xué)生綜合得分的百分位數(shù)
y <- quantile(score, c(0.8, 0.6, 0.4, 0.2))
> y
  80%   60%   40%   20% 
 0.91  0.32 -0.36 -0.88

# 使用邏輯運(yùn)算符將學(xué)生的百分位數(shù)排名重編碼為一個(gè)新的類別型成績(jī)變量外臂。
# 下面在數(shù)據(jù)框roster中創(chuàng)建了變量grade 坐儿。
roster$grade[score >= y[1]] <- "A"
roster$grade[score < y[1] & score >= y[2]] <- "B"
roster$grade[score < y[2] & score >= y[3]] <- "C"
roster$grade[score < y[3] & score >= y[4]] <- "D"
roster$grade[score < y[4]] <- "F"
> roster
             Student Math Science English score grade
1         John Davis  502      95      25  0.47     B
2    Angela Williams  600      99      22  0.87     B
3   Bullwinkle Moose  412      80      18 -0.85     D
4        David Jones  358      82      15 -1.11     F
5  Janice Markhammer  495      75      20 -0.65     D
6     Cheryl Cushing  512      85      28  0.22     C
7     Reuven Ytzrhak  410      80      15 -0.99     F
8          Greg Knox  625      95      30  1.17     A
9       Joel England  573      89      37  1.04     A
10      Mary Rayburn  522      86      18 -0.17     C

#strsplit()函數(shù)以空格為分隔分割學(xué)生姓名
name <- strsplit((roster$Student), " ")

# sapply()函數(shù)分別取姓名的第一位和第二位,
# "["是一個(gè)可以提取某個(gè)對(duì)象的一部分的函數(shù)
# 在這里它是用來提取列表name各成分中的第一個(gè)或第二個(gè)元素的宋光。
lastname <- sapply(name, "[", 2)
firstname <- sapply(name, "[", 1)

# 將姓和名兩列加入列頭貌矿,并舍棄原來第一列的全名列
roster <- cbind(firstname, lastname, roster[, -1])
> roster
    firstname   lastname Math Science English score grade
1        John      Davis  502      95      25  0.47     B
2      Angela   Williams  600      99      22  0.87     B
3  Bullwinkle      Moose  412      80      18 -0.85     D
4       David      Jones  358      82      15 -1.11     F
5      Janice Markhammer  495      75      20 -0.65     D
6      Cheryl    Cushing  512      85      28  0.22     C
7      Reuven    Ytzrhak  410      80      15 -0.99     F
8        Greg       Knox  625      95      30  1.17     A
9        Joel    England  573      89      37  1.04     A
10       Mary    Rayburn  522      86      18 -0.17     C

# order() 依姓氏和名字對(duì)數(shù)據(jù)集進(jìn)行排序
roster <- roster[order(lastname, firstname),]
> roster
    firstname   lastname Math Science English score grade
6      Cheryl    Cushing  512      85      28  0.22     C
1        John      Davis  502      95      25  0.47     B
9        Joel    England  573      89      37  1.04     A
4       David      Jones  358      82      15 -1.11     F
8        Greg       Knox  625      95      30  1.17     A
5      Janice Markhammer  495      75      20 -0.65     D
3  Bullwinkle      Moose  412      80      18 -0.85     D
10       Mary    Rayburn  522      86      18 -0.17     C
2      Angela   Williams  600      99      22  0.87     B
7      Reuven    Ytzrhak  410      80      15 -0.99     F

控制流

在正常情況下,R程序中的語(yǔ)句是從上至下順序執(zhí)行的罪佳。但有時(shí)你可能希望重復(fù)執(zhí)行某些語(yǔ)句逛漫,僅在滿足特定條件的情況下執(zhí)行另外的語(yǔ)句。這就是控制流結(jié)構(gòu)發(fā)揮作用的地方了赘艳。R擁有一般現(xiàn)代編程語(yǔ)言中都有的標(biāo)準(zhǔn)控制結(jié)構(gòu)酌毡。首先你將看到用于條件執(zhí)行的結(jié)構(gòu)克握,接下來是用于循環(huán)執(zhí)行的結(jié)構(gòu)。

為了理解貫穿本節(jié)的語(yǔ)法示例枷踏,請(qǐng)牢記以下概念:

  • 語(yǔ)句( statement )是一條單獨(dú)的R語(yǔ)句或一組復(fù)合語(yǔ)句(包含在花括號(hào) { } 中的一組R語(yǔ)句菩暗,使用分號(hào)分隔);
  • 條件( cond )是一條最終被解析為真( TRUE )或假( FALSE )的表達(dá)式旭蠕;
  • 表達(dá)式( expr )是一條數(shù)值或字符串的求值語(yǔ)句停团;
  • 序列( seq )是一個(gè)數(shù)值或字符串序列。
  • 在討論過控制流的構(gòu)造后掏熬,我們將學(xué)習(xí)如何編寫函數(shù)佑稠。

重復(fù)和循環(huán)

循環(huán)結(jié)構(gòu)重復(fù)地執(zhí)行一個(gè)或一系列語(yǔ)句,直到某個(gè)條件不為真為止旗芬。循環(huán)結(jié)構(gòu)包括 for 和
while 結(jié)構(gòu)舌胶。

for 結(jié)構(gòu)

for 循環(huán)重復(fù)地執(zhí)行一個(gè)語(yǔ)句,直到某個(gè)變量的值不再包含在序列 seq 中為止岗屏。語(yǔ)法為:

for (var in seq) statement

在下例中:

for (i in 1:10) print("Hello")

單詞Hello被輸出了10次辆琅。

while 結(jié)構(gòu)

while 循環(huán)重復(fù)地執(zhí)行一個(gè)語(yǔ)句,直到條件不為真為止这刷。語(yǔ)法為:

while (cond) statement

作為第二個(gè)例子婉烟,代碼:

i <- 10
while (i > 0) {print("Hello"); i <- i - 1}

又將單詞Hello輸出了10次。請(qǐng)確保括號(hào)內(nèi) while 的條件語(yǔ)句能夠改變暇屋,即讓它在某個(gè)時(shí)刻不再為真——否則循環(huán)將永不停止似袁!在上例中,語(yǔ)句:i <- i – 1在每步循環(huán)中為對(duì)象 i 減去1咐刨,這樣在十次循環(huán)過后昙衅,它就不再大于0了。反之定鸟,如果在每步循環(huán)都加1的話而涉,R將不停地打招呼。這也是 while 循環(huán)可能較其他循環(huán)結(jié)構(gòu)更危險(xiǎn)的原因联予。

在處理大數(shù)據(jù)集中的行和列時(shí)啼县,R中的循環(huán)可能比較低效費(fèi)時(shí)。只要可能沸久,最好聯(lián)用R中的內(nèi)建數(shù)值/字符處理函數(shù)和 apply 族函數(shù)季眷。

條件執(zhí)行

在條件執(zhí)行結(jié)構(gòu)中,一條或一組語(yǔ)句僅在滿足一個(gè)指定條件時(shí)執(zhí)行卷胯。條件執(zhí)行結(jié)構(gòu)包括if-else 子刮、 ifelse 和 switch 。

if-else 結(jié)構(gòu)

控制結(jié)構(gòu) if-else 在某個(gè)給定條件為真時(shí)執(zhí)行語(yǔ)句窑睁。也可以同時(shí)在條件為假時(shí)執(zhí)行另外的語(yǔ)
句挺峡。語(yǔ)法為:

if (cond) statement
if (cond) statement1 else statement2

示例如下:

if (is.character(grade)) grade <- as.factor(grade)
if (!is.factor(grade)) grade <- as.factor(grade) else print("Grade already
is a factor")

在第一個(gè)實(shí)例中葵孤,如果 grade 是一個(gè)字符向量,它就會(huì)被轉(zhuǎn)換為一個(gè)因子沙郭。在第二個(gè)實(shí)例中佛呻,兩個(gè)語(yǔ)句擇其一執(zhí)行。如果 grade 不是一個(gè)因子(注意符號(hào) ! )病线,它就會(huì)被轉(zhuǎn)換為一個(gè)因子吓著。如果它是一個(gè)因子,就會(huì)輸出一段信息送挑。

ifelse 結(jié)構(gòu)

ifelse 結(jié)構(gòu)是 if-else 結(jié)構(gòu)比較緊湊的向量化版本绑莺,其語(yǔ)法為:

ifelse(cond, statement1, statement2)

若 cond 為 TRUE ,則執(zhí)行第一個(gè)語(yǔ)句惕耕;若 cond 為 FALSE 纺裁,則執(zhí)行第二個(gè)語(yǔ)句。示例如下:

ifelse(score > 0.5, print("Passed"), print("Failed"))
outcome <- ifelse (score > 0.5, "Passed", "Failed")

在程序的行為是二元時(shí)司澎,或者希望結(jié)構(gòu)的輸入和輸出均為向量時(shí)欺缘,請(qǐng)使用 ifelse 。

switch 結(jié)構(gòu)

switch 根據(jù)一個(gè)表達(dá)式的值選擇語(yǔ)句執(zhí)行挤安。語(yǔ)法為:

switch(expr, ...)

其中的 ... 表示與 expr 的各種可能輸出值綁定的語(yǔ)句谚殊。通過觀察代碼清單5-7中的代碼,可以輕松地理解 switch 的工作原理蛤铜。

feelings <- c("sad", "afraid")
for(i in feelings)
  print(
    switch(i,
           happy  = "I am glad you are happy",
           afraid = "There is nothing to fear",
           sad    = "Cheer up",
           angry  = "Calm down now"
           )
  )
[1] "Cheer up"
[1] "There is nothing to fear"

雖然這個(gè)例子比較幼稚嫩絮,但它展示了 switch 的主要功能。你將在下一節(jié)學(xué)習(xí)如何使用switch 編寫自己的函數(shù)围肥。

用戶自編函數(shù)

R的最大優(yōu)點(diǎn)之一就是用戶可以自行添加函數(shù)剿干。事實(shí)上,R中的許多函數(shù)都是由已有函數(shù)構(gòu)成的穆刻。一個(gè)函數(shù)的結(jié)構(gòu)看起來大致如此:

myfunction <- function(arg1, arg2, ... ){
statements
return(object)
}

函數(shù)中的對(duì)象只在函數(shù)內(nèi)部使用置尔。返回對(duì)象的數(shù)據(jù)類型是任意的,從標(biāo)量到列表皆可氢伟。讓我們看一個(gè)示例撰洗。

mydate <- function(type){
  switch(type,
         long = format(Sys.time(), "%A %B %d %Y"),
         short = format(Sys.time(), "%m-%d-%y"),
         cat(type, "is not a recognized type\n")
  )
}
> mydate("long")
[1] "星期六 十二月 07 2019"
> mydate("short")
[1] "12-07-19"
> mydate("aoe")
aoe is not a recognized type

請(qǐng)注意,函數(shù) cat() 僅會(huì)在輸入的日期格式類型不匹配 "long" 或 "short" 時(shí)執(zhí)行腐芍。使用一個(gè)表達(dá)式來捕獲用戶的錯(cuò)誤輸入的參數(shù)值通常來說是一個(gè)好主意。

有若干函數(shù)可以用來為函數(shù)添加錯(cuò)誤捕獲和糾正功能试躏。你可以使用函數(shù) warning() 來生成一條錯(cuò)誤提示信息猪勇,用 message() 來生成一條診斷信息,或用 stop() 停止當(dāng)前表達(dá)式的執(zhí)行并提示錯(cuò)誤颠蕴。20.5節(jié)將會(huì)更加詳細(xì)地討論錯(cuò)誤捕捉和調(diào)試泣刹。

在創(chuàng)建好自己的函數(shù)以后助析,你可能希望在每個(gè)會(huì)話中都能直接使用它們。附錄B描述了如何定制R環(huán)境椅您,以使R啟動(dòng)時(shí)自動(dòng)讀取用戶編寫的函數(shù)外冀。我們將在第6章和第8章中看到更多的用戶自編函數(shù)示例。

你可以使用本節(jié)中提供的基本技術(shù)完成很多工作掀泳。第20章的內(nèi)容更加詳細(xì)地涵蓋了控制流和其他編程主題雪隧。第21章涵蓋了如何創(chuàng)建包。如果你想要探索編寫函數(shù)的微妙之處员舵,或編寫可以分發(fā)給他人使用的專業(yè)級(jí)代碼脑沿,個(gè)人推薦閱讀這兩章,然后閱讀兩本優(yōu)秀的書籍马僻,你可在本書末尾的參考文獻(xiàn)部分找到:Venables & Ripley(2000)以及Chambers(2008)庄拇。這兩本書共同提供了大量細(xì)節(jié)和眾多示例。

函數(shù)的編寫就講到這里韭邓,我們將以對(duì)數(shù)據(jù)整合和重塑的討論來結(jié)束本章措近。

整合與重構(gòu)

R中提供了許多用來整合(aggregate)和重塑(reshape)數(shù)據(jù)的強(qiáng)大方法。在整合數(shù)據(jù)時(shí)女淑,往往將多組觀測(cè)替換為根據(jù)這些觀測(cè)計(jì)算的描述性統(tǒng)計(jì)量瞭郑。在重塑數(shù)據(jù)時(shí),則會(huì)通過修改數(shù)據(jù)的結(jié)構(gòu)(行和列)來決定數(shù)據(jù)的組織方式诗力。本節(jié)描述了用來完成這些任務(wù)的多種方式凰浮。

在接下來的兩個(gè)小節(jié)中,我們將使用已包含在R基本安裝中的數(shù)據(jù)框 mtcars 苇本。這個(gè)數(shù)據(jù)集是從Motor Trend雜志(1974)提取的袜茧,它描述了34種車型的設(shè)計(jì)和性能特點(diǎn)(汽缸數(shù)、排量瓣窄、馬力笛厦、每加侖汽油行駛的英里數(shù),等等)俺夕。要了解此數(shù)據(jù)集的更多信息裳凸,請(qǐng)參閱 help(mtcars) 。

轉(zhuǎn)置

轉(zhuǎn)置(反轉(zhuǎn)行和列)也許是重塑數(shù)據(jù)集的眾多方法中最簡(jiǎn)單的一個(gè)了劝贸。使用函數(shù) t() 即可對(duì)一個(gè)矩陣或數(shù)據(jù)框進(jìn)行轉(zhuǎn)置堤框。對(duì)于后者,行名將成為變量(列)名嫉拐。代碼清單5-9展示了一個(gè)例子沃暗。

使用函數(shù) t() 即可對(duì)一個(gè)矩陣或數(shù)據(jù)框進(jìn)行轉(zhuǎn)置。

整合數(shù)據(jù)

在R中使用一個(gè)或多個(gè)by變量和一個(gè)預(yù)先定義好的函數(shù)來折疊(collapse)數(shù)據(jù)是比較容易的。調(diào)用格式為:

aggregate(x, by, FUN)

其中 x 是待折疊的數(shù)據(jù)對(duì)象捌议, by 是一個(gè)變量名組成的列表哼拔,這些變量將被去掉以形成新的觀測(cè),
而 FUN 則是用來計(jì)算描述性統(tǒng)計(jì)量的標(biāo)量函數(shù)瓣颅,它將被用來計(jì)算新觀測(cè)中的值倦逐。
作為一個(gè)示例,我們將根據(jù)汽缸數(shù)和擋位數(shù)整合 mtcars 數(shù)據(jù)宫补,并返回各個(gè)數(shù)值型變量的均
值(見代碼清單5-10)檬姥。

> mtcars
                    mpg cyl disp  hp drat  wt qsec vs am gear carb
Mazda RX4            21   6  160 110  3.9 2.6   16  0  1    4    4
Mazda RX4 Wag        21   6  160 110  3.9 2.9   17  0  1    4    4
Datsun 710           23   4  108  93  3.8 2.3   19  1  1    4    1
Hornet 4 Drive       21   6  258 110  3.1 3.2   19  1  0    3    1
Hornet Sportabout    19   8  360 175  3.1 3.4   17  0  0    3    2
Valiant              18   6  225 105  2.8 3.5   20  1  0    3    1
Duster 360           14   8  360 245  3.2 3.6   16  0  0    3    4
Merc 240D            24   4  147  62  3.7 3.2   20  1  0    4    2
Merc 230             23   4  141  95  3.9 3.1   23  1  0    4    2
Merc 280             19   6  168 123  3.9 3.4   18  1  0    4    4
Merc 280C            18   6  168 123  3.9 3.4   19  1  0    4    4
Merc 450SE           16   8  276 180  3.1 4.1   17  0  0    3    3
Merc 450SL           17   8  276 180  3.1 3.7   18  0  0    3    3
Merc 450SLC          15   8  276 180  3.1 3.8   18  0  0    3    3
Cadillac Fleetwood   10   8  472 205  2.9 5.2   18  0  0    3    4
Lincoln Continental  10   8  460 215  3.0 5.4   18  0  0    3    4
Chrysler Imperial    15   8  440 230  3.2 5.3   17  0  0    3    4
Fiat 128             32   4   79  66  4.1 2.2   19  1  1    4    1
Honda Civic          30   4   76  52  4.9 1.6   19  1  1    4    2
Toyota Corolla       34   4   71  65  4.2 1.8   20  1  1    4    1
Toyota Corona        22   4  120  97  3.7 2.5   20  1  0    3    1
Dodge Challenger     16   8  318 150  2.8 3.5   17  0  0    3    2
AMC Javelin          15   8  304 150  3.1 3.4   17  0  0    3    2
Camaro Z28           13   8  350 245  3.7 3.8   15  0  0    3    4
Pontiac Firebird     19   8  400 175  3.1 3.8   17  0  0    3    2
Fiat X1-9            27   4   79  66  4.1 1.9   19  1  1    4    1
Porsche 914-2        26   4  120  91  4.4 2.1   17  0  1    5    2
Lotus Europa         30   4   95 113  3.8 1.5   17  1  1    5    2
Ford Pantera L       16   8  351 264  4.2 3.2   14  0  1    5    4
Ferrari Dino         20   6  145 175  3.6 2.8   16  0  1    5    6
Maserati Bora        15   8  301 335  3.5 3.6   15  0  1    5    8
Volvo 142E           21   4  121 109  4.1 2.8   19  1  1    4    2

options(digits = 3)
attach(mtcars)
aggdata <- aggregate(mtcars, by = list(cyl, gear), FUN=mean, na.rm = TRUE)
detach(mtcars)
aggdata
  Group.1 Group.2  mpg cyl disp  hp drat   wt qsec  vs   am gear carb
1       4       3 21.5   4  120  97 3.70 2.46 20.0 1.0 0.00    3 1.00
2       6       3 19.8   6  242 108 2.92 3.34 19.8 1.0 0.00    3 1.00
3       8       3 15.1   8  358 194 3.12 4.10 17.1 0.0 0.00    3 3.08
4       4       4 26.9   4  103  76 4.11 2.38 19.6 1.0 0.75    4 1.50
5       6       4 19.8   6  164 116 3.91 3.09 17.7 0.5 0.50    4 4.00
6       4       5 28.2   4  108 102 4.10 1.83 16.8 0.5 1.00    5 2.00
7       6       5 19.7   6  145 175 3.62 2.77 15.5 0.0 1.00    5 6.00
8       8       5 15.4   8  326 300 3.88 3.37 14.6 0.0 1.00    5 6.00

在結(jié)果中, Group.1 表示汽缸數(shù)量(4守谓、6或8)穿铆, Group.2 代表?yè)跷粩?shù)(3、4或5)斋荞。舉例來說荞雏,擁有4個(gè)汽缸和3個(gè)擋位車型的每加侖汽油行駛英里數(shù)( mpg )均值為21.5。

在使用 aggregate() 函數(shù)的時(shí)候平酿, by 中的變量必須在一個(gè)列表中(即使只有一個(gè)變量)凤优。你可 以 在 列 表 中 為 各 組 聲 明 自 定 義 的 名 稱 , 例 如 by=list(Group.cyl=cyl, Group.gears=gear) 蜈彼。指定的函數(shù)可為任意的內(nèi)建或自編函數(shù)筑辨,這就為整合命令賦予了強(qiáng)大的力量。但說到力量幸逆,沒有什么可以比 reshape2 包更強(qiáng)棍辕。

reshape2 包

reshape2 包是一套重構(gòu)和整合數(shù)據(jù)集的絕妙的萬能工具。由于它的這種萬能特性还绘,可能學(xué)起來會(huì)有一點(diǎn)難度楚昭。我們將慢慢地梳理整個(gè)過程,并使用一個(gè)小型數(shù)據(jù)集作為示例拍顷,這樣每一步發(fā)生了什么就很清晰了抚太。由于 reshape2 包并未包含在R的標(biāo)準(zhǔn)安裝中,在第一次使用它之前需要使用 install.packages("reshape2") 進(jìn)行安裝昔案。

大致說來尿贫,你需要首先將數(shù)據(jù)融合(melt),以使每一行都是唯一的標(biāo)識(shí)符?變量組合踏揣。然后將數(shù)據(jù)重鑄(cast)為你想要的任何形狀庆亡。在重鑄過程中,你可以使用任何函數(shù)對(duì)數(shù)據(jù)進(jìn)行整合捞稿。將使用的數(shù)據(jù)集如表5-8所示身冀。

用處不大感覺钝尸,先不講了。搂根。。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末铃辖,一起剝皮案震驚了整個(gè)濱河市剩愧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌娇斩,老刑警劉巖仁卷,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異犬第,居然都是意外死亡锦积,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門歉嗓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丰介,“玉大人,你說我怎么就攤上這事鉴分∠保” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵志珍,是天一觀的道長(zhǎng)橙垢。 經(jīng)常有香客問我,道長(zhǎng)伦糯,這世上最難降的妖魔是什么柜某? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮敛纲,結(jié)果婚禮上喂击,老公的妹妹穿的比我還像新娘。我一直安慰自己载慈,他們只是感情好惭等,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著办铡,像睡著了一般辞做。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上寡具,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天秤茅,我揣著相機(jī)與錄音,去河邊找鬼童叠。 笑死框喳,一個(gè)胖子當(dāng)著我的面吹牛课幕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播五垮,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼乍惊,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了放仗?” 一聲冷哼從身側(cè)響起润绎,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎诞挨,沒想到半個(gè)月后莉撇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惶傻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年棍郎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片银室。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涂佃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出粮揉,到底是詐尸還是另有隱情巡李,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布扶认,位于F島的核電站侨拦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏辐宾。R本人自食惡果不足惜狱从,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望叠纹。 院中可真熱鬧季研,春花似錦、人聲如沸誉察。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)持偏。三九已至驼卖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸿秆,已是汗流浹背酌畜。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卿叽,地道東北人桥胞。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓恳守,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親贩虾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子催烘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容