web-dev-qa-db-fra.com

Comment déposer des colonnes par nom dans un cadre de données

J'ai un grand ensemble de données et j'aimerais lire des colonnes spécifiques ou supprimer toutes les autres.

data <- read.dta("file.dta")

Je sélectionne les colonnes qui ne m'intéressent pas:

var.out <- names(data)[!names(data) %in% c("iden", "name", "x_serv", "m_serv")]

et que j'aimerais faire quelque chose comme:

for(i in 1:length(var.out)) {
   paste("data$", var.out[i], sep="") <- NULL
}

pour supprimer toutes les colonnes indésirables. Est-ce la solution optimale?

276
leroux

Vous devez utiliser l'indexation ou la fonction subset. Par exemple :

R> df <- data.frame(x=1:5, y=2:6, z=3:7, u=4:8)
R> df
  x y z u
1 1 2 3 4
2 2 3 4 5
3 3 4 5 6
4 4 5 6 7
5 5 6 7 8

Ensuite, vous pouvez utiliser la fonction which et l'opérateur - dans l'indexation des colonnes:

R> df[ , -which(names(df) %in% c("z","u"))]
  x y
1 1 2
2 2 3
3 3 4
4 4 5
5 5 6

Ou bien, beaucoup plus simple, utilisez l’argument select de la fonction subset: vous pouvez ensuite utiliser l’opérateur - directement sur un vecteur de noms de colonnes, et vous pouvez même omettre les guillemets des noms !

R> subset(df, select=-c(z,u))
  x y
1 1 2
2 2 3
3 3 4
4 4 5
5 5 6

Notez que vous pouvez également sélectionner les colonnes souhaitées au lieu de supprimer les autres:

R> df[ , c("x","y")]
  x y
1 1 2
2 2 3
3 3 4
4 4 5
5 5 6

R> subset(df, select=c(x,y))
  x y
1 1 2
2 2 3
3 3 4
4 4 5
5 5 6
343
juba

N'utilisez pas -which() pour cela, c'est extrêmement dangereux. Considérer:

dat <- data.frame(x=1:5, y=2:6, z=3:7, u=4:8)
dat[ , -which(names(dat) %in% c("z","u"))] ## works as expected
dat[ , -which(names(dat) %in% c("foo","bar"))] ## deletes all columns! Probably not what you wanted...

À la place, utilisez le sous-ensemble ou la fonction !:

dat[ , !names(dat) %in% c("z","u")] ## works as expected
dat[ , !names(dat) %in% c("foo","bar")] ## returns the un-altered data.frame. Probably what you want

J'ai appris cela d'une expérience douloureuse. Ne pas abuser de which()!

110
Ista

First , vous pouvez utiliser l'indexation directe (avec des vecteurs booléens) au lieu de ré-accéder aux noms de colonnes si vous utilisez le même trame de données; comme le souligne Ista, il sera plus sûr d’écrire et d’exécuter. Donc, ce dont vous aurez seulement besoin est:

var.out.bool <- !names(data) %in% c("iden", "name", "x_serv", "m_serv")

et ensuite, réaffectez simplement les données:

data <- data[,var.out.bool] # or...
data <- data[,var.out.bool, drop = FALSE] # You will need this option to avoid the conversion to an atomic vector if there is only one column left

Second , plus rapide à écrire, vous pouvez affecter directement NULL aux colonnes que vous souhaitez supprimer:

data[c("iden", "name", "x_serv", "m_serv")] <- list(NULL) # You need list() to respect the target structure.

