哈佛R语言课程--8.Tidyverse

https://github.com/hbctraining/Intro-to-R

Tidyverse套件旨在共同努力,使通用数据科学的操作更加人性化。这些包具有数据规整,清洗,读/写,解析和可视化等功能。免费电子书R for Data Science详细描述了可用工具的实例以及它们如何协同工作。我们将探讨使用这些软件包的基本语法,以及使用'dplyr'软件包进行数据规整的特定函数,使用'tidyr'软件包清洗数据以及使用'ggplot2'软件包进行数据可视化。

这些包都使用相同的代码风格,即所有函数名和参数的格式均为snake_case。详见http://style.tidyverse.org/

1.下载示例数据

我们将引入一个包含差异表达式分析结果的新文件作为示例数据。下载地址:https://github.com/hbctraining/Training-modules/raw/master/Visualization_in_R/data/Mov10oe_DE_results.csv 将其下载到之前的data文件夹中(选择Save Link AsDownload Linked File As)。下载成功后可在RStudio“文件”选项卡的data文件夹中看到它。

2.将示例数据读取进R

读取刚下载的新文件并加载tidyverse

res_tableOE <- read.csv(file = "data/Mov10oe_DE_results.csv", row.names = 1)

library(tidyverse)

Tidyverse基础知识

Tidyverse软件包套件向用户介绍了一组数据结构,函数和操作符,以便更直观地处理数据,但与R base的工作方式略有不同。管道和tibble是接下来要关注的两个重要的新概念

(1) 管道

长串的R命令让人想退缩。此外,尝试理解具有许多嵌套函数的代码可能会令人困惑。

为了使R代码更具人性化,Tidyverse工具使用%>%magrittr包中获取的管道,现在dplyr是Tidyverse自动安装的软件包的一部分。管道允许将前一个命令的输出用作另一个命令的输入,而不是使用嵌套函数。

注意:写入管道的快捷方式是<kbd style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em;">shift</kbd>+ <kbd style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em;">command</kbd>+<kbd style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em;">M</kbd>

使用管道运行多个命令的示例:

## A single command
sqrt(83)

## Base R method of running more than one command
round(sqrt(83), digit = 2)

## Running more than one command with piping
sqrt(83) %>% round(digit = 2)

管道代表了一种更简单的编写和解密R代码的方式,因此我们将在可能的情况下利用它,因为我们将完成剩下的课程。


练习

  1. replicatemetadata数据框中提取列(使用$表示法)并将值保存到名为的向量中rep_number

  2. 使用pipe(%>%)在一行中执行两个步骤:

    1. rep_number进一个因素。
    2. 使用该head()函数返回rep_number因子的前六个值。

(2)Tibbles

所述的核心组件tidyversetibbleTibbles是标准的现代返工data.frame,有一些内部改进使代码更可靠。它们是数据框,但不遵循所有相同的规则。例如,tibbles可以包含列名的数字/符号,这在基数R中通常不允许。

重要提示:tidyverse对行名称非常了解。这些软件包坚持要求所有列数据(例如data.frame)被平等对待,并且rownames应该弃用特殊的列名称。Tibble提供了简单的实用程序函数来处理rownames:rownames_to_column()column_to_rownames()。可以找到更多有关处理元组中行名的帮助:

help("rownames", "tibble")

可以使用tibble()函数直接创建Tibbles,也可以使用数据帧转换为数据帧as_tibble(name_of_df)

注意:该函数as_tibble()将忽略行名称,因此如果需要表示行名称的列,rownames_to_column(name_of_df)则应在将data.frame转换为tibble之前运行该函数。此外,as_tibble()默认情况下不会将字符向量强制转换为因子。


练习

  1. df_tibble使用tibble()函数组合向量species和创建一个tibble glengths注意:您的glengths向量可能与长度不同species,因此您需要使用适当大小的子集。

  2. metadata数据框更改为名为的tibble meta_tibble。使用该rownames_to_column()函数保留与使用%>%as_tibble()函数结合的rownames 。


