Dividir a coluna da sequência de quadros de dados em várias colunas

245

Eu gostaria de pegar os dados do formulário

before = data.frame(attr = c(1,30,4,6), type=c('foo_and_bar','foo_and_bar_2'))
  attr          type
1    1   foo_and_bar
2   30 foo_and_bar_2
3    4   foo_and_bar
4    6 foo_and_bar_2

e use split()na coluna " type" acima para obter algo parecido com isto:

  attr type_1 type_2
1    1    foo    bar
2   30    foo  bar_2
3    4    foo    bar
4    6    foo  bar_2

Eu vim com algo incrivelmente complexo envolvendo alguma forma de applyque funcionou, mas desde então eu perdi isso. Parecia muito complicado para ser o melhor caminho. Posso usar strsplitcomo abaixo, mas não sei como recuperar isso em duas colunas no quadro de dados.

> strsplit(as.character(before$type),'_and_')
[[1]]
[1] "foo" "bar"

[[2]]
[1] "foo"   "bar_2"

[[3]]
[1] "foo" "bar"

[[4]]
[1] "foo"   "bar_2"

Obrigado por qualquer indicação. Ainda não entendi muito bem as listas R.

jkebinger
fonte

Respostas:

279

Usar stringr::str_split_fixed

library(stringr)
str_split_fixed(before$type, "_and_", 2)
Hadley
fonte
2
isso funcionou muito bem para o meu problema hoje também ... mas estava adicionando um 'c' no início de cada linha. Alguma idéia do porquê disso ??? left_right <- str_split_fixed(as.character(split_df),'\">',2)
aluno
Gostaria de dividir com um padrão que tenha "...", quando aplico essa função, ela não retorna nada. Qual poderia ser o problema? meu tipo é algo como "teste ... pontuação"
user3841581 14/03
2
@ user3841581 - sua antiga consulta, eu sei, mas isso é abordado na documentação - str_split_fixed("aaa...bbb", fixed("..."), 2)funciona bem com fixed()"Corresponder a uma string fixa" no pattern=argumento. .significa 'qualquer caractere' em regex.
Thelatemail
Obrigado hadley, método muito conveniente, mas há uma coisa que pode ser aprimorada: se houver NA na coluna original, após a separação, ela se tornará uma string vazia sevaral nas colunas de resultado, o que é indesejável, quero manter a NA ainda NA depois separação
cloudscomputes
Funciona bem, ou seja, se o separador estiver faltando! ou seja, se eu tiver um vetor 'a <-c ("1N", "2N")' que gostaria de separar nas colunas '1,1, "N", "N"', eu executo 'str_split_fixed (s, " ", 2) '. Só não sei como nomear minhas novas colunas nesta abordagem, 'col1 <-c (1,1)' e 'col2 <-c ("N", "N") "
maycca
173

Outra opção é usar o novo pacote tidyr.

library(dplyr)
library(tidyr)

before <- data.frame(
  attr = c(1, 30 ,4 ,6 ), 
  type = c('foo_and_bar', 'foo_and_bar_2')
)

before %>%
  separate(type, c("foo", "bar"), "_and_")

##   attr foo   bar
## 1    1 foo   bar
## 2   30 foo bar_2
## 3    4 foo   bar
## 4    6 foo bar_2
Hadley
fonte
Existe uma maneira de limitar o número de divisões com separado? Digamos que eu queira dividir o '_' apenas uma vez (ou fazê-lo str_split_fixede adicionar colunas ao quadro de dados existente)?
JelenaČuklina 11/01
66

5 anos depois, adicionando a data.tablesolução obrigatória

library(data.table) ## v 1.9.6+ 
setDT(before)[, paste0("type", 1:2) := tstrsplit(type, "_and_")]
before
#    attr          type type1 type2
# 1:    1   foo_and_bar   foo   bar
# 2:   30 foo_and_bar_2   foo bar_2
# 3:    4   foo_and_bar   foo   bar
# 4:    6 foo_and_bar_2   foo bar_2

Nós também poderia tanto ter certeza de que as colunas resultantes terão tipos corretos e melhorar o desempenho através da adição type.converte fixedargumentos (desde "_and_"não é realmente um regex)

setDT(before)[, paste0("type", 1:2) := tstrsplit(type, "_and_", type.convert = TRUE, fixed = TRUE)]
David Arenburg
fonte
se o número de seus '_and_'padrões variar, você poderá descobrir o número máximo de correspondências (futuras colunas) commax(lengths(strsplit(before$type, '_and_')))
andschar
Esta é a minha resposta favorita, funciona muito bem! Poderia explicar como funciona. Por que transpor (strsplit (…)) e não é paste0 para concatenar strings - não dividi-las ...
Gecko
1
@ Gecko Não sei ao certo qual é a questão. Se você usá- strsplitlo, ele cria um único vetor com 2 valores em cada slot, então o tstrsplittranspõe para 2 vetores com um único valor em cada um. paste0é usado apenas para criar os nomes das colunas, não é usado nos valores. No LHS da equação estão os nomes das colunas, no RHS está a operação split + transpose na coluna. :=significa " atribuir no local "; portanto, você não vê o <-operador de atribuição lá.
David Arenburg
57

