生信小白菜之字符串、数据框、条件与循环专题


title: “Biotrainee Note6 Tips”
author: “yuluyang”
date: “2024-03-14”


生信技能树数据挖掘课程笔记~小洁老师授课

小专题系列

主要内容

  • 玩转字符串
  • 玩转数据框
  • 条件和循环
  • 隐式循环
  • 两个数据框的连接
  • 表达矩阵画箱线图
# 先舔一波装备
options("repos" = c(CRAN="http://mirrors.tuna.tsinghua.edu.cn/CRAN/"))
if(!require(tidyr))install.packages("tidyr",update = F,ask = F)
if(!require(dplyr))install.packages("dplyr",update = F,ask = F)
if(!require(stringr))install.packages('stringr',update = F,ask = F)
if(!require(tibble))install.packages('tibble',update = F,ask = F)
library(tidyr)
library(dplyr)
library(stringr)
library(tibble)

tidyverse是一个集合了ggplot2``dplyr``stringr``tidyr等诸多强大R包的组合
可以直接install.packages("tidyverse"),也可以按需安装不同的子R包
Hadley Wickham团队有个特点是不喜欢让代码里加""

专题一:玩转字符串

应用Tidyverse系列R包的tidyr

作用是整理数据,专注于字符串格式

什么是字符串
yyl=c("i like coffee","and tea","and i love dogs and sea")
# 字符串是""内的所有东西
# `yyl` 则是包括三个字符型数据的向量,即长度为3的向量
class("yyl") # 返回"character"
访问字符串长度str_length()
# 访问字符串里面有多少字符,包括字与标点符号
x<-"never mind i will find someone like you"
str_length(x) # 39
length(x) # 访问的是向量的个数,即1个
拆分字符串str_split
# 当拆分对象是一个字符串
x<-"never mind i will find someone like you"
str_split(x," ") # 按空格拆分
class(str_split(x," "))  # 返回结果是`list`
x1<-str_split(x," ")[[1]];x1 # 先拆分再取子集可以得到一行向量

# 当拆分对象是多个字符串
yyl=c("i like coffee","and tea","and i love dogs and sea")
str_split(yyl," ") # 三个字符串都会被拆分,然后返回3个`list`

# 拆分后直接转化数据结构
# `simplify = T`重要参数
str_split(yyl," ",simplify = T)
class(str_split(yyl," ",simplify = T)) # 返回结果"matrix" "array" 

# 数据框支持修改一列的数据类型
# 矩阵不支持一列一列修改,只能整个修改
访问部分字符串 str_sub
# 按字符位置访问
str_sub(x,7,10) # "mind"
检测是否有某个字符 str_detect str_starts str_ends
# `str_detect()` 是否含有某字符
x1=str_split(x," ")[[1]] # 按空格拆分再以向量结构返回
str_detect(x1,"e") # 按条件访问每个向量,返回等位的逻辑值向
x1[str_detect(x1,"e")] # 按逻辑值向量访问`True`对应的向量

# `str_starts()` 是否以某字符开头
yyl1=str_split(yyl," ")[[2]] # "and" "tea"
yyl1[str_starts(yyl1,"t")]  # "tea"

# `str_ends()` 是否以某字符结尾
yyl2=str_split(yyl," ")[[3]];yyl2 # "and"  "i"    "love" "dogs" "and"  "sea" 
yyl2[str_ends(yyl2,"e")] # "love"
修改字符串内容 str_replace str_remove
# 替换`str_replace()`
## 替换一个字符
x1<-"never mind i will find someone like you" 
x2=str_split(x1," ")[[1]];x2 # `x2`是一个长度为8的向量
str_replace(x2,"e","E") # 默认将`x2`里面每个字符串里匹配的第一个`e`替换成`E`
str_replace_all(x2,"e","E") # 替换所有匹配的
# 替换多个字符
str_replace(x2,"e|o","E") # `e|o`既替换`e`又替换`o`
str_replace_all(x2,"e|o","E")