tibble有个很好的特点,当一个变量打印到屏幕时,默认情况下它只会显示前10行和适合屏幕的列。这样很方便,不必用head()查看数据集了。

# Default printing of data.frame
rpkm_data

# Default printing of tibble
rpkm_data %>% 
  rownames_to_column() %>% 
  as_tibble()

注意:如果需要查看更多行列,print()函数可以更改显示的行数或列数。

# Printing of tibble with print() - change defaults
 rpkm_data %>% 
 rownames_to_column() %>% 
 as_tibble() %>% 
 print(n = 20, width = Inf)


Tidyverse tools

虽然Tidyverse套件中的所有工具都值得进行更深入的探索,但本课程仅研究我们将最常用于数据规整和清洗的工具。

注意:大量的tidyverse函数将同时使用tibbles和dataframes,输出的数据结构将与输入相同。但是,有些函数会返回一个tibble(没有行名),无论是否提供了tibble或dataframe。

Tidyverse之Dplyr

tidyverse中最有用的工具是dplyr。它用于数据规整,堪比瑞士军刀。dplyr有许多方便的函数,非常建议将它用于数据分析:

  • select() 提取列并返回一个tibble。
  • arrange() 更改行的顺序。
  • filter() 根据其值选择行。
  • mutate() 添加作为现有变量函数的新变量。
  • rename() 轻松更改列的名称
  • summarise() 将多个值减少到单个摘要。
  • pull() 提取单个列作为向量。
  • _join()的两个数据帧合并到一起的函数组,包括(inner_join()left_join()right_join(),和full_join())。

注意:dplyr在2017年进行了大规模修订,将版本从0.5切换到0.7。如果在线查阅其他dplyr教程,请注意2017年之前的资料已过时。尤其是用dplyr编写函数的部分(请参阅下面的注释部分)。

select()

要从一个tibble中提取列,用select()函数。

# Convert the res_tableOE data frame to a tibble
res_tableOE <- res_tableOE %>% 
               rownames_to_column(var="gene") %>% 
           as_tibble()

# extract selected columns from res_tableOE 
res_tableOE %>%
    select(gene, baseMean, log2FoldChange, padj)

相反,可以使用否定选择删除不需要的列。

res_tableOE %>%
    select(-c(lfcSE, stat, pvalue))

## # A tibble: 23,368 x 4
##         gene    baseMean log2FoldChange         padj
##          <chr>       <dbl>          <dbl>        <dbl>
##  1 1/2-SBSRNA4  45.6520399    0.266586547 2.708964e-01
##  2        A1BG  61.0931017    0.208057615 3.638671e-01
##  3    A1BG-AS1 175.6658069   -0.051825739 7.837586e-01
##  4        A1CF   0.2376919    0.012557390           NA
##  5       A2LD1  89.6179845    0.343006364 7.652553e-02
##  6         A2M   5.8600841   -0.270449534 2.318666e-01
##  7       A2ML1   2.4240553    0.236041349           NA
##  8       A2MP1   1.3203237    0.079525469           NA
##  9      A4GALT  64.5409534    0.795049160 2.875565e-05
## 10       A4GNT   0.1912781    0.009458374           NA
## # ... with 23,358 more rows

将这个tibble保存为一个名为sub_res的新变量:

sub_res <- res_tableOE %>%
    select(-c(lfcSE, stat, pvalue))

arrange()

请注意,现在行是按symbol排序。用arrange()按照adjusted P value进行排序。

arrange(sub_res, padj)

## # A tibble: 23,368 x 4
##      gene   baseMean log2FoldChange          padj
##       <chr>      <dbl>          <dbl>         <dbl>
##  1    MOV10 21681.7998      4.7695983  0.000000e+00
##  2     H1F0  7881.0811      1.5250811 2.007733e-162
##  3    HSPA6   168.2522      4.4993734 1.969313e-134
##  4 HIST1H1C  1741.3830      1.4868361 5.116720e-101
##  5    TXNIP  5133.7486      1.3868320  4.882246e-90
##  6    NEAT1 21973.7061      0.9087853  2.269464e-83
##  7    KLF10  1694.2109      1.2093969  9.257431e-78
##  8   INSIG1 11872.5106      1.2260848  8.853278e-70
##  9    NR1D1   969.9119      1.5236259  1.376753e-64
## 10    WDFY1  1422.7361      1.0629160  1.298076e-61
## # ... with 23,358 more rows

