如何在R中避免循环:从列表中选择项目

31

我可以使用循环解决这个问题,但我正在尝试思考向量化的方法,以便我的代码更像R。

我有一个名字列表。格式为firstname_lastname。我想从这个列表中获取仅包含名字的分离列表。我似乎无法理解如何做到这一点。以下是一些示例数据:

t <- c("bob_smith","mary_jane","jose_chung","michael_marx","charlie_ivan")
tsplit <- strsplit(t,"_")

它看起来像这样:

> tsplit
[[1]]
[1] "bob"   "smith"

[[2]]
[1] "mary" "jane"

[[3]]
[1] "jose"  "chung"

[[4]]
[1] "michael" "marx"   

[[5]]
[1] "charlie" "ivan"   

我可以使用如下的循环来实现所需功能:

我可以使用以下方式实现所需功能:

for (i in 1:length(tsplit)){
    if (i==1) {t_out <- tsplit[[i]][1]} else{t_out <- append(t_out, tsplit[[i]][1])} 
}

这将会给我这个:
t_out
[1] "bob"     "mary"    "jose"    "michael" "charlie"

那么我如何在不使用循环的情况下完成这个任务呢?

2
顺便提一下,如果您能详细说明这与您以前关于同一主题的问题有何不同,那将会很有帮助:https://dev59.com/nnRC5IYBdhLWcg3wAcQ3https://dev59.com/u0jSa4cB1Zd3GeqPIcuJhttps://dev59.com/8XRB5IYBdhLWcg3w-8Ho - Dirk Eddelbuettel
4
你是指我完全无法学会如何在R中应用函数吗?是的,同样的问题,有不同的细微差别。谢谢你提醒我。 - JD Long
10个回答

43

还有一种方法:

t <- c("bob_smith","mary_jane","jose_chung","michael_marx","charlie_ivan")
pieces <- strsplit(t,"_")
sapply(pieces, "[", 1)

简言之,最后一行代码提取列表中每个组件的第一个元素,然后将其简化为向量。

