Preserve names when coercing vector from binary to

2019-04-18 17:13发布

问题:

In R, when you coerce a vector from binary to numeric, the names are stripped away.

There are a few possible solutions, which I've outlined before. It seems dangerous to rely on implicit conversion by adding 0 to all the values, and the sapply() adds an additional loop to my operations (which seems inefficient). Is there any other way to preserve the names when converting a vector using as.numeric?

# Set the seed
set.seed(1045)

# Create a small sample vector and give it names
example_vec <- sample(x = c(TRUE,FALSE),size = 10,replace = TRUE)
names(example_vec) <- sample(x = LETTERS,size = 10,replace = FALSE)

example_vec
#     Y     N     M     P     L     J     H     O     F     D 
# FALSE  TRUE FALSE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE 

as.numeric(x = example_vec)
#  [1] 0 1 0 0 1 1 1 1 1 1

example_vec + 0
# Y N M P L J H O F D 
# 0 1 0 0 1 1 1 1 1 1 

sapply(X = example_vec,FUN = as.numeric)
# Y N M P L J H O F D 
# 0 1 0 0 1 1 1 1 1 1

回答1:

Just to throw another option out there, since your input is a logical vector, you can use ifelse(). And one could argue this approach is more explicit and straightforward:

ifelse(example_vec,1L,0L);
## Y N M P L J H O F D
## 0 1 0 0 1 1 1 1 1 1

Benchmarking

library(microbenchmark);

ifelse. <- function(x) ifelse(x,1L,0L);
sapply. <- function(x) sapply(x,as.integer);
setstoragemode <- function(x) { storage.mode(x) <- 'integer'; x; };
setmode <- function(x) { mode(x) <- 'integer'; x; };
setclass <- function(x) { class(x) <- 'integer'; x; };
as.and.setnames <- function(x) setNames(as.integer(x),names(x));
plus <- function(x) +x;
addzero <- function(x) x+0L;

## small scale (OP's example input)
set.seed(1045L);
x <- sample(c(T,F),10L,T);
names(x) <- sample(LETTERS,10L);

ex <- ifelse.(x);
identical(ex,sapply.(x));
## [1] TRUE
identical(ex,setstoragemode(x));
## [1] TRUE
identical(ex,setmode(x));
## [1] TRUE
identical(ex,setclass(x));
## [1] TRUE
identical(ex,as.and.setnames(x));
## [1] TRUE
identical(ex,plus(x));
## [1] TRUE
identical(ex,addzero(x));
## [1] TRUE

microbenchmark(ifelse.(x),sapply.(x),setstoragemode(x),setmode(x),setclass(x),as.and.setnames(x),plus(x),addzero(x));
## Unit: nanoseconds
##                expr   min      lq     mean median      uq   max neval
##          ifelse.(x)  6843  8126.0  9627.13   8981  9837.0 21810   100
##          sapply.(x) 18817 20100.5 23234.93  21383 22666.5 71418   100
##   setstoragemode(x)   856  1283.0  1745.54   1284  1711.0 15396   100
##          setmode(x)  7270  8126.0  9862.36   8982 10264.0 32074   100
##         setclass(x)   429  1283.0  2138.97   1284  1712.0 32075   100
##  as.and.setnames(x)  1283  1711.0  1997.78   1712  2139.0  7271   100
##             plus(x)     0   428.0   492.39    428   428.5  9837   100
##          addzero(x)     0   428.0   539.39    428   856.0  2566   100

## large scale
set.seed(1L);
N <- 1e5L;
x <- sample(c(T,F),N,T);
names(x) <- make.unique(rep_len(LETTERS,N));

ex <- ifelse.(x);
identical(ex,sapply.(x));
## [1] TRUE
identical(ex,setstoragemode(x));
## [1] TRUE
identical(ex,setmode(x));
## [1] TRUE
identical(ex,setclass(x));
## [1] TRUE
identical(ex,as.and.setnames(x));
## [1] TRUE
identical(ex,plus(x));
## [1] TRUE
identical(ex,addzero(x));
## [1] TRUE