filter()

只保留表达的基因(baseMean高于0),adjusted P value小于0.01。可以filter()一个命令中放入多个条件筛选。

sub_res %>%
    filter(baseMean > 0 & padj < 0.01)

## # A tibble: 4,959 x 4
##    gene     baseMean log2FoldChange     padj
##    <chr>       <dbl>          <dbl>    <dbl>
##  1 A4GALT       64.5          0.798 2.40e- 5
##  2 AAGAB      2614\.          -0.390 1.68e-11
##  3 AAMP       3157\.          -0.380 9.11e-13
##  4 AARS       3690\.           0.167 2.10e- 3
##  5 AARS2      2255\.          -0.204 3.77e- 4
##  6 AASDHPPT   3561\.          -0.293 3.79e- 7
##  7 AASS       1018\.           0.347 7.94e- 5
##  8 AATF       2613\.          -0.290 1.97e- 7
##  9 ABAT        384\.           0.384 1.99e- 4
## 10 ABCA1       108\.           0.833 4.19e- 7
## # ... with 4,949 more rows

mutate()

mutate()使您可以从现有列创建新列。让我们为每个基因生成log10的baseMeans计算。

sub_res %>%
    mutate(log10BaseMean = log10(baseMean)) %>%
    select(gene, baseMean, log10BaseMean)

## # A tibble: 23,368 x 3
##    gene        baseMean log10BaseMean
##    <chr>          <dbl>         <dbl>
##  1 1/2-SBSRNA4   45.7           1.66 
##  2 A1BG          61.1           1.79 
##  3 A1BG-AS1     176\.            2.24 
##  4 A1CF           0.238        -0.624
##  5 A2LD1         89.6           1.95 
##  6 A2M            5.86          0.768
##  7 A2ML1          2.42          0.385
##  8 A2MP1          1.32          0.121
##  9 A4GALT        64.5           1.81 
## 10 A4GNT          0.191        -0.718
## # ... with 23,358 more rows

rename()

快速重命名现有列rename()。语法是new_name= old_name

sub_res %>%
    rename(symbol = gene)

## # A tibble: 23,368 x 4
##    symbol      baseMean log2FoldChange       padj
##    <chr>          <dbl>          <dbl>      <dbl>
##  1 1/2-SBSRNA4   45.7          0.268    0.264    
##  2 A1BG          61.1          0.209    0.357    
##  3 A1BG-AS1     176\.          -0.0519   0.781    
##  4 A1CF           0.238        0.0130  NA        
##  5 A2LD1         89.6          0.345    0.0722   
##  6 A2M            5.86        -0.274    0.226    
##  7 A2ML1          2.42         0.240   NA        
##  8 A2MP1          1.32         0.0811  NA        
##  9 A4GALT        64.5          0.798    0.0000240
## 10 A4GNT          0.191        0.00952 NA        
## # ... with 23,358 more rows

pull()

在最近的dplyr 0.7更新中,pull()添加了作为向量访问列数据的快速方法。这在与管道操作符连用非常方便。

# Extract first 10 values from the gene column
pull(sub_res, gene) %>% head()

_join()

Dplyr具有一组强大的连接操作,它们基于两个数据框中存在的变量或变量集将一对数据框连接在一起,这些变量或变量集唯一地标识所有观察。这些变量称为

  • inner_join:只有两个数据集中存在键的行将连接在一起。

  • left_join:保留第一个数据框中的所有行,连接第二个数据框中具有键的行。

  • right_join:保留第二个数据框中的所有行,连接第一个数据框中具有键的行。

  • full_join:保留两个数据集中的所有行,没有匹配键的行产生NA值。