Ainda outra abordagem: use rbindem out:

before <- data.frame(attr = c(1,30,4,6), type=c('foo_and_bar','foo_and_bar_2'))  
out <- strsplit(as.character(before$type),'_and_') 
do.call(rbind, out)

     [,1]  [,2]   
[1,] "foo" "bar"  
[2,] "foo" "bar_2"
[3,] "foo" "bar"  
[4,] "foo" "bar_2"

E para combinar:

data.frame(before$attr, do.call(rbind, out))
Aniko
fonte
4
Outra alternativa nas versões R mais recentes éstrcapture("(.*)_and_(.*)", as.character(before$type), data.frame(type_1 = "", type_2 = ""))
alexis_laz 10/11
36

Observe que sapply com "[" pode ser usado para extrair o primeiro ou o segundo itens dessas listas, portanto:

before$type_1 <- sapply(strsplit(as.character(before$type),'_and_'), "[", 1)
before$type_2 <- sapply(strsplit(as.character(before$type),'_and_'), "[", 2)
before$type <- NULL

E aqui está um método gsub:

before$type_1 <- gsub("_and_.+$", "", before$type)
before$type_2 <- gsub("^.+_and_", "", before$type)
before$type <- NULL
IRTFM
fonte
31

aqui está um liner da mesma maneira que a solução da aniko, mas usando o pacote stringr de hadley:

do.call(rbind, str_split(before$type, '_and_'))
Ramnath
fonte
1
Boa captura, melhor solução para mim. Embora um pouco mais lento do que com o stringrpacote.
Melka 30/03
20

Para adicionar às opções, você também pode usar minha splitstackshape::cSplitfunção assim:

library(splitstackshape)
cSplit(before, "type", "_and_")
#    attr type_1 type_2
# 1:    1    foo    bar
# 2:   30    foo  bar_2
# 3:    4    foo    bar
# 4:    6    foo  bar_2
A5C1D2H2I1M1N2O1R2T1
fonte
3 anos depois - esta opção está funcionando melhor para um problema semelhante que eu tenho - no entanto, o quadro de dados com o qual estou trabalhando tem 54 colunas e preciso dividir todas elas em duas. Existe uma maneira de fazer isso usando esse método - exceto digitando o comando acima 54 vezes? Muito obrigado, Nicki.
Nicki
@ Nicki, Você tentou fornecer um vetor dos nomes das colunas ou das posições das colunas? Isso deve fazê-lo ....
A5C1D2H2I1M1N2O1R2T1
Não estava apenas renomeando as colunas - eu precisava literalmente dividir as colunas como acima, dobrando efetivamente o número de colunas no meu df. A seguir foi o que eu usei no final: df2 <- csplit (DF1, splitCols = 1:54, "/")
Nicki
14

