преобразовать переменную времени netcdf в объект даты R

У меня есть файл netcdf с временными рядами, а переменная времени имеет следующие типичные метаданные:

    double time(time) ;
            time:standard_name = "time" ;
            time:bounds = "time_bnds" ;
            time:units = "days since 1979-1-1 00:00:00" ;
            time:calendar = "standard" ;
            time:axis = "T" ;

Внутри R я хочу преобразовать время в объект даты R. На данный момент я достигаю этого аппаратным способом, читая атрибут единиц, разбивая строку и используя третью запись в качестве источника (таким образом, предполагая, что интервал равен «дням», а время — 00:00 и т. д.):

require("ncdf4")
f1<-nc_open("file.nc")
time<-ncvar_get(f1,"time")
tunits<-ncatt_get(f1,"time",attname="units")
tustr<-strsplit(tunits$value, " ")
dates<-as.Date(time,origin=unlist(tustr)[3])

Это аппаратное решение работает для моего конкретного примера, но я надеялся, что в R может быть пакет, который хорошо обрабатывает соглашения UNIDATA о данных netcdf для единиц времени и безопасно преобразовывает их в объект даты R?


person Adrian Tompkins    schedule 01.09.2017    source источник
comment
Обратите внимание, что недавно предложенный и в настоящее время находящийся в разработке пакет awesome stars будет автоматически обрабатывать даты, см. пример в первом сообщении блога: r-spatial.org/r/2017/11/23/stars1.html   -  person AF7    schedule 24.11.2017
comment
Ах, я забыл добавить, что пакет units, кажется, изящно обрабатывает даты. Стоит попробовать.   -  person AF7    schedule 29.12.2017
comment
Смотрите мои правки в моем ответе для примера   -  person AF7    schedule 29.12.2017


Ответы (3)


Нет такого, о котором я знаю. У меня есть эта удобная функция, использующая lubridate, которая в основном идентична вашей.

getNcTime <- function(nc) {
    require(lubridate)
    ncdims <- names(nc$dim) #get netcdf dimensions
    timevar <- ncdims[which(ncdims %in% c("time", "Time", "datetime", "Datetime", "date", "Date"))[1]] #find time variable
    times <- ncvar_get(nc, timevar)
    if (length(timevar)==0) stop("ERROR! Could not identify the correct time variable")
    timeatt <- ncatt_get(nc, timevar) #get attributes
    timedef <- strsplit(timeatt$units, " ")[[1]]
    timeunit <- timedef[1]
    tz <- timedef[5]
    timestart <- strsplit(timedef[4], ":")[[1]]
    if (length(timestart) != 3 || timestart[1] > 24 || timestart[2] > 60 || timestart[3] > 60 || any(timestart < 0)) {
        cat("Warning:", timestart, "not a valid start time. Assuming 00:00:00\n")
        warning(paste("Warning:", timestart, "not a valid start time. Assuming 00:00:00\n"))
        timedef[4] <- "00:00:00"
    }
    if (! tz %in% OlsonNames()) {
        cat("Warning:", tz, "not a valid timezone. Assuming UTC\n")
        warning(paste("Warning:", timestart, "not a valid start time. Assuming 00:00:00\n"))
        tz <- "UTC"
    }
    timestart <- ymd_hms(paste(timedef[3], timedef[4]), tz=tz)
    f <- switch(tolower(timeunit), #Find the correct lubridate time function based on the unit
        seconds=seconds, second=seconds, sec=seconds,
        minutes=minutes, minute=minutes, min=minutes,
        hours=hours,     hour=hours,     h=hours,
        days=days,       day=days,       d=days,
        months=months,   month=months,   m=months,
        years=years,     year=years,     yr=years,
        NA
    )
    suppressWarnings(if (is.na(f)) stop("Could not understand the time unit format"))
    timestart + f(times)
}

РЕДАКТИРОВАТЬ: можно также взглянуть на ncdf4.helpers::nc.get.time.series

EDIT2: обратите внимание, что недавно предложенный и в настоящее время находящийся в разработке пакет awesome stars будет автоматически обрабатывать даты, см. первое сообщение в блоге для примера.

EDIT3: другой способ - напрямую использовать пакет units, который использует stars. Можно было бы сделать что-то вроде этого: (все еще неправильно обрабатывая календарь, я не уверен, что units может)

