为啥 `as` 方法会删除向量名称,有没有办法解决它?

Posted

技术标签:

【中文标题】为啥 `as` 方法会删除向量名称,有没有办法解决它?【英文标题】:Why do `as` methods remove vector names, and is there a way around it?为什么 `as` 方法会删除向量名称,有没有办法解决它? 【发布时间】:2016-04-11 09:39:05 【问题描述】:

基本上,我试图保留一个名为 dates 的向量,该向量在我的分析中经常出现,比如 2016 年新年和 2015 年 7 月 4 日。我希望能够从中提取按名称而不是索引来表示稳健性,例如,dates["nyd"] 表示新年,dates["ind"] 表示 7 月 4 日。

我认为这很简单:

dates <- as.Date(c(ind = "2015-07-04", nyd = "2016-01-01"))

但是as.Date 已经去掉了名字:

dates
# [1] "2015-07-04" "2016-01-01"

不是Date 向量不能被命名(这很奇怪,因为它们基本上是专门解释的integers):

setNames(dates, c("ind", "nyd"))
#          ind          nyd 
# "2015-07-04" "2016-01-01" 

不幸的是,没有办法直接声明Date 向量(据我所知?),尤其是在不知道日期的基础整数值的情况下。

探索一下,这似乎是as* 函数类的标准做法:

as.integer(c(a = "123", b = "436"))
# [1] 123 436

as(c(a = 1, b = 2), "character")
# [1] "1" "2"

出现这种情况有什么原因吗? ?as 或我见过的任何其他帮助页面中都没有提到姓名丢失。

更一般地说,有没有办法(使用 as* 以外的其他方法)来确保对象的名称不会在转换中丢失?

当然,一种方法是编写像 as.Date.named 这样的自定义函数,或者创建一个带有关联方法的自定义类 as.named,但如果没有这样的东西,我会感到惊讶,因为看起来这样应该是很常见的操作了。

以防万一,我在 3.2.2 上。

【问题讨论】:

@thelatemail 我敢说这可能是 gasp 一个错误!? @MichaelChirico 实际上POSIXlt2Date 忽略了其代码中的“名称”,但我想我们可以预期as.DatePOSIXltPOSIXct 方法的行为相似;更令人惊讶的是看到as.Date(c(a = as.POSIXct(Sys.time())), tz = "UTC") VS as.Date(c(a = as.POSIXct(Sys.time())), tz = "GMT")。 ... ases... 都转换(除非我误解了您的第二条评论)因为 c(a = 1, b = 2) 是“数字”; as(, "character") 最终调用 as.character 这就是为什么“名称”丢失但我仍然认为我们可以期待相同的行为..? 我认为as.Date.POSIXlt 不应该删除这些名称。在这方面,所有as.Date 方法的行为都应该相同。你应该报告这个(可能值得一封措辞谨慎的电子邮件给 r-devel)。 @MichaelChirico 好吧,as.Date.character 在内部调用 as.Date.POSIXltas.Date.numeric 保留名称。 如果目标是通过names 访问日期,将向量转换为list 会更好吗? x &lt;- list(ind = "2015-07-04", nyd = "2016-01-01")y &lt;- lapply(x, as.Date)sapply(y,class)y["ind"] 【参考方案1】:

这不是问题的完整答案,但作为一种解决问题的方法,没有人提到 mode 函数。

vec <- c(a = "1", b = "2")
mode(vec) <- "integer"
vec
# returns:
# a b 
# 1 2 

我不确定你如何将它应用于日期:

vec <- c(a = "2010-01-01")
mode(vec) <- "POSIXlt"

给出了一些东西,但看起来不太对。


你也可以使用

sapply(vec, as.whatever)

这将保留名称。但是,我认为这会变慢,因为您失去了矢量化函数的优势。


第三,还有:

structure(as.whatever(vec), names = names(vec))

【讨论】:

因为使用as 方法,您正在更改对象的类型(例如,将字符更改为数字)。 mode 允许您在不丢失名称的情况下做到这一点,因此构成了问题中所要求的“绕过它”。简而言之,它给出了预期的结果,尽管不可否认,给定的带有日期的示例需要更多的工作。 这很有趣,因为理论上 mode&lt;-as 方法的包装器,它首先保存对象的属性,然后将它们返回给新转换的对象,它应该可以完美地工作。但在这种情况下,不知何故,attributes(new_Date_vector) &lt;- attributes(old_character_vector) 操作将新的 Date 向量强制转换为整数向量! (sapply 也会发生同样的事情)。 我必须承认我不熟悉 R 中的 POSIX 系统。对于我自己,我使用一个函数将日、月和年转换为整数(自 1899 年 12 月 30 日以来的天数),这是一致的使用(但优于)MS Excel。但是,这些解决方案是解决类型转换保留名称问题的一般方法。 刚刚注意到grep 完全符合structure(as.character(x), names = names(x)) 方法【参考方案2】:

确实,不同的as.Date 方法存在差异,这就是原因(或者更确切地说是“如何”):

首先,你的例子:

> as.Date(c(ind = "2015-07-04", nyd = "2016-01-01"))
[1] "2015-07-04" "2016-01-01"

这里我们使用方法as.Date.character:

> as.Date.character
function (x, format = "", ...) 

    charToDate <- function(x) 
        xx <- x[1L]
        if (is.na(xx)) 
            j <- 1L
            while (is.na(xx) && (j <- j + 1L) <= length(x)) xx <- x[j]
            if (is.na(xx)) 
                f <- "%Y-%m-%d"
        
        if (is.na(xx) || !is.na(strptime(xx, f <- "%Y-%m-%d", 
            tz = "GMT")) || !is.na(strptime(xx, f <- "%Y/%m/%d", 
            tz = "GMT"))) 
            return(strptime(x, f))
        stop("character string is not in a standard unambiguous format")
    
    res <- if (missing(format)) 
        charToDate(x)
    else strptime(x, format, tz = "GMT")
    as.Date(res)

<bytecode: 0x19d3dff8>
<environment: namespace:base>

无论是否给出格式,您的向量都会传递给strptime,后者将其转换为类POSIXlt,然后再次传递给as.Date,但这次使用方法as.Date.POSIXlt,即:

> as.Date.POSIXlt
function (x, ...) 
.Internal(POSIXlt2Date(x))
<bytecode: 0x19d2df50>
<environment: namespace:base>

意味着最终用于转换为 Date 类的函数是 POSIXlt2Date 调用的 C 函数(快速查看文件 names.c 表明该函数是文件 datetime.c 中的 do_POSIXlt2D)。供参考,这里是:

SEXP attribute_hidden do_POSIXlt2D(SEXP call, SEXP op, SEXP args, SEXP env)

    SEXP x, ans, klass;
    R_xlen_t n = 0, nlen[9];
    stm tm;

    checkArity(op, args);
    PROTECT(x = duplicate(CAR(args)));
    if(!isVectorList(x) || LENGTH(x) < 9)
    error(_("invalid '%s' argument"), "x");

    for(int i = 3; i < 6; i++)
    if((nlen[i] = XLENGTH(VECTOR_ELT(x, i))) > n) n = nlen[i];
    if((nlen[8] = XLENGTH(VECTOR_ELT(x, 8))) > n) n = nlen[8];
    if(n > 0) 
    for(int i = 3; i < 6; i++)
        if(nlen[i] == 0)
        error(_("zero-length component in non-empty \"POSIXlt\" structure"));
    if(nlen[8] == 0)
        error(_("zero-length component in non-empty \"POSIXlt\" structure"));
    
    /* coerce relevant fields to integer */
    for(int i = 3; i < 6; i++)
    SET_VECTOR_ELT(x, i, coerceVector(VECTOR_ELT(x, i), INTSXP));

    PROTECT(ans = allocVector(REALSXP, n));
    for(R_xlen_t i = 0; i < n; i++) 
    tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
    tm.tm_mday  = INTEGER(VECTOR_ELT(x, 3))[i%nlen[3]];
    tm.tm_mon   = INTEGER(VECTOR_ELT(x, 4))[i%nlen[4]];
    tm.tm_year  = INTEGER(VECTOR_ELT(x, 5))[i%nlen[5]];
    /* mktime ignores tm.tm_wday and tm.tm_yday */
    tm.tm_isdst = 0;
    if(tm.tm_mday == NA_INTEGER || tm.tm_mon == NA_INTEGER ||
       tm.tm_year == NA_INTEGER || validate_tm(&tm) < 0)
        REAL(ans)[i] = NA_REAL;
    else 
        /* -1 must be error as seconds were zeroed */
        double tmp = mktime00(&tm);
        REAL(ans)[i] = (tmp == -1) ? NA_REAL : tmp/86400;
    
    

    PROTECT(klass = mkString("Date"));
    classgets(ans, klass);
    UNPROTECT(3);
    return ans;