为了练习连接功能,创建如下示例数据:

  • 描述:对于一个研究项目,我们询问健康志愿者和癌症患者关于他们的饮食和锻炼的问题。我们还为每个人收集了血液工作,每个人都获得了一个独特的身份证。创建数据框behaviorblood,复制/粘贴以下代码:

  • 数据:

      # Creating behavior dataframe
    
      ID <- c(546, 983, 042, 952, 853, 061)
      diet <- c("veg", "pes", "omni", "omni", "omni", "omni")
      exercise <- c("high", "low", "low", "low", "med", "high")
      behavior <- data.frame(ID, diet, exercise)
    
      # Creating blood dataframe
    
      ID <- c(983, 952, 704, 555, 853, 061, 042, 237, 145, 581, 249, 467, 841, 546)
      blood_levels <- c(43543, 465, 4634, 94568, 134, 347, 2345, 5439, 850, 6840, 5483, 66452, 54371, 1347)
      blood <- data.frame(ID, blood_levels)
    
    

并非所有血液ID都有相关的behavior信息。使用_join函数族,练习加入连接数据框的不同选项。

要仅连接两个数据框中都有的ID,可以使用inner_join()函数:

# Inner join
inner_join(blood, behavior)

或者,如果想要返回所有血液ID,但只包括匹配的行为ID,我们可以使用以下left_join()功能:

# Left join
left_join(blood, behavior)

使用 right_join()也可以做同样的事,所有行为ID和匹配的血液ID:

# Right join
right_join(blood, behavior)

最后,无论是否存在匹配的键(ID),都可以两个数据框出现的所有ID:

# Full join
full_join(blood, behavior)

注意:如果两个数据框中的名称不具有相同的列名,则需要包含by参数。例如:

inner_join(df1, df2, by = c("df1_colname" = "df2_colname"))

Tidyverse 之Tidyr

Tidyr的目的是拥有组织良好或整洁的数据,Tidyverse将tidydata其定义为:

  1. 每个变量作为一列
  2. 每个观测作为一行
  3. 每个值占一个单元格

Tidyr有两个主要函数,gather()spread()。这些函数允许在长数据格式和宽数据格式之间进行转换。下游分析决定所需的数据格式。

gather()

gather()函数将宽数据格式更改为长数据格式。当使用'ggplot2'将所有值绘制到单个列中时,此函数特别有用。

要使用此函数,需要提供原数据框中需要聚合的多个列名。然后使用key参数提供原数据框列名聚成的新列的名称,使用value参数提供所有聚合成的的列的名称。

rpkm_data_tb <- rpkm_data %>% 
  rownames_to_column() %>% 
  as_tibble()

gathered <- rpkm_data_tb %>%
  gather(colnames(rpkm_data_tb)[2:13],
         key =  "samplename",
         value = "rpkm")

spread()

spread()函数与gather()函数相反。key列的类别将成为单独的新列,value列中的值将根据关联的key列进行拆分。

gathered %>% 
  spread(key = "samplename", 
         value = "rpkm")

image

Tidyverse之Stringr

Stringr是一个处理字符序列或字符串的强大工具。stringr中有许多函数可用于处理字符串,但我们会介绍一些最有用的函数:

  • str_c() 将字符串连接在一起
  • str_split() 通过指定分隔符来拆分字符串
  • str_sub() 从特定位置的字符串中提取字符
  • str_replace() 用另一个字符串替换现有字符串
  • str_to_()组功能改变了的字符串的情况下的功能,包括str_to_upper()str_to_lower()str_to_title()
  • str_detect() 标识向量中的每个元素中是否存在模式
  • str_subset() 仅返回与模式匹配的元素

除了其他字符串函数之外,为了帮助使用这些函数,还有一个方便的字符串备忘单

str_c()

str_c()函数将值与指定的分隔符连接在一起。collapse参数指定是否将多个对象合并为单个字符串。