Enfin , vous pouvez utiliser subset (), mais il ne peut pas vraiment être utilisé dans le code (même le fichier d'aide met en garde sur il). Plus précisément, un problème pour moi est que si vous souhaitez utiliser directement la fonction de suppression de susbset (), vous devez écrire sans guillemets l'expression correspondant aux noms de colonne:

subset( data, select = -c("iden", "name", "x_serv", "m_serv") ) # WILL NOT WORK
subset( data, select = -c(iden, name, x_serv, m_serv) ) # WILL

En tant que bonus , voici un petit repère des différentes options, qui montre clairement que le sous-ensemble est le plus lent, et que le Premièrement, la méthode de réaffectation est la plus rapide:

                                        re_assign(dtest, drop_vec)  46.719  52.5655  54.6460  59.0400  1347.331
                                      null_assign(dtest, drop_vec)  74.593  83.0585  86.2025  94.0035  1476.150
               subset(dtest, select = !names(dtest) %in% drop_vec) 106.280 115.4810 120.3435 131.4665 65133.780
 subset(dtest, select = names(dtest)[!names(dtest) %in% drop_vec]) 108.611 119.4830 124.0865 135.4270  1599.577
                                  subset(dtest, select = -c(x, y)) 102.026 111.2680 115.7035 126.2320  1484.174

Microbench graph

Code est ci-dessous:

dtest <- data.frame(x=1:5, y=2:6, z = 3:7)
drop_vec <- c("x", "y")

null_assign <- function(df, names) {
  df[names] <- list(NULL)
  df
}

re_assign <- function(df, drop) {
  df <- df [, ! names(df) %in% drop, drop = FALSE]
  df
}

res <- microbenchmark(
  re_assign(dtest,drop_vec),
  null_assign(dtest,drop_vec),
  subset(dtest, select = ! names(dtest) %in% drop_vec),
  subset(dtest, select = names(dtest)[! names(dtest) %in% drop_vec]),
  subset(dtest, select = -c(x, y) ),
times=5000)

plt <- ggplot2::qplot(y=time, data=res[res$time < 1000000,], colour=expr)
plt <- plt + ggplot2::scale_y_log10() + 
  ggplot2::labs(colour = "expression") + 
  ggplot2::scale_color_discrete(labels = c("re_assign", "null_assign", "subset_bool", "subset_names", "subset_drop")) +
  ggplot2::theme_bw(base_size=16)
print(plt)
42
Antoine Lizée

Vous pouvez également essayer le package dplyr:

R> df <- data.frame(x=1:5, y=2:6, z=3:7, u=4:8)
R> df
  x y z u
1 1 2 3 4
2 2 3 4 5
3 3 4 5 6
4 4 5 6 7
5 5 6 7 8
R> library(dplyr)
R> dplyr::select(df2, -c(x, y))  # remove columns x and y
  z u
1 3 4
2 4 5
3 5 6
4 6 7
5 7 8
23
Megatron

Voici une solution rapide pour cela. Disons que vous avez un bloc de données X avec trois colonnes A, B et C:

> X<-data.frame(A=c(1,2),B=c(3,4),C=c(5,6))
> X
  A B C
1 1 3 5
2 2 4 6

Si je veux supprimer une colonne, dites B, utilisez simplement grep sur les noms de colonnes pour obtenir l'index de colonne, que vous pouvez ensuite utiliser pour omettre la colonne.

> X<-X[,-grep("B",colnames(X))]

Votre nouveau bloc de données X ressemblerait à ce qui suit (cette fois sans la colonne B):

> X
  A C
1 1 5
2 2 6

La beauté de grep réside dans le fait que vous pouvez spécifier plusieurs colonnes correspondant à l'expression régulière. Si j'avais X avec cinq colonnes (A, B, C, D, E):

> X<-data.frame(A=c(1,2),B=c(3,4),C=c(5,6),D=c(7,8),E=c(9,10))
> X
  A B C D  E
1 1 3 5 7  9
2 2 4 6 8 10

Sortez les colonnes B et D:

> X<-X[,-grep("B|D",colnames(X))]
> X
  A C  E
1 1 5  9
2 2 6 10

EDIT: Considérant la suggestion de grepl de Matthew Lundberg dans les commentaires ci-dessous:

> X<-data.frame(A=c(1,2),B=c(3,4),C=c(5,6),D=c(7,8),E=c(9,10))
> X
  A B C D  E
1 1 3 5 7  9
2 2 4 6 8 10
> X<-X[,!grepl("B|D",colnames(X))]
> X
  A C  E
1 1 5  9
2 2 6 10

Si j'essaie de supprimer une colonne inexistante, rien ne devrait arriver:

> X<-X[,!grepl("G",colnames(X))]
> X
  A C  E
1 1 5  9
2 2 6 10
8
Joben R. Ilagan

J'ai essayé de supprimer une colonne en utilisant le package data.table et j'ai obtenu un résultat inattendu. Je pense que ce qui suit pourrait valoir la peine d’être publié. Juste un petit avertissement.

[Edité par Matthew ...]

DF = read.table(text = "
     fruit state grade y1980 y1990 y2000
     apples Ohio   aa    500   100   55
     apples Ohio   bb      0     0   44
     apples Ohio   cc    700     0   33
     apples Ohio   dd    300    50   66
", sep = "", header = TRUE, stringsAsFactors = FALSE)

DF[ , !names(DF) %in% c("grade")]   # all columns other than 'grade'
   fruit state y1980 y1990 y2000
1 apples  Ohio   500   100    55
2 apples  Ohio     0     0    44
3 apples  Ohio   700     0    33
4 apples  Ohio   300    50    66

library('data.table')
DT = as.data.table(DF)

DT[ , !names(dat4) %in% c("grade")]    # not expected !! not the same as DF !!
[1]  TRUE  TRUE FALSE  TRUE  TRUE  TRUE

DT[ , !names(DT) %in% c("grade"), with=FALSE]    # that's better
    fruit state y1980 y1990 y2000
1: apples  Ohio   500   100    55
2: apples  Ohio     0     0    44
3: apples  Ohio   700     0    33
4: apples  Ohio   300    50    66

Fondamentalement, la syntaxe pour data.table n'est PAS exactement la même que data.frame. Il y a en fait beaucoup de différences, voir FAQ 1.1 et FAQ 2.17. Tu étais prévenu!

5
Mark Miller

J'ai changé le code en:

# read data
dat<-read.dta("file.dta")

# vars to delete
var.in<-c("iden", "name", "x_serv", "m_serv")

# what I'm keeping
var.out<-setdiff(names(dat),var.in)

# keep only the ones I want       
dat <- dat[var.out]

Quoi qu'il en soit, la réponse de Juba est la meilleure solution à mon problème!

1
leroux
df2 <- df[!names(df) %in% c("c1", "c2")]
1
Marvin W

Voici une autre solution qui peut être utile aux autres. Le code ci-dessous sélectionne un petit nombre de lignes et de colonnes dans un grand ensemble de données. Les colonnes sont sélectionnées comme dans l'une des réponses de juba, sauf que j'utilise une fonction coller pour sélectionner un ensemble de colonnes avec des noms numérotés de manière séquentielle:

df = read.table(text = "

state county city  region  mmatrix  X1 X2 X3    A1     A2     A3      B1     B2     B3      C1      C2      C3

  1      1     1      1     111010   1  0  0     2     20    200       4      8     12      NA      NA      NA
  1      2     1      1     111010   1  0  0     4     NA    400       5      9     NA      NA      NA      NA
  1      1     2      1     111010   1  0  0     6     60     NA      NA     10     14      NA      NA      NA
  1      2     2      1     111010   1  0  0    NA     80    800       7     11     15      NA      NA      NA

  1      1     3      2     111010   0  1  0     1      2      1       2      2      2      10      20      30
  1      2     3      2     111010   0  1  0     2     NA      1       2      2     NA      40      50      NA
  1      1     4      2     111010   0  1  0     1      1     NA      NA      2      2      70      80      90
  1      2     4      2     111010   0  1  0    NA      2      1       2      2     10     100     110     120

  1      1     1      3     010010   0  0  1    10     20     10     200    200    200       1       2       3
  1      2     1      3     001000   0  0  1    20     NA     10     200    200    200       4       5       9
  1      1     2      3     101000   0  0  1    10     10     NA     200    200    200       7       8      NA
  1      2     2      3     011010   0  0  1    NA     20     10     200    200    200      10      11      12

", sep = "", header = TRUE, stringsAsFactors = FALSE)
df

df2 <- df[df$region == 2, names(df) %in% c(paste("C", seq_along(1:3), sep=''))]
df2

#    C1  C2  C3
# 5  10  20  30
# 6  40  50  NA
# 7  70  80  90
# 8 100 110 120
1
Mark Miller