字符串操作
主要介绍R基础函数以及stringr
包中对应函数
-
paste
,paste0
连接两个字符串,其中paste0的默认分隔符是"";在stringr中使用str_c(collapse=)
指定分隔符合并字符串。
# 需要注意一般配合管道函数%>% 使用,只需要指定collapse参数(str_c)或sep(paste)即可
paste('abc', 'def', sep = '')
#等同于paste0('abc', 'def')
#等同于str_c('abc', 'def', collapse = '')
[1] "abcdef"
-
nchar
计算字符串中字符个数,同str_length
。
nchar()能够获取字符串的长度,它和length()的结果是有区别的。
# 计算每一个字符串长度
nchar(c("abc", "abcd")) #求字符串中的字符个数,返回向量c(3, 4)
# 计算元素个数
length(c("abc", "abcd")) #返回2,向量中元素的个数
[1] 3 4
[2] 2
-
substr
,substring
提取指定子字符串,substr按开始和结束位置获取字符串,支持输入多个字符串,使用c()传入,
substr返回的字串个数等于第一个参数的长度,而substring返回字串个数等于三个参数中最长向量长度,短向量循环使用。
# 提取起始位置为2终止位置为4的子字符串
substr("abcdef", 2, 4)
# 也可以传入多个字符串
substr(c("abcdef", "ghijkl"), 2, 4)
# 而对于substring函数则将两个参数向量中对应位置组合并输出切片字符串,两个向量长度不同时短向量循环使用
substring("abcdef",1:2,5) # 输出1:5和2:5的两个子字符串
substring("abcdef",1:6,1:6) # 将字符串分割为单个字符
[1] "bcd"
[2] "bcd" "hij"
[3] "abcde" "bcde"
[4] "a" "b" "c" "d" "e" "f"
-
strsplit
按指定分隔符将字符串分隔,同str_split
,返回一个列表
# R中内置函数输出结果为一个列表
strsplit(c('abc.def', 'aaa.bbb'), '[.]')
# 等同于str_split(c('abc.def', 'aaa.bbb'), '[.]'),如果设置参数simplify = T则输出一个数据框
str_split(c('abc.def', 'aaa.bbb'), '[.]', simplify = T)
[[1]]
[1] "abc" "def"
[[2]]
[1] "aaa" "bbb"
[,1] [,2]
[1,] "abc" "def"
[2,] "aaa" "bbb"
-
tolower
,toupper
转换大小写,同str_to_upper
转换大写,str_to_lower
转换小写,str_to_title
首字母大写 -
grep
,grepl
搜索某个模式的子串,主要关注两个参数pattern
和x
,第一个指定需要匹配的字符串模式,第二个参数数据。grep在字符串向量x中查找一个正则表达式pattern,返回包括这个pattern的字符串的索引。grepl等同于stringr中的str_detect
用于检测字符串是否存在某些指定模式,返回bool值,存在该模式返回True,不存在返回False。
# grep匹配指定字符串随后返回其index
grep("def", c("abc","abcdef","def")) #第2、3两个字符串中都包含def,所以返回2和3
# grepl不返回index而是返回bool值
grepl("def", c("abc","abcdef","def"))
# 等同于str_detect("def", c("abc","abcdef","def"))
[1] 2 3
[2] FALSE TRUE TRUE
-
sub
,gsub
搜索并替换字符串。gsub与sub类似但替换所有符合模式的字符串。同stringr中的str_replace
和str_replace_all
text <- c("aeweqe", "sdfeqewew")
# 仅替换第一个匹配的字符
sub("e", "E", text)
# 替换所有匹配的字符
gsub("e","E",text)
[1] "aEweqe" "sdfEqewew"
[2] "aEwEqE" "sdfEqEwEw"
-
regexpr
,gregexpr
找到匹配的字符串的位置。gregexpr与regexpr类似但查找所有匹配的位置,主要使用两个参数,第一个参数指定需要匹配的pattern,第二个参数指定数据x。在stringr中等同于str_locate
和str_locate_all
,类似于python中的find
需要注意,在stringr中的str_locate
函数第一个参数是指定数据x,而第二个参数才是需要匹配的pattern,这里和regexpr是正好相反的。并且str_locate的返回值也是不同的,str_locate返回一个matrix,里面包括了匹配的起始位置和终止位置,而str_locate_all返回一个list,包含所有匹配到的位置的起始位置和终止位置。
# 返回匹配的起点,符合模式的长度等信息
regexpr("def", "abcdefghidef") #def从第4个字符位置上出现
str_locate("abcdefghidef", "def")
# 返回一个list包含所有匹配的位置信息
gregexpr("def", "abcdefghidef")
str_locate_all("abcdefghidef", "def")
[1] 4
attr(,"match.length")
[1] 3
attr(,"index.type")
[1] "chars"
attr(,"useBytes")
[1] TRUE
start end
[1,] 4 6
[[1]]
[1] 4 10
attr(,"match.length")
[1] 3 3
attr(,"index.type")
[1] "chars"
attr(,"useBytes")
[1] TRUE
[[1]]
start end
[1,] 4 6
[2,] 10 12
-
trimws
删除字符串指定位置的指定符号,可以指定c("both", "left", "right")
以及需要删除的符号[ \t\r\n]
,在stringr中也可以使用str_trim(string, side = c("both", "left", "right"))
实现该功能,同样可以指定左边,右边或者两边都去除。
# 去除空格
> str_trim(" String with trailing and leading white space\t")
# 去除换行符,制表符等符号
> str_trim("\n\nString with trailing and leading white space\n\n")
[1] "String with trailing and leading white space"
[2] "String with trailing and leading white space"
-
starts_with
,ends_with
,contains
这些函数允许根据变量的名称选择变量。starts_with匹配字符串是否以某种模式开头;ends_with匹配字符串是否以某种模式结尾;contains指定字符串中是否包含某种模式。类似于python中的startswith
,endswith
。但是和python中的函数不同的是它们不返回逻辑值而是直接返回与其匹配的字符串的坐标。
nms <- names(iris)
starts_with("Petal", nms, ignore.case = TRUE)
[1] 3 4
-
sprintf
这个函数与C语言的函数类似,同样类似于python中的字符串格式化,使用%d
,%.*f
和%s
分别指定整型,浮点型和字符串类型
# 可以使用%占位符输入字符串s,浮点数f以及整型d
sprintf("%d, %.2f, %s", 12, 1.333, 'fff') # 类似于python中的字符串格式化
# 如果还想要操作多个字符串,还需要配合paste(str_c)函数进行操作
print(paste0(sprintf('this is a tmp: %d', 123), sprintf('%s !!!', 'end')))
# 在python中:print('this is a tmp: %d %s !!!' % (123, 'end'))
[1] "12, 1.33, fff"
[2] "this is a tmp: 123end !!!"
apply家族函数
在R语言的使用中一般情况下apply
,lapply
和sapply
可以胜任大部分重复性编码从而避免使用for循环
-
apply
在apply函数中数据通常是矩阵,输入三个参数,第一个参数指定数据,第二个参数指定轴,1代表行,2代表列,我们希望对行进行计算或者对列进行计算就需要指定该参数,第三个参数指定想要实现的功能,需要注意至多只能有一个参数,如果想要执行的函数具有多个参数则需要为其指定默认值
df <- matrix(c(4, 9, 16, 25, 36, 49), 2, 3)
# 对行操作,2*3矩阵按行求和输出两个数值
apply(df, 1, sum)
# 对列操作,2*3矩阵按列求和输出3个数值
apply(df, 2, sum)
[1] 56 83
[2] 13 41 85
-
lapply
即list_apply,lapply的特殊之处在于,它的输出形式为列表(list),并且每一个运算结果(数值)都是一个单独的列表。这就是说,如果运算结果有六个数值,lapply就会输出六个列表,每一个列表里为一个数值。
df <- matrix(c(4, 9, 16, 25, 36, 49), 2, 3)
lapply(df, sqrt)
[[1]]
[1] 2
[[2]]
[1] 3
[[3]]
[1] 4
[[4]]
[1] 5
[[5]]
[1] 6
[[6]]
[1] 7
lapply比较常用的一个原因是我们在执行lapply的时候即可以预先对结果输出形式进行设计,使用do.call
函数对lapply的输出结果进行整理
# 使用do.call设计输出模式
do.call(cbind, lapply(df, sqrt))
# 或者直接转换为矩阵
data.frame(lapply(df, sqrt))
[,1] [,2] [,3] [,4] [,5] [,6]
[1,] 2 3 4 5 6 7
X2 X3 X4 X5 X6 X7
1 2 3 4 5 6 7
-
sapply
是lapply的变种,sapply函数运算方法与lapply完全相同,唯一的区别是sapply输出形式并不是列表(list),而是“数值”。直观地看一下R中sapply的运算结果:
df <- matrix(c(4, 9, 16, 25, 36, 49), 2, 3)
sapply(df, sqrt) # 输出一维向量
[1] 2 3 4 5 6 7
面向对象
在R中具有S3
,S4
,RC
,R6
等四套标准用于实现OOP范式,其中关于S3、S4两种范式在早期的各种扩展包中使用比较多,是基于泛型函数而实现的,在bioconductor中全部使用S4对象。在这里主要介绍一下S4对象以及RC对象。
S4对象
首先需要显式的定义过程setClass
在其中我们需要指定属性和属性类型,其主要参数如下:
- Class:定义类名
- slots:定义属性和属性类型
- prototype:定义属性的默认值
- contains=character():定义父类,继承关系
- validity:定义属性的类型检查
- where:定义存储空间
- sealed:如果设置TRUE,则同名类不能再次定义
- package:定义所属的包
- S3methods:R3.0.0后不建议使用
- representation:R3.0.0后不建议使用
- access:R3.0.0后不建议使用
- version:R3.0.0后不建议使用
# 创建一个S4对象Person,prototype设定默认值
setClass("Person",slots=list(name="character",age="numeric"),prototype=list(age=20))
# 创建Person的子类
setClass("Son",slots=list(father="Person",mother="Person"),contains = "Person")
# 实例化Person对象
father <- new("Person",name="F",age=44)
mother <- new("Person",name="M",age=39)
# 实例化一个Son对象
son <- new("Son",name="S",age=16,father=father,mother=mother)
# S4对象,还支持从一个已经实例化的对象中创建新对象,创建时可以覆盖旧对象的值
n1 <- new("Person",name="n1",age=19)
# 从实例n1中,创建实例n2,并修改name的属性值
n2 <- initialize(n1,name="n2")
# 查看son对象的name属性
son@name
[1] "S"
S4的泛型函数实现有别于S3的实现,S4分离了方法的定义和实现,如在其他语言中我们常说的接口和实现分离。通过setGeneric()
来定义接口,通过setMethod()
来定义现实类。这样可以让S4对象系统,更符合面向对象的特征。
# S4对象的类型检查
setValidity("Person",function(object){
if(object@age <= 0) stop("Age is negative.")
})
# 定义泛型函数work即接口
setGeneric("work",function(object) standardGeneric("work"))
# 定义work的实现,并指定参数类型为Person对象
setMethod("work",signature(object="Person"),function(object) cat(object@name,"is working"))
[1] "work"
通过S4对象系统,把原来的函数定义和调用2步完成的分成4步。
- 定义数据对象类型
- 定义接口函数
- 定义实现函数
- 把数据对象以参数传入到接口函数,执行实现函数
通过S4对象系统,是一个结构化的,完整的面向对象的实现。
RC对象
首先在介绍RC对象之前需要简单介绍一下R的赋值符号,在R中<-
和->
是一对,可以向左和向右赋值,它们的赋值行为均在它们自身的环境层;=
是单向的,作用和<-
基本相同,但对函数中的变量通常使用=
;<<-
处在某一个环境层的代码都拥有读入上一环境层的变量的权限,但相反地,若只通过标准的赋值运算符<-
,是无法向上一环境层写入变量的。若想在上一环境层进行赋值行为,即向上一层次写入变量,则需要用到<<-
(superassignment)运算符,在类中由于没有self区分类属性,所以就需要<<-
对类的属性进行操作
RC(Reference Class)对象系统从底层上改变了原有S3和S4对象系统的设计,去掉了泛型函数,其真正的以类为基础实现面向对象的特征。RC对象看起来类似于python中的class类,在setRefclass
中我们需要指定属性,初始化函数,主体函数等。其参数列表:
- Class: 定义类名
- fields: 定义属性和属性类型
- contains:定义父类,继承关系
- methods: 定义类中的方法
- where: 定义存储空间
在S3,S4的对象系统中,我们实现对象行为时,都是借助于泛型函数实现的。这种实现方法的最大问题是:在定义函数和对象的代码是分离的,需要在运行时,通过判断对象的类型完成方法的调用。而RC对象系统中,方法可以定义在类的内部,通过实例化的对象完成方法的调用。
User <- setRefClass("User",fields=list(name="character",favorite='vector'),
# 方法属性
methods= list(
# 增加一个兴趣
addFavorite = function(x){
favorite <<- c(favorite,x)
},
# 删除一个兴趣
delFavorite = function(x){
favorite <<- favorite[-which(favorite==x)]
},
# 重新定义兴趣列表
setFavorite = function(x){
favorite <<- x
}
))
# 类比python的类,RC类与python的类已经很相似了
class User(object):
def __init__(self, name:str, favorite:list):
self.name = name
self.favorite = favorite
def addFavorite(self, x):
self.favorite.append(x)
def delFavorite(self, x):
self.favorite.remove(x)
def setFavorite(self, x):
self.favorite = x
RC类中的内置方法:
- initialize类的初始化函数,用于设置属性的默认值,只有在类定义的方法中使用。
- callSuper调用父类的同名方法,只能在类定义的方法中使用
- copy复制实例化对象的所有属性
- initFields给对象的属性赋值
- field查看属性或给属性赋值
- getClass查看对象的类定义
- getRefClass()同getClass()
- show 查看当前对象
- export查看属性值以类为作用域
- import 把一个对象的属性值赋值给另一个对象
- trace跟踪对象中方法调用,用于程序debug
- untrace取消跟踪
- usingMethods用于实现方法调用,只能在类定义的方法中使用,这个方法不利于程序的健壮性,所以不建议使用。
# 创建父类
User <- setRefClass("User",
fields=list(name="character",level="numeric"),
methods = list(
# 调用类的初始化函数
initialize=function(name,level){
print("User::initialize")
name <<- "conan"
level <<- 1
},
addLevel = function(x){
print("User::addlevel")
level<<-level+x
},
addHighLevel = function(){
print("user::addHighLevel")
addLevel(2)
}
))
# 子类Member调用callSuper方法重写父类方法,类似python中super
Member <- setRefClass("Member",contains="User",
# 子类中的属性
fields = list(age='numeric'),
methods=list(
# 覆盖父类的同名方法
addLevel = function(x){
print("Member::addLevel")
callSuper(x)
level <<- level+1
}
)
网友评论