# 删除`str_remove`
## 同理可以一个或多个字符
str_remove(x1," ") # 默认删除字符串里匹配的第一个空格
str_remove_all(x1," ") # 删除字符串里匹配的所有空格
str_remove(x1,"i")
str_remove_all(x1,"i")
str_remove(x2,"e")
str_remove_all(x2,"e")

专题二:玩转数据框

应用Tidyverse系列R包的dplyr

tidyr常与dplyr结合使用,作用是使你的数据更整洁
dplyr专注于data frame数据格式

数据框排序arrange()
## 复习一下,给向量排序用`sort()`
## 补充一下,给字符串排序用`str_sort()`
## 举个🌰
str_sort(c("yyl","yangyulu","yululu","yangxiaolutongxue")) # 排序顺序是啥捏,不如`?`一下

# 给数据框排序用`arrange()`
test <- iris[c(1:2,51:52,101:102),]
rownames(test) =NULL;test # 去掉行名,NULL是"什么都没有"
arrange(test,Sepal.Length) # 按`test`数据框的`Sepal.Length`这一列进行排序,默认是从小到大,整行移动
arrange(test, desc(Sepal.Length)) # `desc()`更改为从大到小
数据框去重复distinct()
# 先复习
## 判断向量是否重复用`duplicated()`,返回等长的逻辑值
## 给向量去重复用`unique()`,返回去掉重复的向量
## 举个🌰
mm = sample(1:20,50,replace = T) # 取50个含重复值的向量
unique(mm) 
duplicated(mm) # `duplicated`加上取子集也可以实现去重复的目的
mm[!duplicated(mm)] # `!`是用来转化`TRUE`和`FALSE`的

# 给数据框去重复用`distinct()`
distinct(test,Species,.keep_all=T) # 按哪一列取重复就把列名放到第二个参数的位置上
## `.keep_all=T`是固定搭配
## 默认保留第一次出现的行,后续有相同值的行就被删去
## 函数有疑惑的地方都可以用`?`问RStudio
数据框新增mutate()
#新增一列
mutate(test,multiplication=Sepal.Length*Sepal.Width) # 第二个参数放新增那一列的列名
## 新增的那一列数据是由原有数据运算得出来的
## ‼️注意这里并没有赋值
test= mutate(test,multiplication=Sepal.Length*Sepal.Width) # 这样`test`才真的发生了改变

# 也可以用`$`来表示
test$multiplication=test$Sepal.Length*test$Sepal.Width
随堂小练
# 加载`group.csv`并从第二列提取信息
exercise1=read.csv("group.csv");exercise1
information1=exercise1$title;information1
information2=str_split(information1," ",simplify = T);information2
information3=information2[,4];information3

# 大小写转换
tolower(information4) # 大写to小写
str_to_lower(information4) # 同上
toupper(information4) # 小写to大写
str_to_upper(information4) # 同上
str_to_title(information4) #将字符串每一个单词首字母大写、其余小写
str_to_sentence(information4) # 将句首字母大写、其余小写

# 去重复
load("test1.Rdata")
nrow(ids)
ids=distinct(ids,symbol,.keep_all = T)
nrow(ids)
## 如果加载后不清楚`Rdata`导入了什么,可以先清空`environment`再加载一遍
筛选函数——select() 选列
# 常用`select()`和`filter()`进行筛选
# 可与`starts_with` `ends_with` `contains` `matches` `one_of` `num_range`等筛选函数组合使用