getNcTime <- function(nc) { ##NEW VERSION, with the units package
    require(units)
    require(ncdf4)
    options(warn=1) #show warnings by default
    if (is.character(nc)) nc <- nc_open(nc)
    ncdims <- names(nc$dim) #get netcdf dimensions
    timevar <- ncdims[which(ncdims %in% c("time", "Time", "datetime", "Datetime", "date", "Date"))] #find (first) time variable
    if (length(timevar) > 1) {
        warning(paste("Found more than one time var. Using the first:", timevar[1]))
        timevar <- timevar[1]
    }
    if (length(timevar)!=1) stop("ERROR! Could not identify the correct time variable")
    times <- ncvar_get(nc, timevar) #get time data
    timeatt <- ncatt_get(nc, timevar) #get attributes
    timeunit <- timeatt$units
    units(times) <- make_unit(timeunit)
    as.POSIXct(time)
}
person AF7    schedule 02.09.2017
comment
ПРИМЕЧАНИЕ: ни функция AF7, ни функция SnowFrog не могут правильно обрабатывать атрибут calendar=365_day, в то время как ncdf4.helpers::nc.get.time.series работает с 365-дневным календарем! - person tbc; 19.11.2017

Я только что обнаружил, что существует пакет под названием ncdf.tools, в котором есть функция:

convertDateNcdf2R

который

преобразует вектор времени из файла netCDF или вектор юлианских дней (или секунд, минут, часов) с указанного начала в вектор POSIXct R.

что полезно. Более подробная информация доступна здесь: https://rdrr.io/cran/ncdf.tools/man/convertDateNcdf2R.html

person Adrian Tompkins    schedule 12.12.2019

Мне не удалось заставить функцию @AF7 работать с моими файлами, поэтому я написал свою собственную. Приведенная ниже функция создает вектор дат POSIXct, для которого начальная дата, временной интервал, единица измерения и длина считываются из файла nc. Он работает с файлами nc многих (но, вероятно, не всех...) форм и форм.

 ncdate <- function(nc) {
    ncdims <- names(nc$dim) #Extract dimension names
    timevar <- ncdims[which(ncdims %in% c("time", "Time", "datetime", "Datetime",
                                          "date", "Date"))[1]] # Pick the time dimension
    ntstep <-nc$dim[[timevar]]$len
    tm <- ncvar_get(nc, timevar) # Extract the timestep count
    tunits <- ncatt_get(nc, timevar, "units") # Extract the long name of units
    tspace <- tm[2] - tm[1] # Calculate time period between two timesteps, for the "by" argument 
    tstr <- strsplit(tunits$value, " ") # Extract string components of the time unit
    a<-unlist(tstr[1]) # Isolate the unit .i.e. seconds, hours, days etc.
    uname <- a[which(a %in% c("seconds","hours","days"))[1]] # Check unit
    startd <- as.POSIXct(gsub(paste(uname,'since '),'',tunits$value),format="%Y-%m-%d %H:%M:%S") ## Extract the start / origin date
    tmulti <- 3600 # Declare hourly multiplier for date
    if (uname == "days") tmulti =86400 # Declare daily multiplier for date
    ## Rename "seconds" to "secs" for "by" argument and change the multiplier.
    if (uname == "seconds") {
        uname <- "secs"
        tmulti <- 1 }
    byt <- paste(tspace,uname) # Define the "by" argument
    if (byt == "0.0416666679084301 days") { ## If the unit is "days" but the "by" interval is in hours
    byt= "1 hour"                       ## R won't understand "by < 1" so change by and unit to hour.
    uname = "hours"}
    datev <- seq(from=as.POSIXct(startd+tm[1]*tmulti),by= byt, units=uname,length=ntstep)
}

Изменить

Чтобы устранить недостаток, отмеченный комментарием @AF7 о том, что приведенный выше код будет работать только для файлов с регулярными интервалами, datev можно рассчитать как

 datev <- as.POSIXct(tm*tmulti,origin=startd)
person SnowFrog    schedule 20.09.2017
comment
большое спасибо - я позаимствовал некоторые идеи кода AF7 и объединил их в свой R-скрипт. Интересно, можно ли добавить такую ​​функциональность в сам пакет ncdf4? Было бы здорово иметь что-то подобное в стандартной комплектации. - person Adrian Tompkins; 21.09.2017
comment
Обратите внимание, что это работает только для регулярных промежутков времени, что не обязательно верно для всех NetCDF. Почему моя функция у вас не сработала? Я постараюсь сделать это более общим. - person AF7; 24.09.2017
comment
@ Адриан Томпкинс. Раньше в пакете была функция вычисления даты, но типов netcdfs так много, что она работала не со всеми файлами, поэтому разработчик убрал ее (спасибо Дэвиду Пирсу за информацию). Поскольку то же самое будет с моей функцией и в настоящее время с AF7, лучше всего сделать эти функции неофициальными и, по крайней мере, помочь другим пользователям настроить свои собственные. - person SnowFrog; 25.09.2017
comment
Спасибо, очень полезно знать - person Adrian Tompkins; 25.09.2017
comment
Я спросил tidync разработчика, может ли он быть заинтересован. Вот проблема github, вы можете высказать свое мнение там: github.com/ hypertidy/tidync/issues/54#issuecomment-331694920 - person AF7; 25.09.2017