R data.table weird value/reference semantics

后端 未结 3 708
野性不改
野性不改 2021-02-19 04:35

(This is a follow up question to this.)

Check this toy code:

> x <- data.frame(a = 1:2)
> foo <- function(z) { setDT(z) ; z[, b:=3:4] ; z } 
&g         


        
3条回答
  •  暖寄归人
    2021-02-19 05:22

    A supplement to GKi's answer:

    setalloccol's location is indeed the direct culprit: it performs a shallow copy (i.e., generates a new vector of pointers to the existing data columns) and in addition allocates extra 1024 (by default) slots for additional columns. If setting the class to data.frame is performed after this shallow copy (either by class(z)<- or by setattr) it is applied to this new vector and not the original argument.

    However.

    Even after using a fixed version of setDT (with setattr called after setalloccol), it seems there is no way to get consistent behaviour. Some operations apply to the caller copy, and some don't.

    df <- data.frame(a=1:2, b=3:4)
    
    foo1 <- function(z) { 
      setDT.fixed(z)
      z[, b:=5]   # will apply to the caller copy
      data.table::setDF(z)
    }
    
    foo1(df)
    #    a b
    # 1: 1 5
    # 2: 2 5
    class(df)
    # [1] "data.frame"
    df
    #   a b
    # 1 1 5
    # 2 2 5
    
    foo2 <- function(z) { 
      setDT.fixed(z)
      z[, c:=5]   # will NOT apply to the caller copy
      data.table::setDF(z)
    }
    foo2(df)
    #    a b c
    # 1: 1 3 5
    # 2: 2 4 5
    # Warning message:
    # In `[.data.table`(z, , `:=`(c, 5)) :
    #  Invalid .internal.selfref detected and fixed by taking a (shallow) copy of the data.table so that := can add this new column by reference. At an earlier point, this data.table has been copied by R (or was created manually using structure() or similar). Avoid names<- and attr<- which in R currently (and oddly) may copy the whole data.table. Use set* syntax instead to avoid copying: ?set, ?setnames and ?setattr. If this message doesn't help, please report your use case to the data.table issue tracker so the root cause can be fixed or this message improved.
    class(df)
    # [1] "data.table" "data.frame"
    df
    #    a b
    # 1: 1 3
    # 2: 2 4
    

    (Using the j argument, e.g., z[!is.na(a), b:=6] gives an extra dimension of weirdness which I won't go into here).

    Bottom line, the data.table package took on the brave task of punching a hole in R's all-value semantics. It was pretty successful until setDT came along (BTW, in response to a SO question here). Using setDT within a function on an argument will probably never have consistent semantics and is almost guaranteed to get you nasty surprises.

提交回复
热议问题