# 多举几个🌰叭
## 以自带数据`mtcars`为例
select(mtcars,cyl)
select(mtcars,2) # 返回同样结果,第二列,按名字、按位置都可行
select(mtcars,cyl,qsec)
select(mtcars,2,7) # 返回同样结果,第二和七列,按名字、按位置都可行
select(mtcars,starts_with("d")) # 筛选列名以d开头的列
select(mtcars,ends_with("ar")) # 筛选列名以ar结尾的列
select(mtcars,-starts_with("d")) # 反选列名不以d开头的列
select(mtcars,contains("s")) # 筛选列名包含s的列
select(mtcars,-contains("s")) # 筛选列名不包含s的列
select(mtcars,matches(".s.")) # 筛选列名符合表达式的列
select(mtcars,mpg:hp) # 使用:连接,筛选多列

## 还可以用来重命名
select(mtcars,cylation=cyl) # 返回修改名称后的子数据集
rename(mtcars,cylation=cyl) # 返回修改名称后的完整数据集

此处笔记借鉴https://blog.csdn.net/qq_42692386/article/details/119678121?spm=1001.2014.3001.5506

筛选函数——filter() 选行
## 可以按给定的逻辑条件筛选,也可以用位置筛选
# 筛选出height为150的行
filter(starwars, height == 150)
# 筛选出sex为female的行
filter(starwars, sex == 'female')
管道符号%>%
# 断断续续不够优雅,且占用`environment`空间  
x1=select(iris,-5) #挑选第5列删掉
x2=as.matrix(x1)
x3=head(x2,50)
pheatmap::pheatmap(x3)

# 嵌套如洋葱般的代码,虽省略了很多中间变量,但并不易读
pheatmap::pheatmap(head(as.matrix(select(iris,-5)),50))

# 承上启下的传递符`%>%`,快捷键`control+shift+m`
## `%>%`前面要看作一个整体
iris %>%  # 默认传递至后一个函数的第一个参数位置
  select(-5) %>% 
  as.matrix() %>% 
  head(50) %>% 
  pheatmap::pheatmap()
## 优点是看起来逻辑清晰又不复杂
## 缺点是传递数据的体量比较大时,运输速度会有点慢

专题三:条件和循环

if条件语句:如果…,就…
if(){}小括号内只能放一个逻辑值,不能是多个逻辑值组成的向量
if(){}大括号内放代码,小括号的逻辑值决定其运行还是不运行

举个🌰🌰
# 写一个简简单单的`if(){}`
i=-10
if(i< -5)print("less") # 一定记得隔开`<`和`-`
if(i>0)print("over") # (false)则不运行,没什么结果返回

# 判断一个包要不要安装
if(!require(tidyr))install.packages("tidyr") # 当{}内只放一句代码的时候,{}可以省略
## 如果这个包已经安装好了,那么`!require()`返回结果为`False`
## 即已安装则不运行后续`install.packages`的代码

利用if管理长脚本
  • if(T){一大段长代码},该代码运行
  • if(F){另一大段长代码},该代码跳过
  • 利用if()函数决定哪一段要运行,T/F看你需求填进()
  • 凡是{}的内容都可以被折叠,方便管理暂时不用的代码
  • 非常适合要保留,但是不需要反复运行的代码
利用Rdata管理长脚本
  • 最后的成果大多数不是一段代码、一个脚本可以搞定的
  • 成果图可以导出,但是整理好的数据和整理数据的代码后续还会要使用
  • ‼️那么就需要分批管理脚本
  • 在一次数据结尾保存所有变量进Rdata
  • 在下一次打开该脚本时rm(list=ls())清空environment
  • load(file="xx.Rdata"),可以一目了然自己导入了哪些文件,然后书接上回往下写
    Rdata存储优势在于可以保存变量,不需要重复导进导出文件
    若以表格文件保存,再次使用时需要重新导入、赋值,不同的读取参数设置可能会导致不同的读取结果
推荐的数据保存格式
  • 在同一级目录下存放
    • export导出文件的文件夹
    • import导入文件的文件夹
    • plot输出图片文件夹
    • Rdata文件夹
    • 外加存放的Rmd、Rproj文件
      ‼️命名不要带特殊字符
