草庐IT

R语言数据处理替换操作(含gsub函数常用示例)——实战单细胞信息注释函数 2022-07-01

黄甫一 2023-09-28 原文

适用背景

在R语言中,我们需要对字符串、向量和数据框等数据类型进行替换操作,有时候是因为需要更换别名,有时候是因为数据存在错误需要修正,有时候则是因为需要删除某些信息。本文将介绍常用的替换函数gsub的常用用法,但gsub也存在某些局限性,一般只能进行一次指定情况的操作。例如在单细胞数据分析的信息注释过程中,我们常常需要把无监督聚类得到的clusters注释成细胞类型,如果每一个clusters都写一行替换的代码就会显得相当冗余,因此可以封装成一个函数进行类似的处理就会简单一些。因此,本文后半部分将介绍批量替换写成函数的方法

gsub函数

R语言中,最常用的替换函数是gsub,其用法也比较容易理解,一般只需传入三个参数,gsub(匹配内容,替换内容,操作对象)

string='strings'
gsub('s','S',string)
[1] "StringS"
  • 删掉某些字符串:
string='strings'
gsub('s','',string)
[1] "tring"
  • 如果只想替换开头的s,则需要使用正则表达式,代码如下:
string='strings'
gsub('^s','S',string)
[1] "Strings"
  • 只替换结尾的s则写成:
gsub('s$','S',string)
  • 如果有多个匹配项,则使用管道符 |
string='strings'
gsub('^s|i','X',string)
[1] "XtrXngs"
  • 把s及s后面的一个字符替换掉:
string='strings'
gsub('s..','X',string)
[1] "Xings"
  • 把i及后面的字符串替换掉:
string='strings'
gsub('i.*','X',string)
[1] "strX"
  • 如果字符串里有特殊的字符,而匹配内容又含有这种特殊字符,例如^,则用方括号[]括起来:
#不用方括号
string='^strings'
gsub('^','X',string)
[1] "X^strings"
#使用方括号
gsub("[.^]",'X',string)
[1] "Xstrings"
#不用方括号
> gsub(".^",'X',string)
[1] "^strings"
  • 模糊匹配数字后替换:
string='0strings1'
gsub('[0-9]','X',string)
[1] "XstringsX"
  • 模糊匹配字母后替换
string='0strings1'
gsub('[a-z]','X',string)
[1] "0XXXXXXX1"

单细胞注释简单方法

以Celltype列为例,以下all为Seurat对象,也可以是数据框。

  • 可以用which函数,返回的是逻辑值
all$Celltype[which(all$seurat_clusters == "24")]<-"XCL+ NK"
  • 用grep函数,返回索引值
all$Celltype[grep("24",all$seurat_clusters)]<-"XCL+ NK"
  • 用grepl函数,与grep类似,grepl返回的是逻辑值,但效果与grep一样
all$Celltype[grepl("24",all$seurat_clusters)]<-"XCL+ NK"

以上方法都比较好理解,但是如果clusters比较多,写起来就很冗余,有时候clusters多达几十种,写起来不美观,可以写个函数封装一下。

单细胞注释函数

其实单细胞数据注释也可以看成是一种对数据框的替换操作,因此可以提取Seurat对象的meta.data信息运行以下函数。

函数一 anno_cell

参数简介:

  • meta,输入的数据框,例如obj@meta.data
  • anno.list,注释对应信息,例如anno.list=list("OLI"=c(1,13,11,3,23,18), "MIC"=c(6,22))
  • ref.col,原始列的列名,默认是无监督聚类得到的聚类列seurat_clusters
  • anno.col,注释列的列名,默认是Celltype,如果此列不存在则会自动建一列并标记为'unknown',如果存在,则此列之前的信息会保留,但可能会被覆盖
anno_cell <- function(meta,anno.list=NULL,ref.col='seurat_clusters',anno.col='Celltype'){
colist <- unique(colnames(meta))
if (length(grep(anno.col,colist))==0) {
meta[,anno.col] <- 'unknown'
}
nlen <- length(anno.list)
for (i in 1:nlen) {
name <- names(anno.list)[i]
clust <- anno.list[[name]]
c1 <- which(meta[,ref.col] %in% clust)
if (length(c1)!=0) {
meta[,anno.col][c1] = name
}
}
return(meta)
}

