Подтвердить что ты не робот

Почему методы `as` удаляют имена векторов, и есть ли способ вокруг него?

В основном, я пытаюсь сохранить вектор с именем dates специального Date, который приносит много результатов в моем анализе, например, Новый год 2016 и 4 июля 2015 года. Я хочу, чтобы у меня было возможность извлечь из него вместо индекса для надежности, например, dates["nyd"], чтобы получить Новый год и dates["ind"], чтобы получить 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.

4b9b3361

Ответ 1

Действительно, существует расхождение в разных методах 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, - это функция C, называемая POSIXlt2Date (быстрый просмотр файла names.c показывает, что функция do_POSIXlt2D из файла datetime.c). Для справки:

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;
}

К сожалению, мое понимание С слишком ограничено, чтобы знать, почему здесь теряются атрибуты. Мое предположение заключалось бы в том, что это происходит либо во время операции 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 для извлечения данных, которые могут быть разрешены для объекта Date, таким образом, не теряя атрибуты, но , если даны какие-либо другие часовые пояса, он затем преобразуется в объект 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 преобразуется в Date через as.Date.character, а затем добавляется вектор числа, сохраняя при этом атрибуты:

> 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" 

Ответ 2

Это не полный ответ на вопрос, но как способ обойти проблему, никто не упомянул функцию 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))