文件访问用tab键补齐
# 访问上一级文件夹
read.csv("../a/b") # 输入"../"按`tab`键自动出现上一级文件夹,选中a文件夹后再选择b文件
# 访问下一级文件夹
read.csv("a/b.csv") # 同样是输入""后按`tab`键,选中下一级文件,再按`tab`补齐文件名

## `load()`访问同上
## 保存文件时同理
## 不管是两个文件还是两个脚本都推荐用`Rdata`来衔接

能用tab键补齐就千万不要手敲
文件及文件夹不要随便挪地方

if(){}else{} “如果…就…否则…”
## `ifelse(x,yes,no)` 
## 只有三个参数
## x: 逻辑值或逻辑值向量 # 注意哦它可以是向量,而`if()`只能放一个逻辑值
## yes: 逻辑值为`TRUE`时返回的值
## no: 逻辑值为`FALSE`时返回的值

## 上🌰
i=5
ifelse(i>0,"yesplease","nothanks")
## 多个逻辑值判断
m=rnorm(10)
ifelse(m<0,"yesplease","nothanks") # 此处`m`为一个长度是8的向量,对应返回等长的结果
str_detect()+ifelse()
library(stringr)

# 举个正确的🌰
samples=c("tumor1","tumor2","tumor3","normal1","normal2","normal3")
standard1=str_detect(samples,"tumor");standard1
ifelse(standard1,"tumor","normal")

# 再举个烂掉的坏🌰
samples=c("tumor1","tumor2","tumor3","normal1","normal2","normal3")
standard2=str_detect(samples,"normal");standard2
ifelse(standard2,"tumor","normal") #`T`和`F`对应的东西搞反啦!!

## 逻辑判断的重点就是要理清判断的逻辑🤪
想要多轮条件判断
# 用`if(){} else if(){}`套娃
example1=1
if(example1==0){
  print("yes")
} else if(example1<0){
  print("no")
} else if(example1>0){
  print("right answer")
}

# 用`ifelse()`也可以套娃
ifelse(example1==0,"yes",ifelse(example1<0,"no","right answer"))

## 补充一个知识点
# `dplyr`包里的`case_when`也可以实现多轮条件判断
library(dplyr)
case_when(example1==0 ~"yes",
          example1<0 ~"no",
          T ~"right answer") # 如果第3个条件不想写了,就用`T`占个位置
## 特点是用"~"来衔接判断条件和`TRUE`对应的结果
随堂小练
# 加载deg.Rdata,根据a、b两列的值,按照以下条件生成向量x
# a< -1 且 b>0.05,则x对应的值为down
# a>1 且 b<0.05,则x对应的值为up
# 其他情况,x对应的值为no
# 统计up、down、no各重复了多少次

load("deg.Rdata")
# 错误答案展示:
table(ifelse(deg$b>0.05,ifelse(deg$a<(-1),"down","no"),ifelse(deg$a>1,"up","no")))
## 为啥错呢?因为这样写会把"b=0.05"涵盖进去
## 但是`table`一下发现,`b`没有0.05 🐶

# 推荐答案
situation1=deg$a<(-1)&deg$b>0.05;table(situation1) # 条件单独写在前面,方便回看检查、修改
situation2=deg$a>1&deg$b<0.05;table(situation2)
table(ifelse(situation1,"down",ifelse(situation2,"up","no")))

## 容易翻车的错误
### `a`和`b`都不能直接用,前面要加上`deg$`
### `<`和`-`要分开保管哦
for循环语句
  • 函数格式 for (variable in vector) {}
  • variable是自定义的循环变量,通常用i指代
  • i每次从vector中顺序取值
  • vector是一个向量表达式,通常是个序列,例如1:10
  • {大括号内是一组表达式}
  • ivector中取了一个值之后,就按那个值执行表达式
  • 直到i取完所有值后循环终止
# 当你有代码需要重复运行3次及以上的时候
# 就可以把它写成循环函数
# 举个小🌰
## 把1:9每个数字都打出来
for (example1 in 1:9) {
   print(example1)
}

