Diviser la colonne de chaîne de cadre 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

j'ai trouvé quelque chose d'incroyablement complexe impliquant une certaine forme de apply qui a fonctionné, mais je l'ai depuis égaré. Il semble beaucoup trop compliqué pour être le meilleur moyen. Je peux utiliser strsplit comme ci-dessous, mais ensuite pas clair comment obtenir ce retour 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 tous les conseils. Je n'ai pas encore tout à fait les listes R.

170
demandé sur David Arenburg 2010-12-04 01:29:15

15 réponses

utiliser stringr::str_split_fixed

library(stringr)
str_split_fixed(before$type, "_and_", 2)
209
répondu hadley 2010-12-04 04:21:27

une autre option consiste à utiliser le nouveau paquet 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
123
répondu hadley 2014-06-11 16:50:59

5 ans plus tard ajoutant la solution obligatoire data.table 151970920"

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 la performance en ajoutant type.convert et fixed arguments (depuis "_and_" n'est pas vraiment un regex)

setDT(before)[, paste0("type", 1:2) := tstrsplit(type, "_and_", type.convert = TRUE, fixed = TRUE)]
47
répondu David Arenburg 2016-08-22 07:47:42

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 à combiner:

data.frame(before$attr, do.call(rbind, out))
42
répondu Aniko 2010-12-04 00:51:30

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

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
31
répondu 42- 2016-09-02 05:20:32

voici une doublure 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_'))
27
répondu Ramnath 2010-12-04 02:09:23

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
18
répondu A5C1D2H2I1M1N2O1R2T1 2017-07-12 12:34:58

un moyen facile 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() 's résultat est une matrice et a besoin de transposer et de renvoyer à une base de données. Ce sont donc de simples manipulations qui donnent le résultat que vous vouliez:

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 point, after 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
12
répondu Gavin Simpson 2010-12-03 23:36:58

voici un liner de base qui chevauche un certain nombre de solutions précédentes, mais renvoie une donnée.cadre 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 remettre les données dans une donnée.cadre. L'amélioration supplémentaire est l'utilisation de setNames pour ajouter des noms de variables aux données.cadre.

7
répondu lmo 2016-07-22 20:34:38

le sujet est presque épuisé, j'aimerais cependant offrir une solution à une version légèrement plus générale où vous ne connaissez pas le nombre de colonnes de sortie, a priori. 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 des colonnes de résultat avant la séparation, donc j'ai créé une fonction qui utilise stringr pour séparer une colonne, Étant donné le modèle et un préfixe de nom pour l'généré colonnes. J'espère que les codes 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)
}

nous pouvons 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
5
répondu Yannis P. 2017-11-01 17:26:04

une autre approche si vous voulez vous en tenir à strsplit() est d'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
répondu ashaw 2010-12-03 23:52:51

depuis la version 3.4.0 de R, Vous pouvez utiliser strcapture() du paquet utils (inclus avec la base R installe), liant la sortie sur la ou 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
4
répondu Rich Scriven 2017-08-28 19:21:12

cette question est assez ancienne mais je vais ajouter la solution que j'ai trouvé le 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
3
répondu Swifty McSwifterton 2017-09-28 20:14:42

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
3
répondu Joe 2018-02-17 03:44:05
tp <- c("a-c","d-e-f","g-h-i","m-n")

temp = strsplit(as.character(tp),'-')

x=c();
y=c();
z=c();

#tab=data.frame()
#tab= cbind(tab,c(x,y,z))

for(i in 1:length(temp) )
{
  l = length(temp[[i]]);

  if(l==2)
  {
     x=c(x,temp[[i]][1]);
     y=c(y,"NA")
     z=c(z,temp[[i]][2]);

    df= as.data.frame(cbind(x,y,z)) 

  }else
  {
    x=c(x,temp[[i]][1]);
    y=c(y,temp[[i]][2]);
    z=c(z,temp[[i]][3]);

    df= as.data.frame(cbind(x,y,z))
   }
}
-5
répondu Soumya Das 2017-05-26 18:08:56