Uma maneira fácil é usar sapply()e a [função:

before <- data.frame(attr = c(1,30,4,6), type=c('foo_and_bar','foo_and_bar_2'))
out <- strsplit(as.character(before$type),'_and_')

Por exemplo:

> data.frame(t(sapply(out, `[`)))
   X1    X2
1 foo   bar
2 foo bar_2
3 foo   bar
4 foo bar_2

sapply()O resultado é uma matriz e precisa ser transposta e lançada de volta para um quadro de dados. São algumas manipulações simples que produzem o resultado desejado:

after <- with(before, data.frame(attr = attr))
after <- cbind(after, data.frame(t(sapply(out, `[`))))
names(after)[2:3] <- paste("type", 1:2, sep = "_")

Neste ponto, afteré o que você queria

> after
  attr type_1 type_2
1    1    foo    bar
2   30    foo  bar_2
3    4    foo    bar
4    6    foo  bar_2
Gavin Simpson
fonte
12

O assunto está quase esgotado, mas gostaria de oferecer uma solução para uma versão um pouco mais geral, na qual você não sabe a priori o número de colunas de saída. Então, por exemplo, você tem

before = data.frame(attr = c(1,30,4,6), type=c('foo_and_bar','foo_and_bar_2', 'foo_and_bar_2_and_bar_3', 'foo_and_bar'))
  attr                    type
1    1             foo_and_bar
2   30           foo_and_bar_2
3    4 foo_and_bar_2_and_bar_3
4    6             foo_and_bar

Não podemos usar o dplyr separate()porque não sabemos o número das colunas de resultados antes da divisão, então criei uma função que usa stringrpara dividir uma coluna, dado o padrão e um prefixo de nome para as colunas geradas. Espero que os padrões de codificação utilizados estejam corretos.

split_into_multiple <- function(column, pattern = ", ", into_prefix){
  cols <- str_split_fixed(column, pattern, n = Inf)
  # Sub out the ""'s returned by filling the matrix to the right, with NAs which are useful
  cols[which(cols == "")] <- NA
  cols <- as.tibble(cols)
  # name the 'cols' tibble as 'into_prefix_1', 'into_prefix_2', ..., 'into_prefix_m' 
  # where m = # columns of 'cols'
  m <- dim(cols)[2]

  names(cols) <- paste(into_prefix, 1:m, sep = "_")
  return(cols)
}

Podemos então usar split_into_multipleem um tubo dplyr da seguinte maneira:

after <- before %>% 
  bind_cols(split_into_multiple(.$type, "_and_", "type")) %>% 
  # selecting those that start with 'type_' will remove the original 'type' column
  select(attr, starts_with("type_"))

>after
  attr type_1 type_2 type_3
1    1    foo    bar   <NA>
2   30    foo  bar_2   <NA>
3    4    foo  bar_2  bar_3
4    6    foo    bar   <NA>

E então podemos usar gatherpara arrumar ...

after %>% 
  gather(key, val, -attr, na.rm = T)

   attr    key   val
1     1 type_1   foo
2    30 type_1   foo
3     4 type_1   foo
4     6 type_1   foo
5     1 type_2   bar
6    30 type_2 bar_2
7     4 type_2 bar_2
8     6 type_2   bar
11    4 type_3 bar_3
Yannis P.
fonte
Saúde, acho que isso é extremamente útil.
Tjebo 04/06
8

Aqui está um liner básico R que se sobrepõe a várias soluções anteriores, mas retorna um data.frame com os nomes próprios.

out <- setNames(data.frame(before$attr,
                  do.call(rbind, strsplit(as.character(before$type),
                                          split="_and_"))),
                  c("attr", paste0("type_", 1:2)))
out
  attr type_1 type_2
1    1    foo    bar
2   30    foo  bar_2
3    4    foo    bar
4    6    foo  bar_2

Ele usa strsplitpara dividir a variável e data.framecom do.call/ rbindpara colocar os dados novamente em um data.frame. A melhoria incremental adicional é o uso de setNamespara adicionar nomes de variáveis ​​ao data.frame.

lmo
fonte
6

Esta questão é bastante antiga, mas vou adicionar a solução que achei mais simples no momento.

library(reshape2)
before = data.frame(attr = c(1,30,4,6), type=c('foo_and_bar','foo_and_bar_2'))
newColNames <- c("type1", "type2")
newCols <- colsplit(before$type, "_and_", newColNames)
after <- cbind(before, newCols)
after$type <- NULL
after
Swifty McSwifterton
fonte
Isso é de longe o mais fácil quando se trata de gerenciar vetores df
Apricot
5

Desde o R versão 3.4.0, você pode usar a strcapture()partir do pacote utils (incluído nas instalações R básicas), vinculando a saída às outras colunas.

out <- strcapture(
    "(.*)_and_(.*)",
    as.character(before$type),
    data.frame(type_1 = character(), type_2 = character())
)

cbind(before["attr"], out)
#   attr type_1 type_2
# 1    1    foo    bar
# 2   30    foo  bar_2
# 3    4    foo    bar
# 4    6    foo  bar_2
Rich Scriven
fonte
4

Outra abordagem que você deseja manter strsplit()é usar o unlist()comando Aqui está uma solução nesse sentido.

tmp <- matrix(unlist(strsplit(as.character(before$type), '_and_')), ncol=2,
   byrow=TRUE)
after <- cbind(before$attr, as.data.frame(tmp))
names(after) <- c("attr", "type_1", "type_2")
ashaw
fonte
4

base, mas provavelmente lento:

n <- 1
for(i in strsplit(as.character(before$type),'_and_')){
     before[n, 'type_1'] <- i[[1]]
     before[n, 'type_2'] <- i[[2]]
     n <- n + 1
}

##   attr          type type_1 type_2
## 1    1   foo_and_bar    foo    bar
## 2   30 foo_and_bar_2    foo  bar_2
## 3    4   foo_and_bar    foo    bar
## 4    6 foo_and_bar_2    foo  bar_2
jpmorris
fonte
1

Aqui está outra solução R básica. Podemos usar, read.tablemas como ele aceita apenas um separgumento de um byte e aqui temos o separador de vários bytes, podemos gsubsubstituir o separador de vários bytes por qualquer separador de um byte e usá-lo como separgumento emread.table

cbind(before[1], read.table(text = gsub('_and_', '\t', before$type), 
                 sep = "\t", col.names = paste0("type_", 1:2)))

#  attr type_1 type_2
#1    1    foo    bar
#2   30    foo  bar_2
#3    4    foo    bar
#4    6    foo  bar_2

Nesse caso, também podemos torná-lo mais curto, substituindo-o pelo separgumento padrão, para não precisarmos mencioná-lo explicitamente

cbind(before[1], read.table(text = gsub('_and_', ' ', before$type), 
                 col.names = paste0("type_", 1:2)))
Ronak Shah
fonte