web-dev-qa-db-fra.com

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

Je voudrais prendre des données de la forme

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() sur la colonne "type" d'en haut 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

Je suis arrivé à quelque chose d'incroyablement complexe impliquant une certaine forme de apply qui a fonctionné, mais je me suis égaré depuis. Cela semblait trop compliqué pour être le meilleur moyen. Je peux utiliser strsplit comme ci-dessous, mais je ne vois pas comment le récupérer dans deux colonnes du 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 tous les pointeurs. Je n'ai pas encore tout à fait grillé les listes R.

206
jkebinger

Utilisez stringr::str_split_fixed

library(stringr)
str_split_fixed(before$type, "_and_", 2)
249
hadley

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
152
hadley

5 ans plus tard, ajout de la solution obligatoire data.table

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 nous assurer que les colonnes résultantes auront les types corrects et améliorer les performances en ajoutant type.convert et fixed arguments (puisque "_and_" n'est pas vraiment un regex)

setDT(before)[, paste0("type", 1:2) := tstrsplit(type, "_and_", type.convert = TRUE, fixed = TRUE)]
53
David Arenburg

Encore une autre approche: utilisez rbind sur 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))
49
Aniko

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

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
35
42-

voici une ligne qui va dans le même sens que la solution d’Aniko, mais en utilisant le paquet stringr de hadley:

do.call(rbind, str_split(before$type, '_and_'))
28
Ramnath

Pour ajouter aux options, vous pouvez également utiliser ma fonction splitstackshape::cSplit 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
19

Une méthode simple consiste à 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

Le résultat de sapply() est une matrice et doit être transposé et retranscrit dans une trame de données. Ce sont ensuite 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, after est ce que tu voulais

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

Voici une ligne de base R qui chevauche un certain nombre de solutions précédentes, mais renvoie un nom de données (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 strsplit pour décomposer la variable et data.frame avec do.call/rbind pour replacer les données dans un nom data.frame. L’amélioration incrémentale supplémentaire consiste à utiliser setNames pour ajouter des noms de variables à data.frame.

7
lmo

Le sujet est presque épuisé. J'aimerais cependant proposer une solution à une version légèrement plus générale dans laquelle vous ne connaissez pas le nombre de colonnes de sortie, a priori. Donc, 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() car nous ne connaissons pas le nombre de colonnes de résultats avant la scission. J'ai donc créé une fonction qui utilise stringr pour scinder une colonne, en fonction du 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 alors utiliser split_into_multiple dans un tuyau 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 ensuite, nous pouvons utiliser gather pour 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
6
Yannis P.

Depuis la version 3.4.0 de R, vous pouvez utiliser strcapture() à partir du paquet tils (inclus avec les installations de base R), en liant la sortie aux 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
5
Rich Scriven

Cette question est assez ancienne mais j'ajouterai la solution que j'ai trouvée la plus simple à l'heure actuelle.

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
5
Swifty McSwifterton

Une autre approche si vous voulez rester avec strsplit() consiste à utiliser la commande unlist(). 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")
4
ashaw

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
4
Joe