web-dev-qa-db-fra.com

Comment supprimer une ligne par référence dans data.table?

Ma question concerne l'affectation par référence à la copie dans data.table. Je veux savoir si on peut supprimer des lignes par référence, semblable à

DT[ , someCol := NULL]

Je veux savoir

DT[someRow := NULL, ]

J'imagine que cette fonction n'existe pas pour une bonne raison. Vous pouvez donc peut-être simplement indiquer une bonne alternative à l'approche de copie habituelle, comme ci-dessous. En particulier, aller avec mon favori de l'exemple (data.table),

DT = data.table(x = rep(c("a", "b", "c"), each = 3), y = c(1, 3, 6), v = 1:9)
#      x y v
# [1,] a 1 1
# [2,] a 3 2
# [3,] a 6 3
# [4,] b 1 4
# [5,] b 3 5
# [6,] b 6 6
# [7,] c 1 7
# [8,] c 3 8
# [9,] c 6 9

Dites que je veux supprimer la première ligne de ce data.table. Je sais que je peux le faire:

DT <- DT[-1, ]

mais souvent nous pouvons éviter cela, car nous copions l'objet (et cela nécessite environ 3 * N mémoire, si N object.size(DT), comme indiqué ici . Maintenant, j'ai trouvé set(DT, i, j, value). Je sais comment définir des valeurs spécifiques (comme ici: définissez toutes les valeurs des lignes 1 et 2 et des colonnes 2 et 3 sur zéro)

set(DT, 1:2, 2:3, 0) 
DT
#      x y v
# [1,] a 0 0
# [2,] a 0 0
# [3,] a 6 3
# [4,] b 1 4
# [5,] b 3 5
# [6,] b 6 6
# [7,] c 1 7
# [8,] c 3 8
# [9,] c 6 9

Mais comment puis-je effacer les deux premières lignes, par exemple? Faire

set(DT, 1:2, 1:3, NULL)

définit l'ensemble du DT sur NULL.

Mes connaissances en SQL sont très limitées, alors vous me dites: étant donné que data.table utilise la technologie SQL, existe-t-il un équivalent de la commande SQL?

DELETE FROM table_name
WHERE some_column=some_value

dans data.table?

140
Florian Oswald

Bonne question. data.table ne peut pas encore supprimer les lignes par référence.

data.table peut ajouter et supprimer des colonnes par référence car il sur-alloue le vecteur de pointeurs de colonne, comme vous le savez. Le plan consiste à faire quelque chose de similaire pour les lignes et à autoriser la rapidité insert et delete. Une suppression de ligne utiliserait memmove en C pour modifier les éléments (dans chaque colonne) après les lignes supprimées. La suppression d'une ligne au milieu de la table resterait tout à fait inefficace par rapport à une base de données de magasin de lignes telle que SQL, qui convient mieux à l'insertion et à la suppression rapides de lignes lorsque ces lignes se trouvent dans la table. Néanmoins, ce serait beaucoup plus rapide que de copier un nouvel objet volumineux sans les lignes supprimées.

D'autre part, comme les vecteurs de colonnes seraient sur-alloués, des lignes pourraient être insérées (et supprimées) à la fin , instantanément; par exemple, une série chronologique croissante.


C'est classé comme un problème: Supprimer les lignes par référence .

115
Matt Dowle

l’approche que j’ai choisie pour utiliser la mémoire de la même manière que la suppression sur place consiste à sous-définir une colonne à la fois et à la supprimer. pas aussi vite qu'une solution C memmove appropriée, mais l’utilisation de la mémoire est tout ce qui m’intéresse ici. quelque chose comme ça:

DT = data.table(col1 = 1:1e6)
cols = paste0('col', 2:100)
for (col in cols){ DT[, (col) := 1:1e6] }
keep.idxs = sample(1e6, 9e5, FALSE) # keep 90% of entries
DT.subset = data.table(col1 = DT[['col1']][keep.idxs]) # this is the subsetted table
for (col in cols){
  DT.subset[, (col) := DT[[col]][keep.idxs]]
  DT[, (col) := NULL] #delete
}
28
vc273

Voici une fonction fonctionnelle basée sur la réponse de @ vc273 et les réactions de @ Frank.

delete <- function(DT, del.idxs) {           # pls note 'del.idxs' vs. 'keep.idxs'
  keep.idxs <- setdiff(DT[, .I], del.idxs);  # select row indexes to keep
  cols = names(DT);
  DT.subset <- data.table(DT[[1]][keep.idxs]); # this is the subsetted table
  setnames(DT.subset, cols[1]);
  for (col in cols[2:length(cols)]) {
    DT.subset[, (col) := DT[[col]][keep.idxs]];
    DT[, (col) := NULL];  # delete
  }
   return(DT.subset);
}

Et exemple de son utilisation:

dat <- delete(dat,del.idxs)   ## Pls note 'del.idxs' instead of 'keep.idxs'

Où "dat" est un data.table. Suppression de 14k lignes de 1,4 million de lignes prend 0,25 seconde sur mon ordinateur portable.

> dim(dat)
[1] 1419393      25
> system.time(dat <- delete(dat,del.idxs))
   user  system elapsed 
   0.23    0.02    0.25 