这是如何实现的呢?你需要意识到另一种写法是x[1]可以表示为"["(x, 1),也就是说有一个名为[的函数进行子集选择。sapply调用了这个函数,对原始列表中的每个元素应用此函数,并传递两个参数,即列表元素和1。

与其他方法相比,这种方法的优势在于,您可以从列表中提取多个元素而无需重新计算拆分。例如,最后一个名称将是sapply(pieces, "[", 2)。一旦您习惯了这种惯用语法,就会发现它非常容易阅读。


Hadley,我看到这个代码可以运行,但是我完全不知道它为什么能够运行。是否有一个隐含的 "]" 符号?你能详细解释一下吗?我的 R 语言水平显然很弱。 - JD Long
顺带一提,如果你打算在分割字符串时使用固定字符串而不是正则表达式,你可能需要考虑将 fixed=TRUE 传递给 strsplit 函数。我发现这可以极大地影响 strsplit 的速度。 - Jonathan Chang
6
R中的所有运算符都是函数 - 中缀运算符可以用前缀表示法书写。TRUE || FALSE可以写成||(TRUE,FALSE),a[b]可以写成[ (a,b),甚至赋值运算符a[b] <- TRUE也可以写成[<-(a,b,value=TRUE)。R是神奇的。 - hatmatrix
我喜欢这个能够工作,也喜欢Stephen的评论“R是魔法”。这是真的! - PaulHurleyuk
如果是一份很长的列表,你想要最后一个元素怎么办? - zach
显示剩余3条评论

26
您可以使用 apply(或 sapply)函数。
t <- c("bob_smith","mary_jane","jose_chung","michael_marx","charlie_ivan")
f <- function(s) strsplit(s, "_")[[1]][1]
sapply(t, f)

bob_smith    mary_jane   jose_chung michael_marx charlie_ivan 

       "bob"       "mary"       "jose"    "michael"    "charlie" 

请参考:R中“apply”的简要介绍


10

这样怎么样:

tlist <- c("bob_smith","mary_jane","jose_chung","michael_marx","charlie_ivan")
fnames <- gsub("(_.*)$", "", tlist)
# _.*匹配下划线后面的所有字符
# $锚定在输入字符串的末尾
# 因此,下划线后跟一串字符,然后是输入字符串的末尾

用于正则表达式的方法如何?


1
+1 以表彰最快的速度。使用rep(t, 1e4)测试,我的方法花费了83.23秒(其中81.41秒用于转换为数据框!),David的方法花费了4.39秒,而你的方法只花费了0.81秒。我认为它的输出效果也是最好的。 - Matt Parker
1
谢谢,马特...我在想每个解决方案的效率如何! - William Doane
1
这真的很有用。我只是认为strsplit部分是一个常识。哇,很高兴看到另一种方法。 - JD Long

9

关于什么:

t <- c("bob_smith","mary_jane","jose_chung","michael_marx","charlie_ivan")

sub("_.*", "", t)

7
我怀疑这不是最优雅的解决方案,但它比循环要好。
t.df <- data.frame(tsplit)
t.df[1, ]

将列表转换为数据框是我能够让它们按照我的意愿工作的唯一方法。我期待着看到那些真正理解如何处理列表的人提供的答案。


我喜欢这个。我“理解”data.frame结构。而且由于我的真实数据在每个“名称”中具有相同数量的项目,因此这应该不会更少的内存效率。为什么我没想到这个! - JD Long
请注意,对于较大的数据,这种方法需要很长时间-请参阅我在William Doane的答案中的评论。 - Matt Parker

4
你差不多就成功了。实际上,这只是一个关于以下几个步骤的问题:
  1. 使用其中一个*apply函数来循环遍历你现有的列表,我通常会从lapply开始,有时会切换到sapply
  2. 添加一个匿名函数,它逐个操作列表元素
  3. 你已经知道了strsplit(string, splitterm),并且需要使用奇数的[[1]][1]来选择答案的第一个项
  4. 将它们组合起来,从首选变量名开始(我们避免使用tc等)
这样做可以得到:
> tlist <- c("bob_smith","mary_jane","jose_chung","michael_marx","charlie_ivan") 
> fnames <- sapply(tlist, function(x) strsplit(x, "_")[[1]][1]) 
> fnames 
  bob_smith    mary_jane   jose_chung michael_marx charlie_ivan   
      "bob"       "mary"       "jose"    "michael"    "charlie" 
>

我真的很难理解如何正确使用R中的apply函数。有些日子感觉就像在反向行驶一样学车一样困难,虽然简单的环形交叉路口会导致心理堵塞。 - JD Long
1
我以类似于腿的方式完成它。你知道strsplit。你知道你需要一个参数为'anon function'的apply family。只需将它们粘在一起...最后,不是挑剔,我发布了这个答案,比你接受的“the”答案本质上相同但不那么冗长。 - Dirk Eddelbuettel
笔误:应为“类似乐高”,而非“类似腿”。 - Dirk Eddelbuettel
Dirk,我在学习R语言时发现一个问题,就是很难看出两个问题之间的相似之处。我认为随着经验的增长,我们能够更快地选择有意义的类比。我正在慢慢地学会发现模式。我很感谢你上面的评论,关于找出乐高积木是什么。例如,我还在提高自己的能力,看到一个问题并意识到我需要一个匿名函数。 - JD Long

3
您可以使用unlist()函数:
> tsplit <- unlist(strsplit(t,"_"))
> tsplit
 [1] "bob"     "smith"   "mary"    "jane"    "jose"    "chung"   "michael"
 [8] "marx"    "charlie" "ivan"   
> t_out <- tsplit[seq(1, length(tsplit), by = 2)]
> t_out
[1] "bob"     "mary"    "jose"    "michael" "charlie"

可能有更好的方法来提取只有奇数索引的条目,但无论如何你都不需要使用循环。


不太理想,因为你需要强制使用“by = 2”来选择匹配的元素。 - Dirk Eddelbuettel

2

还有一种方法,基于brentonk的unlist示例...

tlist <- c("bob_smith","mary_jane","jose_chung","michael_marx","charlie_ivan")
tsplit <- unlist(strsplit(tlist,"_"))
fnames <- tsplit[seq(1:length(tsplit))%%2 == 1]

这段代码的作用是将一个包含下划线的字符串列表拆分成单独的字符串,并从中提取出所有的奇数项。

1
我会使用基于unlist()的以下方法:
> t <- c("bob_smith","mary_jane","jose_chung","michael_marx","charlie_ivan")
> tsplit <- strsplit(t,"_")
> 
> x <- matrix(unlist(tsplit), 2)
> x[1,]
[1] "bob"     "mary"    "jose"    "michael" "charlie"

这种方法的最大优点是同时解决了姓氏的等价问题:
> x[2,]
[1] "smith" "jane"  "chung" "marx"  "ivan" 

缺点是您需要确保所有名称都符合firstname_lastname结构; 如果有任何不符合要求的名称,则此方法将无法正常工作。


0
从一开始给定的原始tsplit列表对象,此命令将执行以下操作:
unlist(lapply(tsplit,function(x) x[1]))

它提取所有列表元素的第一个元素,然后将列表转换为向量。首先将第一个元素解压成矩阵,然后提取第一列也可以,但这样你就依赖于所有列表元素具有相同的长度。以下是输出:

> tsplit

[[1]]
[1] "bob"   "smith"

[[2]]
[1] "mary" "jane"

[[3]]
[1] "jose"  "chung"

[[4]]
[1] "michael" "marx"   

[[5]]
[1] "charlie" "ivan"   

> lapply(tsplit,function(x) x[1])

[[1]]
[1] "bob"

[[2]]
[1] "mary"

[[3]]
[1] "jose"

[[4]]
[1] "michael"

[[5]]
[1] "charlie"

> unlist(lapply(tsplit,function(x) x[1]))

[1] "bob"     "mary"    "jose"    "michael" "charlie"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接