使用示例(obj是Seurat对象):

olic <- c(1,13,11,3,23,18)
micc <- c(6,22)
vlmc <- c(37,40)
nfol <- c(38)
opcc <- c(2)
unknownc <- c(39)
inc <- c(31,25,16,29)
exc <- c(14,12,7,32,34,28,27,24,4,0,35,8,10,30,33,21,36,19)
ast <- c(17,20,41,9,5,15,26)
obj@meta.data <- anno_cell(obj@meta.data,anno.list=list("OLI"=olic,
                                      "MIC"=micc,
                                      "ENDO"=vlmc,
                                      "NFOL"=nfol,
                                      "OPC"=opcc,
                                      "IN"=inc,
                                      "EX"=exc,
                                      "AST"=ast)

以上就完成注释了,可以通过Dimplot函数检查注释得是否正确

函数二 map_anno

有时候,注释结果不是根据无监督聚类得到的,而是需要从其它方法,或者亚群聚类得到信息来注释,简单来说就是需要把从数据框df1中信息转移到数据框df2中,但df1和df2的维度不相等,因此需要进行匹配注释。当然,Seurat自带AddMetaData函数,但由于维度问题,可能会报错。
参数简介如下:

  • ref,参考数据框,含有注释信息
  • que,待注释数据框
  • ref.col,默认为Celltype,参考数据框注释信息所在列
  • que.col,默认为Celltype,待注释的列名,如果此列不存在则会自动建一列并标记为'unknown',如果存在,则此列之前的信息会保留,但可能会被覆盖
map_anno <- function(ref,que,ref.col='Celltype',que.col='Celltype'){
colist <- unique(colnames(que))
if (length(grep(que.col,colist))==0) {
que[,que.col] <- 'unknown'
}
c1 <- which(rownames(ref) %in% rownames(que))
if (length(c1)!=dim(ref)[1]) {
print('Note: some cells in ref are not in que!')
ref <- ref[c1,]
}


nlen <- length(unique(ref[,ref.col]))
for (i in 1:nlen) {
name <- unique(ref[,ref.col])[i]
c1 <- which(ref[,ref.col] %in% name)
cellist <- rownames(ref)[c1]
que[cellist,que.col] <- name
}
return(que)
}

使用示例:

df1 <- read.table('cell_info.xls',sep="\t",header=T,stringsAsFactor=F)
obj@meta.data <- map_anno(ref=df1,que=obj@meta.data)

更新函数三 seq_anno

最近拿到师兄给的注释表格是把seurat_clusters从0群到n群的注释表格,如下图:

注释信息表格

因此上面的两个函数都不太方便,还需要进一步整理才能用上前面的两个函数,这个跟每个人的细胞注释信息习惯有关,因此写了这个新函数seq_anno,可以按seurat_clusters列的顺序进行注释。

  • meta,数据框
  • ref.col,参考列,默认为seurat_clusters,可以改成其它分辨率的结果,例如RNA_snn_res.1.5
  • anno.col,注释列,默认为Celltype
  • anno.list,从0群到n群注释信息的向量,例如c("BC","MC","TC"),其中0群为BC,1群为MC,2群为TC。
seq_anno <- function(meta,ref.col='seurat_clusters', anno.col='Celltype',anno.list=NULL) {
colist <- unique(colnames(meta))
meta$ref.col <- meta[,ref.col]
if (length(grep(anno.col,colist))==0) {
meta[,anno.col] <- 'unknown'
}
nmax <- max(meta$ref.col)
for (i in 0:nmax){
meta[,anno.col][grep(i,meta$ref.col)] <- anno.list[i+1]
}
return(meta)
}
使用示例:

meta为数据框,cell.anno为向量

> head(cell.anno)
[1] "CD16+Mono" "CD14+Mono" "CD16+Mono" "CD16+Mono" "CD1C+DC"   "CD1C+DC"
> meta <- seq_anno(meta,anno.list=cell.anno,ref.col='seurat_clusters', anno.col='Celltype')

可以简写为

df2 <- seq_anno(df1,anno.list=cell.anno)
映射关系替换

基因ID与基因名之间的替换是分析过程中容易遇到的问题,简单来说可以写个循环,但是基因有上万个时就比较麻烦了,不过已经有人写了现成的函数。

目标:df2是基因ID与基因名的映射关系,需要把df1的gene列的基因ID替换成基因名。

df1数据结构:

                      p_val avg_logFC pct.1 pct.2    p_val_adj cluster
AMEX60DD035333 1.276051e-76 0.2897204 0.811 0.836 3.139595e-72       0
AMEX60DD000806 4.985819e-61 0.5910303 0.739 0.894 1.226711e-56       0
AMEX60DD039553 1.624536e-44 0.2744919 0.175 0.469 3.997009e-40       0
AMEX60DD025106 1.054587e-41 0.2832646 0.145 0.396 2.594705e-37       0
AMEX60DD008073 2.048906e-40 0.2503263 0.136 0.375 5.041127e-36       0
AMEX60DD002836 4.554993e-40 0.2639632 0.168 0.430 1.120710e-35       0
                         gene       FC
AMEX60DD035333 AMEX60DD035333 1.336054
AMEX60DD000806 AMEX60DD000806 1.805848
AMEX60DD039553 AMEX60DD039553 1.315862
AMEX60DD025106 AMEX60DD025106 1.327456
AMEX60DD008073 AMEX60DD008073 1.284444
AMEX60DD002836 AMEX60DD002836 1.302080

df2数据结构:

      Axolotl_ID  hs_gene
1 AMEX60DD000002   ZNF569
2 AMEX60DD000006   ZNF141
3 AMEX60DD000016    FZD10
4 AMEX60DD000033   GLT1D1
  • 函数一 stri_replace_all_regex
library(stringi)
df3 <- stri_replace_all_regex(df1$gene,df2$Axolotl_ID,df2$hs_gene,vectorize=F)
  • 函数二 mgsub
library(mgsub)
df4 <- mgsub(df1$gene,df2$Axolotl_ID,df2$hs_gene)

两个函数用法一样,输入三个参数:待替换向量,映射关系的原始ID,映射关系的替换内容。实测stri_replace_all_regex比mgsub快,推荐使用stri_replace_all_regex。


根据映射关系替换数据框某一列
map_index <- function(df,index=NULL,content=NULL,mapcol=NULL,newcol='newcol'){
df1 <- data.frame(content)
rownames(df1) <- index
df2 <- df1[df[,mapcol],]
df[,'newcol'] <- df2
return(df)
}
  • 使用示例
df3 <- map_index(df2,index=celt,content=major,mapcol='Celltype',newcol='newcol')

使用到的数据格式如下:

> head(df2)
        orig.ident nCount_Spatial nFeature_Spatial percent.mt nCount_SCT
BIN.214    Spatial           3096             1030 0.38759690      11238
BIN.371    Spatial           5609             1716 0.44571225      11438
BIN.374    Spatial          11045             2608 0.09959258      11533
BIN.375    Spatial           9026             2246 0.33237314      11646
BIN.378    Spatial           9520             2412 0.23109244      11541
BIN.379    Spatial          12353             3047 0.09714239      12151
        nFeature_SCT SCT_snn_res.0.5 seurat_clusters BIN.100 bin100.y bin100.x
BIN.214         1814               0               0 BIN.214        2       52
BIN.371         1853               4               4 BIN.371        3       47
BIN.374         2608               4               4 BIN.374        3       50
BIN.375         2248               4               4 BIN.375        3       51
BIN.378         2412               4               4 BIN.378        3       54
BIN.379         3047               2               2 BIN.379        3       55
        refined_pred ref.col major  sub Celltype Major
BIN.214           10      10   mDC  mDC      mDC    DC
BIN.371            5       5    LC LC_2       LC    LC
BIN.374            5       5    LC LC_2       LC    LC
BIN.375            5       5    LC LC_2       LC    LC
BIN.378            5       5    LC LC_2       LC    LC
BIN.379            5       5    LC LC_2       LC    LC
> celt
 [1] "CP"            "DMC"           "DVR"           "EG"
 [5] "LC"            "lDC"           "MC"            "mDC"
 [9] "PT"            "Septum_l"      "Septum_m"      "Striatum"
[13] "Striatum_CCK"  "Striatum_TAC4" "VLMC"
> major
 [1] "CP"     "HIP"    "DVR"    "VZ"     "LC"     "DC"     "HIP"    "DC"
 [9] "PT"     "Septum" "Septum" "STR"    "STR"    "STR"    "VLMC"

注意index和content是对应的映射关系向量,顺序不能乱,且index不能有重复的元素。


小结与讨论

替换操作不只有gsub函数这一种方法,我写的2个函数就没有使用到gsub,当然也可以写成gsub的脚本,但已经写好了就懒得写了。

有关R语言数据处理替换操作(含gsub函数常用示例)——实战单细胞信息注释函数 2022-07-01的更多相关文章

  1. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

  2. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

  3. ruby-on-rails - 在 ruby​​ 中使用 gsub 函数替换单词 - 2

    我正在尝试用ruby​​中的gsub函数替换字符串中的某些单词,但有时效果很好,在某些情况下会出现此错误?这种格式有什么问题吗NoMethodError(undefinedmethod`gsub!'fornil:NilClass):模型.rbclassTest"replacethisID1",WAY=>"replacethisID2andID3",DELTA=>"replacethisID4"}end另一个模型.rbclassCheck 最佳答案 啊,我找到了!gsub!是一个非常奇怪的方法。首先,它替换了字符串,所以它实际上修改了

  4. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  5. ruby - 在 Ruby 中按名称传递函数 - 2

    如何在Ruby中按名称传递函数?(我使用Ruby才几个小时,所以我还在想办法。)nums=[1,2,3,4]#Thisworks,butismoreverbosethanI'dlikenums.eachdo|i|putsiend#InJS,Icouldjustdosomethinglike:#nums.forEach(console.log)#InF#,itwouldbesomethinglike:#List.iternums(printf"%A")#InRuby,IwishIcoulddosomethinglike:nums.eachputs在Ruby中能不能做到类似的简洁?我可以只

  6. postman——集合——执行集合——测试脚本——pm对象简单示例02 - 2

    //1.验证返回状态码是否是200pm.test("Statuscodeis200",function(){pm.response.to.have.status(200);});//2.验证返回body内是否含有某个值pm.test("Bodymatchesstring",function(){pm.expect(pm.response.text()).to.include("string_you_want_to_search");});//3.验证某个返回值是否是100pm.test("Yourtestname",function(){varjsonData=pm.response.json

  7. C51单片机——实现用独立按键控制LED亮灭(调用函数篇) - 2

    说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时

  8. ruby - Ruby gsub 替换中的行为不一致? - 2

    两个gsub产生不同的结果。谁能解释一下为什么?代码也可在https://gist.github.com/franklsf95/6c0f8938f28706b5644d获得.ver=9999str="\tCFBundleDevelopmentRegion\n\ten\n\tCFBundleVersion\n\t0.1.190\n\tAppID\n\t000000000000000"putsstr.gsub/(CFBundleVersion\n\t.*\.).*()/,"#{$1}#{ver}#{$2}"puts'--------'putsstr.gsub/(CFBundleVersio

  9. ruby-on-rails - 将字符串转换为 ruby​​-on-rails 中的函数 - 2

    我需要一个通过输入字符串进行计算的方法,像这样function="(a/b)*100"a=25b=50function.something>>50有什么方法吗? 最佳答案 您可以使用instance_eval:function="(a/b)*100"a=25.0b=50instance_evalfunction#=>50.0请注意,使用eval本质上是不安全的,尤其是当您使用外部输入时,因为它可能包含注入(inject)的恶意代码。另请注意,a设置为25.0而不是25,因为如果它是整数a/b将导致0(整数)。

  10. ruby-on-rails - 在这种情况下我如何模拟一个对象?没有明显的方法可以用模拟替换对象 - 2

    假设我在Store的模型中有这个非常简单的方法:defgeocode_addressloc=Store.geocode(address)self.lat=loc.latself.lng=loc.lngend如果我想编写一些不受地理编码服务影响的测试脚本,这些脚本可能已关闭、有限制或取决于我的互联网连接,我该如何模拟地理编码服务?如果我可以将地理编码对象传递到该方法中,那将很容易,但我不知道在这种情况下该怎么做。谢谢!特里斯坦 最佳答案 使用内置模拟和stub的rspecs,你可以做这样的事情:setupdo@subject=MyCl

随机推荐