microbenchmark(ifelse.(x),sapply.(x),setstoragemode(x),setmode(x),setclass(x),as.and.setnames(x),plus(x),addzero(x));
## Unit: microseconds
##                expr       min          lq         mean      median         uq        max neval
##          ifelse.(x)  7633.598   7757.1900  16615.71251   7897.4600  29401.112  96503.642   100
##          sapply.(x) 86353.737 102576.0945 125547.32957 123909.1120 137900.406 264442.788   100
##   setstoragemode(x)    84.676     92.8015    343.46124     98.3605    113.543  23939.133   100
##          setmode(x)   124.020    155.0245    603.15744    167.2125    181.111  22395.736   100
##         setclass(x)    85.104     92.3740    328.25393    100.2850    118.460  21807.713   100
##  as.and.setnames(x)    70.991     78.2610    656.98177     82.3235     88.953  35710.697   100
##             plus(x)    40.200     42.9795     48.68026     44.9040     49.608     88.953   100
##          addzero(x)   181.326    186.4580    196.34882    189.6650    201.211    282.679   100

## very large scale
set.seed(1L);
N <- 1e7L;
x <- sample(c(T,F),N,T);
names(x) <- make.unique(rep_len(LETTERS,N));

ex <- ifelse.(x);
identical(ex,sapply.(x));
## [1] TRUE
identical(ex,setstoragemode(x));
## [1] TRUE
identical(ex,setmode(x));
## [1] TRUE
identical(ex,setclass(x));
## [1] TRUE
identical(ex,as.and.setnames(x));
## [1] TRUE
identical(ex,plus(x));
## [1] TRUE
identical(ex,addzero(x));
## [1] TRUE

microbenchmark(ifelse.(x),sapply.(x),setstoragemode(x),setmode(x),setclass(x),as.and.setnames(x),plus(x),addzero(x),times=5L);
## Unit: milliseconds
##                expr          min           lq         mean       median           uq         max neval
##          ifelse.(x)  1082.220903  1308.106967  3452.639836  1473.723533  6306.320235  7092.82754     5
##          sapply.(x) 16766.199371 17431.458634 18401.672635 18398.345499 18843.890150 20568.46952     5
##   setstoragemode(x)    13.298283    13.648103   173.574496    19.661753    24.736278   796.52806     5
##          setmode(x)    19.043796    19.878573    75.669779    19.969235    39.683589   279.77370     5
##         setclass(x)    14.025292    14.119804   259.627934    14.414457    26.838618  1228.74150     5
##  as.and.setnames(x)    12.889875    24.241484   178.243948    24.962934    25.103631   804.02182     5
##             plus(x)     7.577576     7.676364     9.047674     8.245142     8.253266    13.48602     5
##          addzero(x)    18.861615    18.960403    71.284716    26.622226    26.950662   265.02867     5

Looks like the unary plus takes the cake. (And my ifelse() idea kinda sucks.)



回答2:

One possibility is to use the mode<- replacement function to change the internal storage mode (type) of the object. Also, integers are more appropriate than doubles (i.e. numerics) for this case of logical coercion.

mode(example_vec) <- "integer"
example_vec
# Y N M P L J H O F D 
# 0 1 0 0 1 1 1 1 1 1 

From help(mode) -

mode(x) <- "newmode" changes the mode of object x to newmode. This is only supported if there is an appropriate as.newmode function, for example "logical", "integer", "double", "complex", "raw", "character", "list", "expression", "name", "symbol" and "function". Attributes are preserved.

The documentation also notes that storage.mode<- is a more efficient primitive version of mode<-. So the following could also be used.

storage.mode(example_vec) <- "integer" 

But as @joran pointed out in the comments, it looks like class<- also does the same thing.