R的简单教程--02进阶-1
r-rbase华中科技大学–组学数据分析与可视化课程–大二上课程笔记第二部分,包括管道运算符、dplyr包、tidyr包、字符串处理、使用循环的各种方式、基础绘图等
写在前面:本篇内容及例子(除标注了参考文章/具体案例的部分,以及一些比较零碎的小知识点)均来自华中科技大学–组学数据分析与可视化课程–大二上 / 笔记中使用的数据 / 关于本课程的复习资料 / 课程作业答案 / 课程作业答案–中文注释版
管道运算符pipe
由magrittr
包提供
向右操作符
%>%
向右操作符(最常用),把左侧的数据或表达式,传递给右侧的函数调用或表达式进行运行,可以连续操作
快捷键:CTRL
+shift
+M
在右侧函数中,可以用.
指代传递来的数据,也可以省略不写,默认放入右侧(外层)函数的第一个参数位置
如果右侧是一个嵌套函数,我们要在内层函数中使用传递来的数据,就必须用.
指代
swiss %>% do( head(., n = 4 ) );
swiss %>% head(., n = 4 );
swiss %>% head( n = 4 );
swiss %>% do( head( n = 4 ) ); # x被传到了do函数内,head未接收到x,报错
向左操作符
%T>%
向左操作符,把左侧的数据传给右侧函数调用或表达式,并将左侧的数据作为结果
a %T>% func()
的结果为a
,而a %>% func()
的结果为func(a)
,它们都会执行一次func(a)
,只是a %T>% func()
整体的值不同
例:
res <-
rnorm(100) %>%
matrix(ncol = 2) %T>%
plot(); # 都会画图
res # 一个matrix
res2 <-
rnorm(100) %>%
matrix(ncol = 2) %T>%
plot();
res2 # 一个plot()图片
解释操作符
%$%
解释操作符,把左侧数据的属性名传给右侧,让右侧的调用函数直接通过名字,就可以获取左侧的数据
作用可类比attach()
和with()
mtcars %$%
cor.test( cyl, mpg ); # 相当于
attach( mtcars );
cor.test( cyl, mpg ); # 相当于
with( mtcars, cor.test( cyl, mpg ) );
复合赋值操作符
%<>%
复合赋值操作符,功能可类比%>%
,多了一项额外的操作,就是把结果写回到最左侧的对象(覆盖原来的值)
x<-rnorm(10);
x %>% abs %>% sort;
x; # 取绝对值、排序之后的结果并没有直接写到x
x %<>% abs %>% sort;
x; # 取绝对值、排序之后的结果直接覆盖掉了原来的x
在实际应用中,尽量使用方向明确的%>%
,而不使用其它方向不明确的pipe
%in%
判断它左边的向量是否在右边的向量中
a <- 1:10
b <- c(1,3,5,9)
a %in% b
# TRUE FALSE TRUE FALSE TRUE FALSE FALSE FALSE TRUE FALSE
即依次判断1在不在c(1,3,5,9)
中、2在不在c(1,3,5,9)
中、…
取a、b交集:
a[a %in% b]
与ifelse和which结合:
ifelse(a %in% 1, 1, 0) #若a的值包含在1里面,输出1,否者输出0
# 1 0 0 0 0 0 0 0 0 0
which(a %in% 1) #输出x包含在1中值的位置
# 1
dplyr包
类似于python中的pandas,能对dataframe/tb类型的数据做很方便的数据处理和分析操作
本节中使用的例子:aapl
表
arrange
arrange(a, b)
对a数据按照其中b列的值进行升序排序
arrange(a, -b)
对a数据按照其中b列的值进行降序排序
dplyr包的函数通常与管道运算符联用,写成a %>% arrange(-b)
的形式
例:
aapl %>% arrange(-Volume)
select
a %>% select(列名1,列名2,...)
选取a中指定的列
例:
aapl %>% select(Date, Close, Volume)
另外一种表达方式:a %>% select(-c(列名1,列名2,...))
排除这些列,选取剩下的列
aapl %>% select(-c("Open", "High", "Low"))
得到相同的结果
也可以传入一个函数判断式,用来选择符合条件的列名
starwars %>%
select( name, ends_with("color") );
表示选出以”color”结尾的列名
其它的操作:
-
select(data, starts_with("Sepal"))
选择以”Sepal”开头的所有列 -
select(data, Sepal.Length:Petal.Width)
选择”Sepal.Length”与”Petal.Width”之间的所有列 -
select(data, contains("."))
选择名称包含”.”的列 -
select(data, matches("正则表达式"))
选择名称与正则表达式匹配的列 -
select(data, num_range("x", 1:5))
选择名为x1、x2、x3、x4、x5的列 -
select(data, one_of(c("Species", "Genus")))
选择名称在一组名称中的列 -
select(data, everything())
选择每一列
filter
a %>% filter(条件表达式)
选取a中符合条件的行,条件表达式中一般用列名进行判断
例:
aapl %>% filter(Close>=150) # 选择close列>=150的行
aapl %>% filter((Close>=150) & (Close>Open)) # 选择close列>=150且close列值>open列值的行
mutate
a %>% mutate(新列1=列运算,...)
将现有的列经过计算后生成新列
例:
# 将High列减去Low列的结果定义为maxDif,并取log
aapl %>% mutate(maxDif = High-Low,
log_maxDif=log(maxDif))
aapl %>% mutate(n=row_number()) # 为每行数据添加行数标记
其它的mutate函数:
-
transmute(新列1=列运算,...)
类似mutate
,区别是它会删除使用过的就列 -
mutate_each(funs(列处理函数),c(列名1,列名2,...))
对每列/指定列执行给定的函数data %>% mutate_each(funs(as.numeric), c("dbl1","dbl2","dbl3"))
表示将”dbl1”,”dbl2”,”dbl3”三列变成数值型
summarise与group_by
a %>% group_by(列1,列2,...)
按指定列分组,有相同列值的行为同一组
a %>% summarise(新列1=列运算,...)
将多个值转换为单个值(通过mean平均值
、median中位数
、n_distinct计算每组行数
等操作),生成新列(总行数减少,通常与group_by
配合使用),结果中每一组的计算结果为一行
例:weather数据集
# 按city分组,计算温度的平均值,存入新列mean_temperature中
weather %>% group_by(city) %>% summarise(mean_temperature = mean(temperature))
当不指定分组,直接summarise时,按所有数据为一组,即计算所有数据的平均值等
weather %>% summarise(mean_temperature = mean(temperature))
更多的列运算:
其它取行操作
-
distinct(data)
删除重复行 -
sample_frac(data, 0.5, replace = TRUE)
随机选择部分行 -
sample_n(data, 10, replace = TRUE)
随机选择n行 -
slice(data, 10:15)
按位置选择行 -
top_n(data, n, 列名)
选择前n行,并按指定列排序(如果数据分组,则按组)
实际案例
成绩分析
给定tbgrades.melted
grades <- tibble( "Name" = c("Weihua Chen", "Mm Hu", "John Doe", "Jane Doe","Warren Buffet", "Elon Musk", "Jack Ma"),
"Occupation" = c("Teacher", "Student", "Teacher", "Student", rep( "Entrepreneur", 3 ) ),
"English" = sample( 60:100, 7 ),
"ComputerScience" = sample(80:90, 7),
"Biology" = sample( 50:100, 7),
"Bioinformatics" = sample( 40:90, 7));
grades.melted <- grades %>%
gather( course, grade, -Name, -Occupation, na.rm = T );
观察数据可以看到,每人都有很多门科目
-
求出每个人的平均成绩,并排序
grades.melted %>% group_by(Name, Occupation) %>% # 按姓名/职位分组,即选出每人的各科成绩 summarise( avg_grades = mean( grade )) %>% # 将结果保存在avg_grades列 arrange( -avg_grades ); # 降序排序
-
将每个人的分数排序
grades.melted %>% arrange( Name, -grade ); # 相当于先按Name排序,再排各Name中的grade
-
求出每个人的最好科目及其成绩(在上面的基础上)
grades.melted %>% arrange( Name, -grade ) %>% group_by( Name ) %>% # 按姓名分组 summarise( best_course = first( course ), # 每组best_course值为第一个course best_grade = first( grade ), # 每组best_grade值为第一个grade avg_grades = mean( grade ) )
为什么是第一个course/grade?因为在
arrange( Name, -grade )
已经对每人的成绩排好序了,第一个就是最高的成绩 -
对每个职业的平均成绩排序
grades.melted %>% group_by(Occupation) %>% # 按职业分组 summarise(avg_grade=mean(grade)) %>% # 取平均值 arrange(-avg_grade) # 对平均值排序
-
求出每个职业的最佳学科
grades.melted %>% group_by(Occupation,course) %>% # 同时要知道职业和学科,所以按occupation和course分组 summarise(avg_grade=mean(grade)) %>% # 先得到每个职业的每个学科的平均值,共3*4行 arrange(-avg_grade) %>% # 对平均值排序 summarise(best_course=first(course)) # 取每个职业的course列的第一个元素(最好的学科)为best_course列
starwars
使用R中自带数据集starwars
-
取出相关列,用于计算人物的BMI
starwars %>% select( name, height, mass ) %>% mutate( bmi = mass / ( (height / 100 ) ^ 2 ) ) ;
-
获取与颜色相关的列: hair_color, skin_color, eye_color
starwars %>% select( name, ends_with("color") );
-
挑选金发碧眼的人物
starwars %>% select( name, ends_with("color"), gender, species ) %>% filter( hair_color == "blond" & eye_color == "blue" )
-
挑选出所有人类,按BMI将他们分为三组,<18、18~25、>25,统计每组的人数并排序
一种较容易想到的方法:让计算出的bmi列只取
<18
、18~25
、>25
三个值,之后按bmi列分组统计数量data <- starwars %>% select(name,height,mass,species) %>% mutate( bmi = mass / ( (height / 100 ) ^ 2 ) ) %>% filter(species=="Human" & !is.na(bmi)) %>% select(bmi) data <- as.data.frame(data) #dataframe的[i,1]是一个值(vector),而tibble的是一个tibble for(i in 1:nrow(data)) # nrow获取行数 { if(data[i,1]<18&&data[i,1]>0) { data[i,1] <- "bmi<18" } else if(data[i,1]>=18&&data[i,1]<=25) { data[i,1] <- "18<=bmi<=25" } else if(data[i,1]>25) { data[i,1] <- "bmi>25" } # 必须加else,要不"bmi<18">25会判断为true } data <- as.tibble(data) %>% group_by(bmi) %>% summarise(num=n()) %>% # 统计每组行数(每个BMI区间人数) arrange(-num);
注:最后一步统计行数可以改成
data <- as.tibble(data) %>% count(bmi,sort = T)
Theoph
-
产生新列trend,其值为Time与Time列平均值的差
Theoph %>% filter(!is.na(Time)) %>% # 去除NA值 mutate(trend=Time-mean(Time)) %>% select(Time,trend)
-
产生新列weight_cat,其值根据Wt的取值范围而不同:
-
如果
Wt
> 76.2,为 ‘Super-middleweight’,否则 -
如果
Wt
> 72.57,为 ‘Middleweight’,否则 -
如果
Wt
> 66.68,为 ‘Light-middleweight’ -
其它值,为 ‘Welterweight’
Theoph %>% select(Wt) %>% mutate(weight_cat=c("1")) for(i in 1:nrow(res4)) { if(res4[[i,1]]>76.2) { res4[i,2] <- 'Super-middleweight' } else if(res4[[i,1]]>72.57) { res4[i,2] <- 'Middleweight' } else if(res4[[i,1]]>66.68) { res4[i,2] <- 'Light-middleweight' } else { res4[i,2] <- 'Welterweight' } }
-
基因分析
使用tbmouse.tibble
mouse.tibble <- read_delim( file = "./r-data/mouse_genes_biomart_sep2018.txt",
delim = "\t", quote = "" )
共6列:
例1:
-
处理行:将染色体限制在常染色体和XY上(去掉未组装的小片段)
-
处理行:将基因类型限制在 protein_coding, miRNA和 lincRNA 这三种
-
新增列:统计每条染色体上不同类型基因(protein_coding, miRNA, lincRNA)的数量
-
按染色体(正)、基因数量(倒)进行排序
mouse.tibble %>%
## 1.
filter( `Chromosome/scaffold name` %in% c( 1:19, "X", "Y" ) ) %>%
## 2.
filter( `Transcript type` %in% c( "protein_coding", "miRNA", "lincRNA" ) ) %>%
## 改变列的名称,方便后续处理列
select( CHR = `Chromosome/scaffold name`, TYPE = `Transcript type`,
GENE_ID = `Gene stable ID`,
GENE_LEN = `Transcript length (including UTRs and CDS)` ) %>%
## 3.
## 按染色体和基因类型分组,因为要统计"每条染色体上"不同类型基因
group_by( CHR, TYPE ) %>%
## 使用n_distinct计算数量,mean计算平均值
summarise( count = n_distinct( GENE_ID ), mean_len = mean( GENE_LEN ) ) %>%
## 4.
arrange( CHR , desc( count ) );
例2:
每个染色体上每种基因类型的数量、平均长度、最大和最小长度,挑出最长和最短的基因
mouse.tibble %>%
select( CHR = `Chromosome/scaffold name`,
TYPE = `Transcript type`,
GENE_ID = `Gene stable ID`,
GENE_LEN = `Transcript length (including UTRs and CDS)` ) %>% # 改变列名
arrange( -GENE_LEN ) %>% # 按照基因长度排序(从大到小)
group_by( CHR, TYPE ) %>% # 按照chr和type分组
summarise(count=n_distinct(GENE_ID),
mean_len=mean(GENE_LEN),
max_len=first(GENE_LEN),
min_len=last(GENE_LEN),
max_GENE=first(GENE_ID),
min_GENE=last(GENE_ID));
最后的summarise
:之前按基因长度排完序后,每组的GENE_LEN
的第一个元素就是最大值,GENE_ID
的第一个元素就是最大值对应的id。若没排序就用min
max
取最大最小值
例3:
去掉含有500以下基因的染色体,按染色体数量从高到低进行排序
mouse.tibble %>%
select( CHR = `Chromosome/scaffold name`,
TYPE = `Transcript type`,
GENE_ID = `Gene stable ID`,
GENE_LEN = `Transcript length (including UTRs and CDS)` ) %>%
group_by(CHR)%>%
summarise(count=n_distinct(GENE_ID)) %>%
filter(count>500) %>% # 选择count>500的行
arrange(-count) # 按count从大到小排序
tidyr包
用于数据的清理,其中最重要的就是长宽数据转换
grades_wide <- read_tsv(file = "./r-data/grades2.txt")
宽数据:列多行少,如
特点:自然、易理解,但不易处理、且稀疏时问题较大
长数据:列少行多,如
pivot_longer/wider系列
宽变长:pivot_longer(要改变的列, names_to, values_to)
-
names_to
把原列名变成一个新列的列名 -
values_to
原列对应的值变成的新列的列名
比如上例中实现转换的代码为:
grades_long <- grades_wide %>%
pivot_longer( - name, names_to = "course", values_to = "grade" )
-name
表示除了name
的剩下列,name
列不改变。原列名表示学科名,现在让它们都存到course
列中,grade
列表示每个course对应的值
pivot_longer
还可以接收values_drop_na = TRUE
参数,用于消除值为NA的行
长变宽:pivot_wider( names_from, values_from)
-
names_from
列名来自哪列 -
values_from
列的值来自哪列
比如上例中实现转换的代码为:
grades_wide <- grades_long %>%
pivot_wider( names_from = "course", values_from = "grade" )
例1:
mini_iris <- iris[ c(1, 51, 101), ];
宽变长:
mini_iris.longer <- mini_iris %>%
pivot_longer( - Species, names_to = "type", values_to = "dat" )
长变宽:
mini_iris.wider <- mini_iris.longer %>%
pivot_wider( names_from = "type", values_from = "dat" )
例2:较复杂,有3列以上的长数据
grades_long <- read_delim( file = "./r-data/grades3.txt", delim = "\t",
quote = "", col_names = T)
长变宽:
grades_wide <- grades_long %>%
pivot_wider( names_from = course, values_from = grade )
同之前的例子,默认没改的两列保留
宽变长:
grades_wide %>%
pivot_longer( ! c( name, class ),
names_to = "course", values_to = "grade",
values_drop_na = T )
!
加上一个vector表示其中元素是想保留的列,!
相当于前面的-name
中减号的作用
也可以:
grades_wide %>%
pivot_longer( bioinformatics:spanish,
names_to = "course", values_to = "grade",
values_drop_na = T )
表示想改变bioinf-spanish的列
gather和spread系列
宽变长:gather(合并后新列名, 合并后新列对应数值列名, 要合并的列)
长变宽:spread(新列名来自哪列, 新列值来自哪列)
例:
宽数据为
长数据为
宽变长:
long <- wide %>%
gather(Quarter, Revenue, Qtr.1 : Qtr.4)
-
Quarter
:合并后新列的列名 -
Revenue
:合并后新列对应数值列的列名 -
Qtr.1 : Qtr.4
:把原始数据中的Qtr.1列到Qtr.4列合并在一起
长变宽:
wide <- long %>%
spread(Quarter, Revenue)
separate()
将含有文字的一列,按照某种规律将文字分开至多列
用法:separate(data, col, into, sep = "[^[:alnum:]]+", remove=T, convert=F)
-
data
:为要处理的数据 -
col
:要分开的列名 -
into
:一个字符串vector,指定分开后的新列名 -
seq
:指定分隔符,默认为非字母数字字符 -
remove
:是否删除原始列 -
convert
:是否将分开后的列转换成相应的数据类型
原始数据:
data %>%
separate(Quarter, c("Time_Interval", "Interval_ID"));
分隔后数据:
表示将Quarter
列分隔成Time_Interval
和Interval_ID
两列,默认按照非字母数字字符切分列值
unite()
把分开的两列合并成一列,是separate()
函数的反向操作
用法:unite(data, 合并后新列名, 要合并的列1, 要合并的列2, ..., sep, remove=T)
-
data
:为要处理的数据 -
seq
:指定合并时连接多个列值的符号
data %>%
unite(Quarter, Time_Interval, Interval_ID, sep = ".");
就可以将上面的分隔后数据转回原始数据
字符串处理
R中自带函数
单双引号区别:如果想在字符串中写入单引号,就需要用单引号来包裹字符串,并用\'
来转义;字符串中可以有双引号,无需转义,单双引号包裹均可
多行字符串:r中可以直接按回车换行来表示多行字符串
( string3 <- "a multiline
string" );
# [1] "a multiline \nstring"
可以看到输出结果中有\n
表示回车换行
字符串长度:nchar(str)
,包含空格
nchar( c("a", "R for data science", NA) );
# [1] 1 18 NA
字符串拼接:paste
和paste0
,之前介绍过,这里只说一个比较特殊的地方,即它们的参数也可以是vector,进行循环调用
paste( c( "a", "b", "c" ), 2, sep = "|" );
# [1] "a|2" "b|2" "c|2"
paste0(2: 3, c("a", "b", "c")); # 以长的为准
# [1] "2a" "3b" "2c"
大小写转换:
toupper( letters[1:10] ); # 小写变大写
# [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J"
tolower( LETTERS[1:5] ); # 大写变小写
# [1] "a" "b" "c" "d" "e"
字符串拆分:strsplit(str, split)
按照split拆分,以vector形式返回;若str是一个字符串数组,则以list(vector[str])形式返回
字符串截取:substr(str, start, stop)
取[start, stop]位置的字符串,可以给该结果赋值以局部替换字符串
字符串查询:grep(pattern, x, ...)
在x中找pattern(可以是正则表达式),返回第一次出现的索引;若x是一个字符串数组,则返回所有出现pattern的字符串在数组中的索引
grepl
与它类似,只不过返回的是T/F,标识是否出现
字符串替换:gsub("目标字符", "替换字符", 对象)
可以使用正则表达式
stringr和stringi包
是常用的字符串处理包,其中函数常用str_
和stri_
开头来命名
函数 | 功能 |
---|---|
str_c | 字符串拼接 |
str_trim | 去掉字符串的空格和TAB(\t) |
str_sub | 截取字符串 |
str_subset | 返回匹配的字符串 |
str_count | 字符串计数 |
str_length | 字符串长度 |
str_sort | 字符串值排序 |
str_split | 字符串分割 |
str_detect | 检查匹配字符串的字符 |
str_match | 从字符串中提取匹配组 |
str_replace | 字符串替换 |
str_locate | 找到匹配的字符串的位置 |
str_extract | 从字符串中提取匹配字符 |
str_to_upper | 字符串转成大写 |
str_to_lower | 字符串转成小写 |
str_to_title | 字符串转成标题 |
其中str_match
、str_replace
、str_locate
、str_extract
都有_all
形式,表示操作整个字符串,如str_extract_all
就是从字符串中提取所有的匹配字符
-
str_c(..., sep = "", collapse = NULL)
-
sep: 用于字符串拼接,为字符串的分割符
-
collapse: 用于向量拼接,为向量字符串的分割
-
-
str_sub(string, start = 1L, end = -1L)
截取[start, stop]位置的字符串 -
str_subset(string, pattern)
返回匹配pattern的字符串 -
str_count(string, pattern = "")
计算字符串中指定字符的个数 -
str_replace(string, pattern, replacement)
替换字符串中的部分字符 -
str_detect(string, pattern)
检查字符串中是否包含指定字符,返回逻辑向量 -
str_locate(string, pattern)
返回匹配到的字符的位置 -
str_match(string, pattern)
返回匹配到的字符,以矩阵形式返回,如果string是字符串是字符串数组,就以矩阵列表形式返回 -
str_extract(string, pattern)
返回匹配到的字符,以数组形式返回,如果string是字符串是字符串数组,就以数组列表形式返回
其它
字符串比较:
-
使用
>
<
==
等直接进行比较,是按照字符的ASCII码"A" > "abc"; # FALSE "A" == "abc"; # FALSE "abc" < "abcd"; # TRUE
-
使用
pracma
包中的strcmp(s1, s2)
和strcmpi(s1, s2)
比较两个字符串/字符串数组是否相等,strcmpi是忽略大小写library(pracma); strcmp("a", "ab"); # FALSE strcmpi(c("a", "b"), c("B", "A")); # FALSE strcmpi(c("A", "B"), c("A", "B")); # TRUE
R中的循环
普通for循环
for(i in 可迭代对象){
# 循环体
}
例:计算指定df的行平均值
res1 <- vector("double", nrow(df));
# 初始化结果数组,生成长度为nrow(df)的元素为double类型的数组,每个元素默认值为0
for(row_idx in 1:nrow(df)){
res1[row_idx] <- mean(as.numeric(df[row_idx, ])); # 计算行平均值
}
另一种方法
res2 <- c(); # 初始化空数组
for(row_idx in 1:nrow(df)){
res2[length(res2) + 1] <- mean(as.numeric(df[row_idx, ])); # 不断往里面添加新结果
}
由于运行效率可能比较低,尽量使用for循环的替代:
rowMeans(df); # 行平均值
colMeans(df); # 列平均值
rowSums(df); # 行和
colSums(df); # 列和
apply系列函数
apply
apply(X, MARGIN, FUN, ...)
-
X:要计算的数据集
-
MARGIN:取
1
是计算行、2
是计算列、c(1,2)
是计算行和列 -
FUN:处理函数,可以是系统自带,也可以自己写。计算行/列时把行/列传入函数中
df <- tibble( a = rnorm(5), b = rnorm(5), c = rnorm(5));
df %>% apply( ., 1, median ); ## 取行的 median
df %>% apply( ., 2, median ); ## 取列的 median
df %>% apply( ., c(1,2), median ) ## 取both的 median
MARGIN=c(1,2)
是计算每个元素的平均值,结果的n行m列的元素就对应原数据集中的n行m列的元素的平均值,若使用的是median这种的,输出和原数据集相同
apply与自定义函数配合:
df %>% apply( ., 2, function(x) {
return( c( n = length(x), mean = mean(x), median = median(x) ) );
} ); ## 列的一些统计结果
注意:行操作大部分可以被dplyr包中的函数代替
tapply
tapply(X, INDEX, FUN = NULL)
-
X:要计算的数据
-
INDEX:将x按INDEX分组
-
FUN:处理函数
将mtcars中的mpg按cyl分组后,计算每组的平均值
tapply( mtcars$mpg, mtcars$cyl, mean )
# 也可以
mtcars %$% tapply( mpg, cyl, mean );
4 6 8
26.66364 19.74286 15.10000
还可以使用dplyr包的函数实现相同操作:
mtcars %>%
group_by( cyl ) %>%
summarise( mean = mean( mpg ) );
lapply和sapply
sapply(X, FUN)
lapply(X, FUN)
都是基于列的操作,它们执行的功能相同,只是lapply返回一个列表,sapply返回一个向量
可输入数据:
-
vector:每次取一个元素
-
data.frame、tibble、matrix:每次取一列
-
list:每次取一个成员
例1:输入tb,计算每列的平均值
df <- tibble( a = rnorm(5), b = rnorm(5), c = rnorm(5));
df %>% lapply( mean );
df %>% sapply( mean );
可以看到,当输入是tb这种二维矩阵格式时,与apply( x, 2, FUN)
类似
例2:输入list,计算每个元素的长度
list( a = 1:10, b = letters[1:5], c = LETTERS[1:8] ) %>%
sapply( function(x) { length(x) } );
a b c
10 5 8
purrr包
map
map(data, FUN)
如果data是tb等,就遍历每列;如果是list,就遍历每个元素,传入FUN函数中,将计算结果返回至一个list,对应上面的lapply
df <- tibble( a = rnorm(5), b = rnorm(5), c = rnorm(5));
df %>% map( summary ); # 对每一列都进行summary操作
对应sapply的map_
系列函数:
-
map_lgl()
返回一个逻辑型数组 -
map_int()
返回一个整数型数组 -
map_dbl()
返回一个浮点数型数组 -
map_chr()
返回一个字符串数组
df <- tibble( a = rnorm(5), b = rnorm(5), c = rnorm(5));
df %>% map_dbl( mean );
a b c
-0.135128127 0.141081189 0.002431413
注意,因为它们返回的都是数组,每列计算结果的返回值只能是单个值
# df %>% map_dbl( summary ); # 报错
df %>% sapply( summary ); # 不报错
map的高阶应用:
将mtcars按汽缸分类cyl
分组,计算燃油效率mpg与吨位wt的关系
mtcars %>%
split( .$cyl ) %>%
map( ~ cor.test( .$wt, .$mpg ) ) %>%
map_dbl( ~.$estimate );
4 6 8
-0.7131848 -0.6815498 -0.6503580
-
split(.$cyl)
将mtcars按cyl列分为三个tibble,存入一个list中.
在pipe中代表从上游传递而来的数据,这里的.
是mtcars,因此.$cyl
就代表mtcars$cyl
,下同 -
map(~cor.test(.$wt, .$mpg))
遍历上游传来的数据(三个tb),对每个tb使用处理函数cor.test(mtcars$wt, mtcars$mpg )
这里使用了
~
简写:-
正常写法:
map(function(df) { cor.test(df$wt, df$mpg)})
-
简写:
map(~cor.test(.$wt, .$mpg))
~
用于取代function(df)
,下同 -
-
map_dbl(~.$estimate)
这里使用map_dbl
进行数值提取,取出cor.test函数计算结果中的estimate
值
还可以这样写:
mtcars %>%
split( mtcars$cyl ) %>%
map(function(df) { cor.test(df$wt, df$mpg)}) %>%
map_dbl( function(eq) { eq$estimate} );
可以得到完全相同的结果
注意map和map_dbl传入函数中的df
和eq
都是形参,名称可以随意起
reduce
reduce(func, data, init=NULL)
-
data:可迭代对象(数组、列表等)
-
func:接收两个参数,有一个返回值的函数
表示先取前两个元素执行func操作,将计算结果与第三个元素赋值给func,进行第二次循环,如次迭代
- init:设置初始值,类型必须和data中对象相同。指定init后,第一次迭代就是第一个元素和init传入func中计算
例1:累加
x <- 1:5
Reduce("+", x) # 1+2+3+4+5=15
Reduce("+", x, init = 10) # 10+1+2+3+4+5=25
注意:+
也是一个函数
例2:取交集
vs <- list(
c(1, 3, 5, 6, 10),
c(1, 2, 3, 7, 8, 10),
c(1, 2, 3, 4, 8, 9, 10)
);
vs %>% reduce(intersect); # 1 3 10
例3:多个tb的合并
dfs <- list(
age = tibble(name = "John", age = 30),
sex = tibble(name = c("John", "Mary"), sex = c("M", "F")),
trt = tibble(name = "Mary", treatment = "A")
);
dfs %>% reduce(full_join);
与它类似的还有accumulate
函数,表示将每次执行func得到的结果存入一个新的数组中并返回
x <- 1:5;
x %>% accumulate(`+`);
# 1 3 6 10 15
初始值为1,存入数组中;第一次func执行结果为1+2=3,存入数组中;第二次func执行结果为3+3=6,存入数组中;…
基础绘图
画图函数
barplot
创建条形图
barplot(
data, # 一个向量/矩阵,表示每个柱子的高度,即频率或计数值
names.arg = NULL, # 一个向量,表示每个柱子的标签(名称)
beside = FALSE, # 当一组数据有多个时,是否将柱子并排显示(默认堆叠)
horiz = FALSE, # 是否绘制水平柱状图
col = NULL, # 柱子的填充颜色
border = NULL, # 柱子的边框颜色
main = NULL, # 标题
xlab = NULL, # x轴标签
ylab = NULL, # y轴标签
...
)
例1:
height <- c(10, 20, 15, 25);
barplot(height,
names.arg = c("A", "B", "C", "D"),
col = "skyblue",
main = "Bar Plot Example",
xlab = "Categories",
ylab = "Frequency"
);
例2:
准备数据:
data <- matrix( c(20, 30.1, 2, 45.8, 23, 14), nrow = 2, byrow = T );
[,1] [,2] [,3]
[1,] 20.0 30.1 2
[2,] 45.8 23.0 14
barplot(data);
barplot(data,
beside = T
);
boxplot
创建箱型图
boxplot(x, # 向量、数据框或者列表,为画图提供数据
range = 1.5, # 边界范围,通常是1.5倍的四分位距(IQR)
width = NULL, # 箱体的宽度
varwidth = FALSE, # 是否根据每个组的观测数量来调整箱体的宽度
notch = FALSE, # 是否在箱线图中显示缺口
outline = TRUE, # 是否绘制异常值的轮廓
names, # 指定每个箱线图的名称
plot = TRUE, # 是否绘制箱线图
border = par("fg"), # 边框颜色
col = "lightgray", # 填充颜色
log = "", # 是否对数据取对数:可以取"x"对x轴取对数、"y"对y轴取对数、"xy"同时对xy轴取对数
pars = list(boxwex = 0.8, staplewex = 0.5, outwex = 0.5), # 其他控制绘图的参数
ann = !add, # 是否显示注释信息
horizontal = FALSE, # 是否绘制水平箱线图
add = FALSE, # 是否将箱线图添加到已有的绘图上
at = NULL # 放置位置
)
例1:简单的箱型图
data <- c(10, 15, 20, 25, 30)
boxplot(data)
例2:复杂的箱线图
准备数据:
sales_data <- data.frame(
region = rep(c("North", "South", "East", "West"), each = 25),
product_type = rep(c("A", "B"), times = 50),
sales = rnorm(100, mean = 500, sd = 100)
);
# 绘制箱线图
boxplot(
sales ~ region, # 指定sales是因变量(纵轴),region是自变量(横轴)
data = sales_data,
col = "lightblue",
main = "Sales by Region",
xlab = "Region",
ylab = "Sales Amount"
);
pie
用于创建饼图,用于显示各类别数据在整体中的占比
pie(x, # 要绘制饼图的数据
labels = names(x), # 每个部分的标签
main = NULL, # 标题
col, # 填充颜色
border, # 边框颜色
clockwise = F, # 是否顺时针绘制饼图
init.angle # 初始角度
)
例:
data <- c(10, 20, 30, 40);
pie(data);
hist
创建直方图
hist(x, # 向量或因子
breaks = "Sturges", # 指定直方图的分箱方式:可以是一个数值向量来指定箱子的边界,也可以是一个表示分箱方法的字符串
freq = NULL, # 是否绘制频数直方图
probability = !freq, # 是否绘制概率密度直方图
include.lowest = TRUE, # 是否将最小值包括在第一个箱子中
right = TRUE, # 是否为左开右闭区间,反之为左开右闭
density = NULL, # 指定用于绘制密度曲线的密度值
angle = 45, # 柱子的角度
col = "lightgray", # 填充颜色
border = NULL, # 边界颜色
main = paste("Histogram of" , xname), # 标题
xlim = range(breaks), # x轴的范围
ylim = NULL, # y轴的范围
xlab = xname, # x轴的标签
ylab, # y轴的标签
axes = TRUE, # 是否绘制坐标轴
plot = TRUE, # 是否绘制图形
labels = FALSE, # 是否在柱子上显示标签
nclass = NULL, # 分成几个柱子(几个区间),如果指定此参数将忽略breaks参数
)
例:
data <- c(5, 8, 10, 12, 15, 18, 20, 22, 25, 28, 30, 32, 35, 38, 40);
hist(data, # 数据
breaks = 5, # 指定分组区间的个数
col = "skyblue", # 指定直方图的颜色
main = "Histogram of Data", # 指定标题
xlab = "Value", # 指定 x 轴标签
ylab = "Frequency", # 指定 y 轴标签
border = "black", # 指定直方图边界的颜色
xlim = c(0, 45), # 指定 x 轴的范围
ylim = c(0, 5), # 指定 y 轴的范围
las = 1 # 设置坐标轴标签方向为竖直,如不指定,y轴标签数字将是横向写的
);
plot(重点)
是一种用于创建图形的基本函数,能够根据输入数据生成多种类型的图表
plot(
x, y = NULL, # 绘图数据,x是横坐标的数据,y是纵坐标,如果只提供x,则y默认为x的索引
type = "p", # 图形的类型
xlim = NULL, ylim = NULL, # x轴和y轴的范围,如xlim=c(0, 10)设置x轴从0到10
log = "", # 坐标轴是否取对数,可以设置为"x"、"y"、"xy"
main = NULL, # 主标题
sub = NULL, # 副标题
xlab = NULL, ylab = NULL, # x轴和y轴的标签文本
ann = par("ann"), # 是否自动注释图形,包括绘制标题、坐标轴标签等
axes = TRUE, # 是否绘制坐标轴
frame.plot = axes, # 是否绘制图形边框
asp = NA, # 设置图形的纵横比
xgap.axis = NA, ygap.axis = NA, # 控制轴线的间隙
外观样式参数, # 详见下面
...
)
其它参数可输入命令? plot.default
查看
常见的type类型:
-
“p”只绘制点
-
“l”只绘制线
-
“b” 点和线都绘制
-
“c” 只绘制线段的一部分
-
“o” 点和线重叠
-
“h” 绘制垂直线
-
”s”和”S” 分别表示阶梯图,其中 “S” 为水平开始
-
“n” 不绘制任何图形
一个更直观的例子:
par( mfrow = c(2,4) ); # 在一张图上画2 x 4个子图
type_list <- c( "p", "l", "o", "b", "c", "s", "S", "h" );
for(type in type_list){
plot(1:5, # 数据点为(1,1),(2,2),...,(5,5)
type = type,
main = type,
axes = F, # 不画xy轴
xlab = "", ylab = "" # 不画xy轴标签
);
}
常用外观样式参数:
-
col
指定线条和点的颜色:可指定多个颜色,使每个点都有自己的颜色。如果颜色数量少于点的数量,会按照标准方式循环使用颜色,而线条将全部用第一个指定的颜色绘制 -
bg
指定开放符号的背景颜色,如在画散点图(points()
函数)时,可以用来设置数据点的背景色,也是可以让所有点有同一个背景色、或者每个点都有自己的背景色 -
pch
指定绘图字符或符号(点的形状),取值为一个整数: -
cex
指定绘图字符和符号相对于默认大小的缩放比例 -
lty
指定线条类型(实线、虚线、点线等) -
lwd
指定线条宽度
一个更直观的例子:
with(
swiss, # 表示plot函数内使用Swiss数据集
plot(
Education, Fertility, # x-Education列 y-Fertility列
type = "p", # 只绘制点
main = "Swiss data 1888", # 主标题
sub = "Socioeconomic indicators & Fertility", # 副标题
xlab = "Education", ylab = "Fertility", # xy轴标签col = "darkblue",
xlim = range(Education), ylim = range(Fertility), # xy轴范围
pch = 20, # 点的形状
col = 'darkblue' # 点的颜色
)
);
还可以:
with(
swiss,
plot(
Education, Fertility,
type = "p",
main = "Swiss data 1888",
sub = "Socioeconomic indicators & Fertility",
xlab = "Education", ylab = "Fertility",
xlim = range( Education ), ylim = range( Fertility ),
col = c("darkblue", "darkred"), # 使用多种颜色画点
pch = 0:25, # 使用多种形状画点
frame.plot = F, # 不画边框
log = "xy" # 对xy轴数据取对数
)
);
一些关于画图的基础知识
高低图层
-
high level:在图形设备上创建新的绘图
-
low level:向现有绘图中添加更多信息
high level plots:
-
通用画图函数 plot
-
hist
-
pie
-
barplot
-
boxplot
-
…
low level plots:
-
points 点图
-
lines 线图
-
abline 直线
-
polygon 多边形
-
legend 图例
-
title 标题
-
axis 轴
-
…
可以用add = TRUE
参数(如果可用)将high level函数强制转换为low level
例:
plot( 1:10, col = "red" ); # high level
points( sample(1:10, 10), col = "darkgreen", pch = 20 ); # low level
使用系统函数设定图形相关参数
par()
函数:显示或修改当前图形设备的参数
par( c( "mar", "bg" ) ); # 显示指定参数的值
par(); # 显示所有参数
它们都是列表形式
注意:par()
用于指定全局参数,因此在改变前尽量备份
oldpar <- par(); # 备份
par(oldpar); #恢复
常用图形参数:
-
margin
图形边距par(mar = c( 5.1, 4.1, 4.1, 2.1 )); # 设置新 martin
分别指定 下 -> 左 -> 上 -> 右 的边距,即从下面开始,顺时针移动,单位是text lines
也可以使用
par(mai = c( 5.1, 4.1, 4.1, 2.1 )); # 设置新 martin
它的单位是inch
-
mfrow
和mfcol
设定画多个子图(panel)例:画2x3共6个panel(两行三列)
oldpar <- par(); par( mai = c( 5.1, 4.1, 4.1, 2.1 )); # 设置新 martin par( mfrow=c(2,3) ); # 或者 par( mfcol=c(2,3) ); for( i in 1:6 ) plot( sample( 1:10, 10 ), main = i ); par(oldpar);
图形设备
指图形输出的设备,可以理解为保存格式
默认情况下,图形显示在显示器(Rstudio软件)上。如果我们想将画出的图保存到某个文件中:
-
pdf(file=文件路径, height, width)
-
png(file=文件路径, height, width)
-
jpeg(file=文件路径, height, width)
分别对应输出文件格式,默认文件名为Rplots.pdf
,height
和width
参数的单位是inch
使用方式:
pdf(file = "/path/to/dir/<file_name>.pdf", height = 5, width = 5); # 创建一个pdf文件
plot(1:10); # 作图
dev.off(); # 关闭设备
注意:
-
必须使用
dev.off()
关闭 -
如果运行多个high level作图命令,则会产生多页pdf
-
尽量使用pdf作为文件输出格式,因为PDF矢量图可无限放大而不失真(变成像素),且易于编辑