Fractionner la colonne de chaîne du bloc de données en plusieurs colonnes

246

J'aimerais prendre des données du formulaire

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

et utilisez split()la colonne " type" ci-dessus pour obtenir quelque chose comme ceci:

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

J'ai trouvé quelque chose d'incroyablement complexe impliquant une certaine forme de applycela qui a fonctionné, mais je l'ai depuis égaré. Cela semblait beaucoup trop compliqué pour être le meilleur moyen. Je peux utiliser strsplitcomme ci-dessous, mais je ne sais pas comment récupérer cela en 2 colonnes dans le bloc de données.

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

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

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

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

Merci pour tout pointeur. Je n'ai pas encore tout à fait grillé les listes R.

jkebinger
la source

Réponses:

280

Utilisation stringr::str_split_fixed

library(stringr)
str_split_fixed(before$type, "_and_", 2)
hadley
la source
2
cela a très bien fonctionné pour mon problème aujourd'hui aussi .. mais il ajoutait un «c» au début de chaque ligne. Une idée pourquoi est-ce ??? left_right <- str_split_fixed(as.character(split_df),'\">',2)
LearneR
Je voudrais diviser avec un motif qui a "...", lorsque j'applique cette fonction, elle ne renvoie rien. Quel pourrait être le problème. mon type est quelque chose comme "test ... score"
user3841581
2
@ user3841581 - ancienne requête que je connais, mais qui est couverte dans la documentation - str_split_fixed("aaa...bbb", fixed("..."), 2)fonctionne très bien avec fixed()"Match a fixed string" dans l' pattern=argument. .signifie «n'importe quel caractère» en regex.
courrier
Merci hadley, méthode très pratique, mais il y a une chose qui peut être améliorée, s'il y a NA dans la colonne d'origine, après la séparation, cela deviendra plusieurs chaînes vides dans les colonnes de résultat, ce qui n'est pas souhaité, je veux garder le NA toujours NA après separation
cloudscomputes
Fonctionne bien c'est à dire si le séparateur est manquant! c'est-à-dire si j'ai un vecteur 'a <-c ("1N", "2N")' que je voudrais séparer dans les colonnes '1,1, "N", "N"' je lance 'str_split_fixed (s, " ", 2)». Je ne sais pas comment nommer mes nouvelles colonnes dans cette approche, 'col1 <-c (1,1)' et 'col2 <-c ("N", "N")'
maycca
175

Une autre option consiste à utiliser le nouveau package 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
la source
Existe-t-il un moyen de limiter le nombre de scissions avec séparé? Imaginons que je souhaite fractionner une seule fois sur «_» (ou le faire avec str_split_fixedet en ajoutant des colonnes à la trame de données existante)?
JelenaČuklina
67

5 ans après l'ajout de la data.tablesolution obligatoire

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