metadata <- metadata %>%
  mutate(sample = str_c(genotype, celltype, replicate, sep = "_"))

str_split()

str_c()相反,str_split()将基于指定的分隔符分隔值。

metadata %>% 
  pull(sample) %>% 
  str_split("_")

str_sub()

为了从字符串中提取字符,str_sub()函数可按照位置提取的字符串中:

metadata %>% 
  pull(sample) %>% 
  str_sub(start = 1, end = 8)

要用另一个字符串替换现有字符串,使用str_replace()函数:

str_replace()

metadata %>%
  pull(celltype) %>%
  str_replace("typeA", "typeP")

默认情况下,str_replace()只会替换每个元素/组件中的第一个匹配。如果你想替换所有匹配,那么就用str_replace_all()函数。

str_to_()

在数据整理过程中,需要确保列的所有值都具有相同的大小写,因为R区分大小写。使用str_to_函数族,包括str_to_upper()str_to_lower()str_to_title(),可以很简单的修改任何值的大小写。

metadata %>%
  pull(genotype) %>%
  str_to_upper()

metadata %>%
  pull(genotype) %>%
  str_to_lower()

metadata %>%
  pull(genotype) %>%
  str_to_title()

最后两个函数,str_detect()str_subset()需要一个模式来匹配。通常在字符串中指定模式时,使用正则表达式(regexps)来描述。使用正则表达式时,有一些有用的特殊字符,有关这些的详细信息可以在R for Data Science一书和Duke的材料(http://www2.stat.duke.edu/~cr173/Sta523_Fa16/regex.html)中找到,我们在这里列出了一些常用字符:

  • "."匹配每个字符(如果想匹配.字符,就需要使用\\.来转换)
  • "^characters"匹配字符串的开头
  • "characters$"匹配字符串的结尾
  • "[characters]"匹配[]内的任何字符
  • "[ ^characters]"匹配不在[]内的任何字符
  • "[A-z0-9]"匹配任何字母或数字
  • "*"匹配零次或多次

str_detect()

str_detect()函数标识向量的每个元素中是否存在模式。此函数返回一个逻辑值,表示每个元素是否与模式匹配。

idx <- str_detect(metadata$sample, "typeA_1")

# Allows for subsetting dataframes using the logical operators
metadata[idx, ]

str_subset()

要仅返回与模式匹配的值,用str_subset()函数:

metadata %>% 
  pull(sample) %>% 
  str_subset("typeA_1")

编程说明

在处理过程中,tidyverse包用rlang在base R语言上构建,这是对函数如何处理变量名和评估参数的全盘重做。这是通过tidyeval框架实现的,该框架使用tidy evaluation与命令操作交互。这超出了本课程的范围,但在Programming with dplyr(http://dplyr.tidyverse.org/articles/programming.html)中有详细解释,可以了解这些新工具与基础base R的表现有何不同。

其他资源

微信公众号生信星球同步更新我的文章,欢迎大家扫码关注!


我们有为生信初学者准备的学习小组,点击查看◀️
想要参加我的线上线下课程,也可加好友咨询🔼
如果需要提问,请先看生信星球答疑公告

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 写在前面的话 代码中的# > 表示的是输出结果 输入 使用input()函数 用法 注意input函数输出的均是字...
    FlyingLittlePG阅读 2,729评论 0 8
  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 5,149评论 0 9
  • 1. 数据操作:dplyr包应用 dplyr包是为数据分析提供了一系列快捷有效的操作,其中有五个关键函数基本可以解...
    100gle阅读 3,509评论 0 7
  • php usleep() 函数延迟代码执行若干微秒。 unpack() 函数从二进制字符串对数据进行解包。 uni...
    思梦PHP阅读 1,980评论 1 24
  • 当小猪回到出租屋的时候,小瑶已经回来了,大家正讨论着要吃什么,而房东刚才也已经过来把房租收走了,又是小静先帮小猪的...
    人海中的沙子阅读 430评论 2 10