## 把从1至5每个数字的平方求出来
for (num in 1:5) {
  print(num^2)
}

## 运算从1加到100的和
answer1=0 #先指定一个初始值
for(a in 1:100){     # `a`是循环体,`1:100`是循环区间,`a`从1开始
  answer1=answer1+a  # `answer1=answer1+a`是循环式,即循环体每次都要干些啥
}                    # `answer1`循环第一次=0+1,循环第二次=1+2,循环第三次=3+3...
answer1 # 最后求出1累计加到100的和

## 使用`cat()`函数可以得到循环过程的每一次结果
answer1=0
for(a in 1:10){  
   answer1=answer1+a
cat("sqrt(",a,")=",answer1,"\n")
}

## 拼图
par(mfrow=c(2,2)) # 将画板分成4个象限
for (i in 1:4){
  plot(iris[,i],col=iris[,5])
}

## 批量装包
packages=c("tidyr","dylyr","stringr")
for(a in packages){
  if(!require(a,character.only = T))
    install.packages(a,ask=F,update=F)
}   
## 因为这里用`a`指代`package`,但`a`同时可以是一个向量
## `character.only=T`的作用是告诉循环语句,`a`只是一个指代`package`的字符

for语句的首要要求是能看懂,其次是仿写,最后是自己写,看看能不能运行出预期结果

其他循环语句
  • 先知道有这么个东西,真的遇到再百度不迟
  • repeat-break循环语句
  • while循环语句

专题四:隐式循环

矩阵/数据框的隐式循环——apply
  • apply(X,MARGIN,FUN,...,simplify=T)格式
  • X是矩阵或数据框的名字,即data
  • MARGIN指示行或列,填1基于行进行操作,填2则基于列,c(1,2)即对行和列都进行操作
  • FUN是函数,通常情况下函数写在()前面,但这里只写函数名称,不写括号哟
  • simplify这个一般默认是TRUE
# 上🌰🌰🌰

## 一个🌰
tmp=matrix(1:9,nrow = 3);tmp
tmp=apply(tmp,2,mean);tmp

## 两个🌰
test1=test[,-5]
apply(test1,1, median) # 返回结果自带行名
class(apply(test1,1, median)) # "numeric",说明啥捏,说明向量也是可以有名字滴
apply(test1, 2, mean) # 返回结果自带列名

## 三个🌰
par(mfrow=c(2,2))
apply(iris[,1:4],2,plot)
### 拼图不出来就`dev.off()`再`dev.new()`一下
如何挑选最大/小的数
# 30个随机数,挑出5个最大的
x=rnorm(30)
tail(sort(x),5) #`sort`默认从小排到大,`tail`取最后n个(第二个参数)
# 有没有想起来它的对应函数`head()`

# 生成一个随机数表达矩阵, 选出 每行最大值>1 的行
test2 =matrix(rnorm(300),nrow = 30) # 1个30行*10列的随机数矩阵
k=apply(test2,1,max)>1;table(k) # 设定条件`apply(test2,1,max)`每行取最大值`>`
test2[k,] # 取出`TRUE`对应的行

## 小学奥数题来啦🤪
# 如何挑选一个表达矩阵里方差最大的100个基因
# `test2.Rdata`有表达矩阵
## 提示,需要以下几个步骤
### 计算每个基因方差 # `var()`求方差
### 对每个基因方差进行排序
### 最后1000个数字对应的基因
rm(list=ls())
load("test2.Rdata")
exm1=tail(sort(apply(test,1,var)),1000) # 如何提取基因名呢
answer2=names(exm1);answer2 
向量/列表的隐式循环——lapply
# lapply(X,FUN,...) 
# 多一个`l`代表输出格式为`list`,其余与`apply`无差别
# 对向量/列表里面的每个元素实施相同的操作
## 列表和向量的特点是没有行、列的概念

