为啥 `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
向量不能被命名(这很奇怪,因为它们基本上是专门解释的integer
s):
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.Date
的POSIXlt
和POSIXct
方法的行为相似;更令人惊讶的是看到as.Date(c(a = as.POSIXct(Sys.time())), tz = "UTC")
VS as.Date(c(a = as.POSIXct(Sys.time())), tz = "GMT")
。 ... as
es... 都转换(除非我误解了您的第二条评论)因为 c(a = 1, b = 2)
是“数字”; as(, "character")
最终调用 as.character
这就是为什么“名称”丢失但我仍然认为我们可以期待相同的行为..?
我认为as.Date.POSIXlt
不应该删除这些名称。在这方面,所有as.Date
方法的行为都应该相同。你应该报告这个(可能值得一封措辞谨慎的电子邮件给 r-devel)。
@MichaelChirico 好吧,as.Date.character
在内部调用 as.Date.POSIXlt
。 as.Date.numeric
保留名称。
如果目标是通过names
访问日期,将向量转换为list
会更好吗? x <- list(ind = "2015-07-04", nyd = "2016-01-01")
y <- 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<-
是 as
方法的包装器,它首先保存对象的属性,然后将它们返回给新转换的对象,它应该可以完美地工作。但在这种情况下,不知何故,attributes(new_Date_vector) <- 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<-.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` 方法会删除向量名称,有没有办法解决它?的主要内容,如果未能解决你的问题,请参考以下文章