2017-07-11 2 views
0

EDIT: possible au sujet de doublesetnames data.table modification objet dans un environnement appelant

Le proposed link aide à comprendre où la question vient (je l'avais posté déjà comme le 1er commentaire). Cependant, ma question est de savoir comment résoudre le problème spécifique de l'appel de setnames dans une fonction, pas simplement comprendre ce qui se passe, il n'est pas directement abordé là. En utilisant copy pourrait être une option, il pourrait y avoir d'autres.

La question avait 2 downvotes sans commentaire jusqu'à présent, s'il vous plaît parler si je peux l'améliorer.


data.tables::setnames modifie des valeurs par référence, dans le cas ci-dessous, il semble conduire à un comportement inattendu (inattendu pour moi au moins).

J'ai trouvé une manière laide de le gérer, mais il y a peut-être une meilleure façon plus systématique d'y aller, alors j'aimerais vos suggestions.

Le comportement étrange

df1 <- data.frame(a=1,b="x") 
f1 <- function(df2){ 
    setnames(df2,"b","c") 
    df2 
} 
f1(df1) 
# a c 
# 1 1 x 
df1 
# a c 
# 1 1 x 

DF1 a été modifié

si je fais une copie?

df1 <- data.frame(a=1,b="x") 
f1 <- function(df2){ 
df3 <- df2 
setnames(df3,"b","c") 
df3 
} 
f1(df1) 
# a c 
# 1 1 x 
df1 
# a c 
# 1 1 x 

Nope

si je fais une copie et "faire semblant de changer", mais ne pas

df1 <- data.frame(a=1,b="x") 
f1 <- function(df2){ 
    df3 <- subset(df2) # note: it doesn't work with `identity`, EDIT: we can also use `data.table::copy` 
    setnames(df3,"b","c") 
    df3 
} 
f1(df1) 
# a c 
# 1 1 x 
df1 
# a b 
# 1 1 x 

Il fonctionne


Comment devrait J'y vais? Est-ce un bug?


EDIT: J'ai découvert data.table a une fonction copy qui est plus général et certainement plus efficace que mon utilisation de subset

+0

Ceci est lié: https://stackoverflow.com/questions/15913417/why-does-data-table-update-namesdt-by-reference-even-if-i-assign-to-another- v –

+1

Je pense que 'copy' est probablement la meilleure solution, mais peut-être que quelqu'un d'autre peut peser. Le' df2' pointe vers le même objet que 'df1' alors quand' setnames' modifie 'df2' c'est vraiment aussi 'df1' (parce que le point de' setnames' est de ne pas faire une copie de l'objet que vous modifiez). Essayez 'library (pryr); adresse (df1); f_address <- fonction (df2) print (adresse (df2)); f_address (df1) ' –

+1

Plus de code de test si vous êtes intéressé -' x <-1; y <- x adresse (x); adresse (y) '. L'utilisation de 'setattr (x," test ", TRUE)' à la fois 'x' et' y' sera changé car 'setattr' est spécifiquement conçu pour ne pas faire de copie. Cependant, en utilisant une approche de base R avec quelque chose comme: 'attributes (y) <- list (" Attr2 "= FALSE)' nous voyons que 'y' et' x' diffèrent maintenant et vérifiant les 'adresse's ils pointent vers différents objets. –

Répondre

2

Je pense que c'est le comportement attendu depuis df2 points au même objet comme df1. Vous pouvez le voir avec:

library(pryr) 
library(data.table) 

df1 <- data.frame(a=1,b="x") 
address(df1) 
#[1] "0000000002892318" (will be different for others) 

f_address<-function(df2) print(address(df2)) 
f_address(df1) 
#[1] "0000000002892318" 

Depuis setnames modifie les entrées de référence, quand il est en train de changer df2 il est en train de changer l'objet que les deux points df1 et df2 à.

Pour changer cela, vous pouvez créer votre propre fonction explicitement des copies df1 puis le modifie:

setnames_copy <- function(x, old, new){ 
    y <- copy(x) 
    setnames(y, old, new) 
} 

f2 <- function(df2){ 
    setnames_copy(df2, "b", "c") 
} 
df3 <- f2(df1) 
df3 
# a c 
#1 1 x 

df1 
# a b 
#1 1 x 

Comme vous pouvez le voir, df1 est laissé non modifiée.

+0

J'ai construit sur votre proposition d'écrire une fonction hacky qui se comporte plus comme setnames, voir ma réponse –

+0

@Moody_Mudskipper cool! Approche intéressante - fyi, pas mon downvote –

0

Inspiré par la solution de @ mike-h, voici une fonction qui se comporte comme des noms d'ensembles, sauf qu'elle crée une copie si vous utilisez des setsnames en dehors de l'environnement où votre entrée a été définie.

library(pryr) 
setnames2 <- function(`$$`,old,new,verbose = FALSE){ 
    var_name <- as.character(substitute(`$$`)) 
    calling_env_objects <- sapply(ls(envir=parent.frame(n=2)),. %>% get(envir=parent.frame(n=2)) %>% address) 
    copied_object <- calling_env_objects[calling_env_objects == address(`$$`)] 
    if(length(copied_object) ==0){   # if the address isn't used in the calling environment's calling environment 
    if(verbose){cat("use regular setnames\n")} 
    setnames(`$$`,old,new) 
    } else if (length(copied_object) ==1){ # if the address is already in use in the calling environment's calling environment 
    if(verbose){cat("create copy then use setnames on it and assign to relevant environment\n")} 
    y <- copy(`$$`);setnames(y, old, new);assign(var_name,y,envir=parent.frame()) 
    } else {print("you shouldn't go around naming objects `$$`...")} 
} 

df1 <- data.frame(a=1,b="x") 
f3 <- function(df2){ 
    setnames2(df2,"b","c",verbose=TRUE) 
    print(df2) 
} 
f3(df1) 
# create copy then use setnames on it and assign to relevant environment 
# a c 
# 1 1 x 
df1 
# a b 
# 1 1 x  

f4 <- function(df2){ 
    df2 <- copy(df2) 
    setnames2(df2,"b","c",verbose=TRUE) 
    print(df2) 
} 
f4(df1) 
# use regular setnames 
# a c 
# 1 1 x 
df1 
# a b 
# 1 1 x