Como evitar um loop em r: selecionando itens de uma lista
Pergunta
Eu poderia resolver isso usando loops, mas estou tentando pensar em vetores para que meu código seja mais R-Sque.
Eu tenho uma lista de nomes. O formato é primeironame_lastname. Quero sair desta lista uma lista separada com apenas os primeiros nomes. Parece que não consigo entender como fazer isso. Aqui estão alguns dados de exemplo:
t <- c("bob_smith","mary_jane","jose_chung","michael_marx","charlie_ivan")
tsplit <- strsplit(t,"_")
que se parece com o seguinte:
> tsplit
[[1]]
[1] "bob" "smith"
[[2]]
[1] "mary" "jane"
[[3]]
[1] "jose" "chung"
[[4]]
[1] "michael" "marx"
[[5]]
[1] "charlie" "ivan"
Eu poderia sair do que quero usando loops como este:
for (i in 1:length(tsplit)){
if (i==1) {t_out <- tsplit[[i]][1]} else{t_out <- append(t_out, tsplit[[i]][1])}
}
o que me daria isso:
t_out
[1] "bob" "mary" "jose" "michael" "charlie"
Então, como posso fazer isso sem loops?
Solução
Você pode usar apply
(ou 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"
Outras dicas
E mais uma abordagem:
t <- c("bob_smith","mary_jane","jose_chung","michael_marx","charlie_ivan")
pieces <- strsplit(t,"_")
sapply(pieces, "[", 1)
Em palavras, a última linha extrai o primeiro elemento de cada componente da lista e a simplifica em um vetor.
Como é que isso funciona? Bem, você precisa perceber uma maneira alternativa de escrever x[1]
é "["(x, 1)
, ou seja, há uma função chamada [
Isso é subconjunto. o sapply
A chamada aplica chama essa função uma vez para cada elemento da lista original, passando em dois argumentos, o elemento da lista e 1.
A vantagem dessa abordagem sobre os outros é que você pode extrair vários elementos da lista sem precisar recomputar as divisões. Por exemplo, o sobrenome seria sapply(pieces, "[", 2)
. Depois de se acostumar com esse idioma, é muito fácil de ler.
Que tal:
tlist <- c("bob_smith","mary_jane","jose_chung","michael_marx","charlie_ivan")
fnames <- gsub("(_.*)$", "", tlist)
# _.* matches the underscore followed by a string of characters
# the $ anchors the search at the end of the input string
# so, underscore followed by a string of characters followed by the end of the input string
para a abordagem regex?
A respeito:
t <- c("bob_smith","mary_jane","jose_chung","michael_marx","charlie_ivan")
sub("_.*", "", t)
Duvido que esta seja a solução mais elegante, mas supera o loop:
t.df <- data.frame(tsplit)
t.df[1, ]
A conversão de listas em quadros de dados é a única maneira de fazer com que eles façam o que eu quiser. Estou ansioso para ler respostas de pessoas que realmente entendem como lidar com listas.
Você quase teve. Isto verdade é apenas uma questão de
- usando um dos
*apply
funções para dar um loop sobre sua lista existente, muitas vezes começo comlapply
E às vezes muda parasapply
- Adicione uma função anônima que opera em um dos elementos da lista de cada vez
- Você já sabia que era
strsplit(string, splitterm)
e que você precisa do estranho[[1]][1]
Para escolher o primeiro mandato da resposta - Basta juntar tudo, começando com uma variável preferida Namne (como ficamos afastados
t
ouc
e amigos)
que dá
> 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"
>
Você poderia usar 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"
Pode haver uma maneira melhor de retirar apenas as entradas indexadas, mas, de qualquer forma, você não terá um loop.
E uma outra abordagem, com base no exemplo de Ulhist de Brentonk ...
tlist <- c("bob_smith","mary_jane","jose_chung","michael_marx","charlie_ivan")
tsplit <- unlist(strsplit(tlist,"_"))
fnames <- tsplit[seq(1:length(tsplit))%%2 == 1]
Eu usaria o seguinte método baseado em 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"
A grande vantagem desse método é que ele resolve o problema equivalente para sobrenomes ao mesmo tempo:
> x[2,]
[1] "smith" "jane" "chung" "marx" "ivan"
A desvantagem é que você precisará ter certeza de que todos os nomes estão em conformidade com o firstname_lastname
estrutura; Se houver, não, esse método quebrará.
do original tsplit
Objeto de lista fornecida no início, este comando fará:
unlist(lapply(tsplit,function(x) x[1]))
Ele extrai o primeiro elemento de todos os elementos da lista e transforma uma lista em um vetor. Unistar primeiro a uma matriz e, em seguida, extrair a coluna do punho também é bom, mas você depende do fato de que todos os elementos da lista têm o mesmo comprimento. Aqui está a saída:
> 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"