加载页面中...
R的简单教程–02进阶-1 | lwstkhyl

R的简单教程--02进阶-1

华中科技大学–组学数据分析与可视化课程–大二上课程笔记第二部分,包括管道运算符、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

dplyr包例

arrange

arrange(a, b)对a数据按照其中b列的值进行升序排序

arrange(a, -b)对a数据按照其中b列的值进行降序排序

dplyr包的函数通常与管道运算符联用,写成a %>% arrange(-b)的形式

例:

aapl %>% arrange(-Volume)

arrange例

select

a %>% select(列名1,列名2,...)选取a中指定的列

例:

aapl %>% select(Date, Close, Volume)

select例

另外一种表达方式: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的行

filter例

aapl %>% filter((Close>=150) & (Close>Open))  # 选择close列>=150且close列值>open列值的行

filter例2

mutate

a %>% mutate(新列1=列运算,...)将现有的列经过计算后生成新列

例:

# 将High列减去Low列的结果定义为maxDif,并取log
aapl %>% mutate(maxDif = High-Low,
                log_maxDif=log(maxDif)) 

mutate例

aapl %>% mutate(n=row_number())  # 为每行数据添加行数标记

mutate例1

其它的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数据集

summarise与group_by

# 按city分组,计算温度的平均值,存入新列mean_temperature中
weather %>% group_by(city) %>% summarise(mean_temperature = mean(temperature))

summarise与group_by例

当不指定分组,直接summarise时,按所有数据为一组,即计算所有数据的平均值等

weather %>% summarise(mean_temperature = mean(temperature))

summarise与group_by例2

更多的列运算:

列运算

其它取行操作

  • 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
    

    成绩分析1

  • 求出每个人的最好科目及其成绩(在上面的基础上)

    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 )已经对每人的成绩排好序了,第一个就是最高的成绩

    成绩分析2

  • 对每个职业的平均成绩排序

    grades.melted %>% 
      group_by(Occupation) %>%  # 按职业分组
      summarise(avg_grade=mean(grade)) %>%  # 取平均值
      arrange(-avg_grade)  # 对平均值排序
    

    成绩分析3

  • 求出每个职业的最佳学科

    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列
    

    成绩分析4

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列只取<1818~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);
    

    starwars

    注:最后一步统计行数可以改成

    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列:

基因分析

基因分析2

例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" )

pivot_wider


例1:

mini_iris <- iris[ c(1, 51, 101),  ];

mini_iris

宽变长:

mini_iris.longer <- mini_iris %>% 
  pivot_longer( - Species, names_to = "type", values_to = "dat" )

mini_iris_longer

长变宽:

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_long

长变宽:

grades_wide <- grades_long %>% 
  pivot_wider( names_from = course, values_from =  grade )

同之前的例子,默认没改的两列保留

grades_wide

宽变长:

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_IntervalInterval_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

字符串拼接pastepaste0,之前介绍过,这里只说一个比较特殊的地方,即它们的参数也可以是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_开头来命名

关于stringr的更多详细介绍

关于stringr和stringi的更多详细介绍

函数 功能
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_matchstr_replacestr_locatestr_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

apply函数结果

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) ) );
  } ); ## 列的一些统计结果

apply函数结果2

注意:行操作大部分可以被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 ) );

tapply函数结果

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 );

lapply和sapply函数结果

可以看到,当输入是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操作

map函数结果


对应sapplymap_系列函数:

  • 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函数结果2


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传入函数中的dfeq都是形参,名称可以随意起

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);

reduce函数结果


与它类似的还有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"
);

barplot

例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);

barplot2

barplot(data,
        beside = T
);

barplot3

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)

boxplot

例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)
);

boxplot2

# 绘制箱线图
boxplot(
  sales ~ region,  # 指定sales是因变量(纵轴),region是自变量(横轴)
  data = sales_data, 
  col = "lightblue", 
  main = "Sales by Region",
  xlab = "Region",
  ylab = "Sales Amount"
);

boxplot3

pie

用于创建饼图,用于显示各类别数据在整体中的占比

pie(x,  # 要绘制饼图的数据
  labels = names(x),  # 每个部分的标签
  main = NULL,  # 标题
  col,  # 填充颜色
  border,  # 边框颜色
  clockwise = F,  # 是否顺时针绘制饼图
  init.angle  # 初始角度
)

例:

data <- c(10, 20, 30, 40);
pie(data);

boxplot4

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轴标签数字将是横向写的
);

boxplot5

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轴标签
  );
}

type类型

常用外观样式参数

  • col指定线条和点的颜色:可指定多个颜色,使每个点都有自己的颜色。如果颜色数量少于点的数量,会按照标准方式循环使用颜色,而线条将全部用第一个指定的颜色绘制

  • bg指定开放符号的背景颜色,如在画散点图(points()函数)时,可以用来设置数据点的背景色,也是可以让所有点有同一个背景色、或者每个点都有自己的背景色

  • pch指定绘图字符或符号(点的形状),取值为一个整数:

    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'  # 点的颜色
  ) 
);

plot参数

还可以:

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轴数据取对数
  ) 
);

plot参数2

一些关于画图的基础知识

高低图层
  • 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

  • mfrowmfcol设定画多个子图(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.pdfheightwidth参数的单位是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矢量图可无限放大而不失真(变成像素),且易于编辑