> dim(dat)
[1] 1404715      25
> 

PS Depuis que je suis nouveau sur SO, je n'ai pas pu ajouter de commentaire au fil de @ vc273 :-(

5
Jarno P.

Le sujet intéresse toujours beaucoup de monde (moi compris).

Que dire de cela? J'ai utilisé assign pour remplacer le glovalenv et le code décrit précédemment. Il serait préférable de capturer l’environnement d’origine, mais au moins dans globalenv, la mémoire est efficace et elle agit comme un changement par réf.

delete <- function(DT, del.idxs) 
{ 
  varname = deparse(substitute(DT))

  keep.idxs <- setdiff(DT[, .I], del.idxs)
  cols = names(DT);
  DT.subset <- data.table(DT[[1]][keep.idxs])
  setnames(DT.subset, cols[1])

  for (col in cols[2:length(cols)]) 
  {
    DT.subset[, (col) := DT[[col]][keep.idxs]]
    DT[, (col) := NULL];  # delete
  }

  assign(varname, DT.subset, envir = globalenv())
  return(invisible())
}

DT = data.table(x = rep(c("a", "b", "c"), each = 3), y = c(1, 3, 6), v = 1:9)
delete(DT, 3)
4
JRR

À la place ou si vous essayez de définir la valeur NULL, essayez de définir la valeur NA (correspondant au type NA de la première colonne).

set(DT,1:2, 1:3 ,NA_character_)
4
42-

Voici quelques stratégies que j'ai utilisées. Je crois qu'une fonction .ROW peut être à venir. Aucune de ces approches ci-dessous ne sont rapides. Ce sont des stratégies un peu au-delà des sous-ensembles ou du filtrage. J'ai essayé de penser comme DBA juste essayer de nettoyer les données. Comme indiqué ci-dessus, vous pouvez sélectionner ou supprimer des lignes dans le fichier data.table:

data(iris)
iris <- data.table(iris)

iris[3] # Select row three

iris[-3] # Remove row three

You can also use .SD to select or remove rows:

iris[,.SD[3]] # Select row three

iris[,.SD[3:6],by=,.(Species)] # Select row 3 - 6 for each Species

iris[,.SD[-3]] # Remove row three

iris[,.SD[-3:-6],by=,.(Species)] # Remove row 3 - 6 for each Species

Remarque: .SD crée un sous-ensemble des données d'origine et vous permet de faire un peu de travail dans j ou dans data.table ultérieur. Voir https://stackoverflow.com/a/47406952/305675 . Ici, j'ai commandé mes iris par longueur de sépale, prenez au minimum un sépale spécifié, sélectionnez les trois premiers (par longueur de sépale) de toutes les espèces et renvoyez toutes les données d'accompagnement:

iris[order(-Sepal.Length)][Sepal.Length > 3,.SD[1:3],by=,.(Species)]

Les approches ci-dessus réorganisent de manière séquentielle une table de données lors de la suppression de lignes. Vous pouvez transposer un fichier data.table et supprimer ou remplacer les anciennes lignes qui sont maintenant des colonnes transposées. Lorsque vous utilisez ': = NULL' pour supprimer une ligne transposée, le nom de colonne suivant est également supprimé:

m_iris <- data.table(t(iris))[,V3:=NULL] # V3 column removed

d_iris <- data.table(t(iris))[,V3:=V2] # V3 column replaced with V2

Lorsque vous transposez le fichier data.frame dans un fichier data.table, vous souhaiterez peut-être renommer le fichier data.table d'origine et restaurer les attributs de classe en cas de suppression. L'application de ": = NULL" à un fichier data.table désormais transposé crée toutes les classes de caractères.

m_iris <- data.table(t(d_iris));
setnames(d_iris,names(iris))

d_iris <- data.table(t(m_iris));
setnames(m_iris,names(iris))

Vous voudrez peut-être simplement supprimer les lignes en double que vous pouvez faire avec ou sans clé:

d_iris[,Key:=paste0(Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species)]     

d_iris[!duplicated(Key),]

d_iris[!duplicated(paste0(Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species)),]  

Il est également possible d'ajouter un compteur incrémental avec ".I". Vous pouvez ensuite rechercher les clés ou les champs en double et les supprimer en supprimant l'enregistrement avec le compteur. Cela coûte cher en termes de calcul, mais présente certains avantages, car vous pouvez imprimer les lignes à supprimer.

d_iris[,I:=.I,] # add a counter field

d_iris[,Key:=paste0(Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species)]

for(i in d_iris[duplicated(Key),I]) {print(i)} # See lines with duplicated Key or Field

for(i in d_iris[duplicated(Key),I]) {d_iris <- d_iris[!I == i,]} # Remove lines with duplicated Key or any particular field.

Vous pouvez également simplement remplir une ligne avec des 0 ou des NA, puis utiliser une requête i pour les supprimer:

 X 
   x v foo
1: c 8   4
2: b 7   2

X[1] <- c(0)

X
   x v foo
1: 0 0   0
2: b 7   2

X[2] <- c(NA)
X
    x  v foo
1:  0  0   0
2: NA NA  NA

X <- X[x != 0,]
X <- X[!is.na(x),]
2
rferrisx