不幸的是,我对 C 的理解太有限,无法知道为什么这里会丢失属性。我的猜测是它发生在 coerceVector 操作期间或 POSIXlt 列表的每个元素被单独强制转换为整数时(如果这是第 1268-70 行发生的情况)。

但是让我们看看另一个as.Date 方法,从主要的罪犯as.Date.POSIXct 开始:

> as.Date.POSIXct
function (x, tz = "UTC", ...) 

    if (tz == "UTC") 
        z <- floor(unclass(x)/86400)
        attr(z, "tzone") <- NULL
        structure(z, class = "Date")
    
    else as.Date(as.POSIXlt(x, tz = tz))

<bytecode: 0x19c268bc>
<environment: namespace:base>

有了这个,如果没有给出时区,或者时区是“UTC”,该函数只是操纵POSIXct列表来提取可以解析为日期对象的数据,因此不会丢失属性,但是如果给定了任何其他时区,则它会被转换为POSIXlt 对象,因此会进一步传递给相同的POSIXlt2Date 内部,最终会失去其属性!确实:

> as.Date(c(a = as.POSIXct("2016-01-01")), tz="UTC")
           a 
"2015-12-31" 

> as.Date(c(a = as.POSIXct("2016-01-01")), tz="CET")
[1] "2016-01-01"

最后,正如@Roland 提到的,as.Date.numeric 确实保留了这些属性:

> as.Date.numeric
function (x, origin, ...) 

    if (missing(origin)) 
        stop("'origin' must be supplied")
    as.Date(origin, ...) + x

<bytecode: 0x568943d4>
<environment: namespace:base>

origin通过as.Date.character转换为Date,然后加上numeric的向量,因此保留了属性:

> c(a=1) + 2
a 
3 

自然而然:

> c(a=16814) + as.Date("1970-01-01")
           a 
"2016-01-14"

在解决此差异之前,我认为,您必须保留属性的唯一解决方案是首先转换为 POSIXct(但要注意时区问题)或数字,或者复制原始属性向量:

> before <- c(ind = "2015-07-04", nyd = "2016-01-01")
> after <- as.Date(before)
> names(after) <- names(before)
> after
         ind          nyd 
"2015-07-04" "2016-01-01" 

【讨论】:

“POSIXlt”对象的“名称”设置在其“年份”组件上(参见names&lt;-.POSIXlt)。当“POSIXlt”被传递给POSIXlt2Date 时,“年份”组件的“名称”属性没有明确的获取/设置。 coerceVector 似乎没有丢失“名称”(它的 R 等价物 as.sth 明确将“属性”设置为 NULL),所以,我猜,“名称”只是被忽略了吗?此外,可能值得注意的是,as.POSIXct.POSIXlt 确实获得了“POSIXlt”对象的“年份”组件的“名称”,以将其设置为其返回值,而 as.Date.POSIXlt 忽略它(两者都在 C和 R 代码)。 感谢您的详尽回答!似乎写一个通用的as.named 方法可能是值得的。剩下的问题是为什么会出现这种情况——只是“效率”? @MichaelChirico 这可能只是一个疏忽。举报吧。 @Roland 文档(?as.vector?as.double,也许更多)都提到了名称的丢失,这表明这可能不是疏忽。反正我已经举报了。

以上是关于为啥 `as` 方法会删除向量名称,有没有办法解决它?的主要内容,如果未能解决你的问题,请参考以下文章

具有特征名称的 OneHot 向量

为啥在定义为宏值的路径中指定的反斜杠会被删除?有没有办法避免这种情况?

为啥我的搜索功能没有按预期工作,我该如何解决?

ie8下showModalDialog为啥无效呢解决办法

gitignore有时候为啥过滤不了文件或目录

为啥这个向量代码会出现分段错误?