## 🌰1
lapply(1:4, rnorm) # 返回4个`list`,1至4代入`rnorm()`中生成随机数

## 🌰2
movies=c("Captain America","Black Widow","Iron Man","Thor","Hulk")
movies_lower=lapply(movies,tolower)
movies_lower
movies_lower=unlist(lapply(movies,tolower))
movies_lower

## 🌰3
dev.off()
dev.new()
par(mfrow=c(2,2))
lapply(1:4,function(i){plot(iris[,i],col=iris[,5])}) # `function`是写函数的函数🤪,感觉在套娃

循环语句属于进阶版代码
当你把数据格式整理一致,就不用一遍遍套代码,直接带入循环语句中
或者批量处理数据时,直接循环读取多个数据集

专题五:两个数据框的连接

  • 还是Hadley大神的作品🤔️
  • xx_join格式
交集与并集
# 先弄两个数据框
name=paste0("yyl",1:5)
tall=c("171","172","174","175","180")
df1=data.frame(name,tall);df1
row.names(df1)=c(1:5);df1

name=paste0("yyl",c(1,3,5))
gender=c("boy","girl","girl")
df2=data.frame(name,gender)
row.names(df2)=c(1:3);df2

# 两个数据框取交集 `inner_join`
df3=inner_join(df1,df2);df3
## 相同的部分才保留下来

# 两个数据框取并集 `full_join`
df4=full_join(df1,df2);df4
## 全都并到一个数据框里,缺失就用`NA`

# 以左边表格为标准,有的则保留
df5=left_join(df1,df2);df5

# 以右边表格为标准,有的则保留
df6=right_join(df1,df2);df6

## 看到这就复习下`merge()`吧

专题六:如何用表达矩阵作组间比较

  • 首先,ggplot2要求数据格式是data.frame,而表达矩阵是matrix
  • 其次,表达矩阵列名为样品、行名为基因,而作图横坐标通常是基因名称,纵坐标为表达量,如何调整数据格式
    重点:如何调整数据格式以适应画图函数的需求
表达矩阵画箱线图
# 先整一个表达矩阵
set.seed(10086)
example2 = matrix(rnorm(18),ncol = 6) # 1个3行、6列的矩阵,一共18个数值
example2 = round(example2,2) # `round()`取小数点位
rownames(example2) = paste0("gene",1:3) # 取行名`gene1~3`
colnames(example2) = paste0("test",1:6) # 取列名`test1~6`
example2[,1:3] = example2[,1:3]+1 # 1:3列在原来的基础上+1
example2

# 调整格式第一步:转置
library(tidyr)
library(tibble)
library(dplyr)
example3=t(example2) %>%  
  as.data.frame() %>% 
  rownames_to_column() %>%  # 行名转为一列,目的是为了保留行名信息
  mutate(group=rep(c("control","treat"),each=3)) # 新增一列`group`,按`control`、`treat`进行分组
example3 # 现在是一个6行、5列的
pexample3=example3 %>% 
  pivot_longer(cols=starts_with("gene"), # `cols=`取走哪些列名,也可写成`cols=c(2:4)`
               names_to = "gene", # 拿下来的列名组成新的一列,列名`gene`
               values_to = "count") # 拿走的那些列里面的数值集合成新的一列,列名`count`
pexample3 # 此时是一个18行、4列的`data.frame`,18行代表曾经的18个数值
library(ggplot2)
p1=ggplot(pexample3,aes(gene,count))+
  geom_boxplot(aes(fill=group))+
  theme_bw()
p1

不一定非得学会了一个函数才能去用它,咱可以边用边了解,不合适就踹了换新的🐶

一些散装但好用的函数
match() # 用来匹配不同数据
dir() # 列出工作目录下有哪些文件
file.create() # 利用代码新建一个文件
file.exists() # 判断一个文件是否存在
file.remove() # 移除一个文件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值