Nous pourrions également à la fois nous assurer que les colonnes résultantes auront des types corrects et améliorer les performances en ajoutant type.convertet des fixedarguments (car ce "_and_"n'est pas vraiment une expression régulière)

setDT(before)[, paste0("type", 1:2) := tstrsplit(type, "_and_", type.convert = TRUE, fixed = TRUE)]
David Arenburg
la source
si le nombre de vos '_and_'patterns varie, vous pouvez trouver le nombre maximum de correspondances (ie futures colonnes) avecmax(lengths(strsplit(before$type, '_and_')))
andschar
Ceci est ma réponse préférée, fonctionne très bien! Pourriez-vous s'il vous plaît expliquer comment cela fonctionne. Pourquoi transposer (strsplit (…)) et ne pas coller0 pour concaténer des chaînes - ne pas les séparer ...
Gecko
1
@ Gecko, je ne sais pas quelle est la question. Si vous l'utilisez, strsplitil crée un seul vecteur avec 2 valeurs dans chaque emplacement, il le tstrsplittranspose donc en 2 vecteurs avec une seule valeur dans chacun. paste0est juste utilisé pour créer les noms des colonnes, il n'est pas utilisé sur les valeurs. Sur le LHS de l'équation se trouvent les noms des colonnes, sur le RHS se trouve l'opération split + transpose sur la colonne. :=signifie " assign in place ", donc vous ne voyez pas l' <-opérateur d'affectation là-bas.
David Arenburg
58

Encore une autre approche: utiliser rbindsur 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"

Et pour combiner:

data.frame(before$attr, do.call(rbind, out))
Aniko
la source
4
Une autre alternative sur les nouvelles versions R eststrcapture("(.*)_and_(.*)", as.character(before$type), data.frame(type_1 = "", type_2 = ""))
alexis_laz
37

Notez que sapply avec "[" peut être utilisé pour extraire le premier ou le deuxième élément de ces listes afin:

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

Et voici une méthode gsub:

before$type_1 <- gsub("_and_.+$", "", before$type)
before$type_2 <- gsub("^.+_and_", "", before$type)
before$type <- NULL
IRTFM
la source
32

voici une doublure dans le même sens que la solution d'aniko, mais en utilisant le package stringr de hadley:

do.call(rbind, str_split(before$type, '_and_'))
Ramnath
la source
1
Bonne prise, meilleure solution pour moi. Bien qu'un peu plus lent qu'avec le stringrpaquet.
Melka
20

Pour ajouter aux options, vous pouvez également utiliser ma splitstackshape::cSplitfonction comme ceci:

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
la source
3 ans plus tard - cette option fonctionne mieux pour un problème similaire que j'ai - mais la trame de données avec laquelle je travaille a 54 colonnes et je dois les diviser toutes en deux. Y a-t-il un moyen de le faire en utilisant cette méthode - à moins de taper 54 fois la commande ci-dessus? Merci beaucoup, Nicki.
Nicki
@ Nicki, Avez-vous essayé de fournir un vecteur des noms de colonnes ou des positions des colonnes? Cela devrait le faire ....
A5C1D2H2I1M1N2O1R2T1
Il ne s'agissait pas simplement de renommer les colonnes - j'avais besoin de diviser littéralement les colonnes comme ci-dessus, doublant ainsi le nombre de colonnes dans mon df. Ce qui suit est ce que j'ai utilisé à la fin: df2 <- cSplit (df1, splitCols = 1:54, "/")
Nicki
14

Un moyen simple est d'utiliser sapply()et la [fonction:

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_')

Par exemple:

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

sapply()Le résultat est une matrice et doit être transposé et converti en une trame de données. Ce sont alors quelques manipulations simples qui donnent le résultat souhaité:

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

À ce stade, afterc'est ce que vous vouliez

> 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
la source
12

Le sujet est presque épuisé, j'aimerais cependant proposer une solution à une version un peu plus générale où l'on ne connaît pas a priori le nombre de colonnes de sortie. Ainsi, par exemple, vous avez

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

Nous ne pouvons pas utiliser dplyr separate()parce que nous ne connaissons pas le nombre de colonnes de résultat avant le fractionnement, j'ai donc créé une fonction qui utilise stringrpour fractionner une colonne, étant donné le modèle et un préfixe de nom pour les colonnes générées. J'espère que les modèles de codage utilisés sont corrects.

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)
}

On peut ensuite utiliser split_into_multipledans une pipe dplyr comme suit:

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>

Et puis nous pouvons utiliser gatherpour ranger ...

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.
la source
À la vôtre, je pense que c'est extrêmement utile.
Tjebo
8

Voici une doublure de base R one qui chevauche un certain nombre de solutions précédentes, mais renvoie un data.frame avec les noms propres.

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

Il utilise strsplitpour décomposer la variable, et data.frameavec do.call/ rbindpour remettre les données dans un data.frame. L'amélioration incrémentielle supplémentaire est l'utilisation de setNamespour ajouter des noms de variables au data.frame.

lmo
la source
6

Cette question est assez ancienne mais j'ajouterai la solution que j'ai trouvée la plus simple actuellement.

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
la source
C'est de loin le plus facile quand il s'agit de gérer les vecteurs df
Apricot
5

Depuis R version 3.4.0, vous pouvez utiliser à strcapture()partir du package utils (inclus avec les installations de base R), en liant la sortie sur les autres colonnes.

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
la source
4

Une autre approche si vous voulez continuer strsplit()est d'utiliser la unlist()commande. Voici une solution dans ce sens.

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
la source
4

base mais probablement lente:

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
la source
1

Voici une autre solution de base R. Nous pouvons utiliser read.tablemais comme il n'accepte qu'un separgument sur un octet et ici nous avons un séparateur sur plusieurs octets que nous pouvons utiliser gsubpour remplacer le séparateur sur plusieurs octets par n'importe quel séparateur sur un octet et l'utiliser comme separgument dansread.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

Dans ce cas, nous pouvons également le raccourcir en le remplaçant par un separgument par défaut afin de ne pas avoir à le mentionner explicitement

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