Newer
Older
#'Save CSTools objects of class 's2dv_cube' containing experiments or observed
#'data in NetCDF format
#'
#'@author Perez-Zanon Nuria, \email{nuria.perez@bsc.es}
#'
#'@description This function allows to divide and save a object of class
#''s2dv_cube' into a NetCDF file, allowing to reload the saved data using
#'\code{CST_Load} function.
#'@param data An object of class \code{s2dv_cube}.
#'@param destination A character string containing the directory name in which
#' to save the data. NetCDF file for each starting date are saved into the
#' folder tree: \cr
#' destination/experiment/variable/. By default the function
#' creates and saves the data into the folder "CST_Data" in the working
#' directory.
#'@param extra_string A character string to be include as part of the file name,
#' for instance, to identify member or realization. It would be added to the
#' file name between underscore characters.
#'@param single_file A logical value indicating if all object is saved in a
#' unique file (TRUE) or in separated directories (FALSE). When it is FALSE,
#' the array is separated for Datasets, variable and start date.
#'@seealso \code{\link{CST_Load}}, \code{\link{as.s2dv_cube}} and \code{\link{s2dv_cube}}
#'data <- lonlat_temp$exp
#'CST_SaveExp(data = data, destination = destination)
#'}
#'
#'@import ncdf4
#'@importFrom s2dv Reorder InsertDim
#'@importFrom ClimProjDiags Subset
#'@import multiApply
CST_SaveExp <- function(data, destination = "./CST_Data", sdate_dim = 'sdate',
ftime_dim = 'time', dat_dim = 'dataset',
var_dim = 'var', memb_dim = 'member',
single_file = TRUE, extra_string = NULL) {
# Check 's2dv_cube'
if (!inherits(data, 's2dv_cube')) {
stop("Parameter 'data' must be of the class 's2dv_cube', ",
"as output by CSTools::CST_Load.")
}
# Check object structure
if (!all(c('data', 'coords', 'attrs') %in% names(data))) {
stop("Parameter 'data' must have 'data', 'coords' and 'attrs' elements ",
"within the 's2dv_cube' structure.")
}
if (!is.character(sdate_dim)) {
stop("Parameter 'sdate_dim' must be a character string.")
warning("Parameter 'sdate_dim' has length greater than 1 and ",
"only the first element will be used.")
if (!all(sdate_dim %in% names(data$coords))) {
stop("Parameter 'sdate_dim' is not found in 'data' dimension.")
}
if (!is.character(data$attrs$Variable$varName)) {
stop("Element 'varName' mustbe a character string.")
}
# metadata
if (!inherits(data$attrs$Variable$metadata, 'list')) {
stop("Element $attrs$Variable$metadata must be a list.")
}
if (!any(names(data$attrs$Variable$metadata) %in% names(data$coords))) {
warning("Metadata is not found for any coordinate.")
} else if (!any(names(data$attrs$Variable$metadata) %in%
data$attrs$Variable$varName)) {
warning("Metadata is not found for any variable.")
}
# Coordinate attributes
if (!any(names(data$coords) %in% .KnownLonNames()) |
!any(names(data$coords) %in% .KnownLatNames())) {
stop("Spatial coordinate names do not match any of the names accepted by ",
"the package.")
}
lon_dim <- names(data$coords)[which(names(data$coords) %in% .KnownLonNames())]
lat_dim <- names(data$coords)[which(names(data$coords) %in% .KnownLatNames())]
# Dates
time_values <- data$attrs$Dates
if (is.null(dim(time_values))) {
stop("Dates element in '$data$attrs$Dates' must have time dimensios.")
}
SaveExp(data = data$data,
destination = destination,
coords = data$coords,
Datasets = data$attrs$Datasets,
dat_dim = dat_dim, sdate_dim = sdate_dim,
ftime_dim = ftime_dim, var_dim = var_dim, memb_dim = memb_dim,
single_file = single_file)
#'Save an experiment in a format compatible with CST_Load
#'@description This function is created for compatibility with CST_Load/Load for
#'saving post-processed datasets such as those calibrated of downscaled with
#'CSTools functions
#'
#'@author Perez-Zanon Nuria, \email{nuria.perez@bsc.es}
#'
#'@param data An multi-dimensional array with named dimensions (longitude,
#' latitude, time, member, sdate).
#'@param destination A character string indicating the path where to store the
#' NetCDF files.
#'@param coords A named list with elements of the coordinates corresponding to
#' the dimensions of the data parameter. The names and length of each element
#' must correspond to the names of the dimensions. If any coordinate is not
#' provided, it is set as an index vector with the values from 1 to the length
#' of the corresponding dimension.
#'@param startdates A vector of dates indicating the initialization date of each
#' simulations.
#'@param Dates A matrix of dates with the corresponding sdate and forecast time
#' dimension.
#'@param Datasets A vector of character string indicating the names of the
#'@param varname A character string indicating the name of the variable to be
#'@param metadata A named list where each element is a variable containing the
#' corresponding information. The information must be contained in a list of
#' lists for each variable.
#'@param single_file A logical value indicating if all object is saved in a
#' unique file (TRUE) or in separated directories (FALSE). When it is FALSE,
#' the array is separated for Datasets, variable and start date.
#'@param extra_string A character string to be include as part of the file name,
#' for instance, to identify member or realization.
#'@return The function creates as many files as sdates per Datasets. Each file
#'could contain multiple members. It would be added to the file name between
#'underscore characters. The path will be created with the name of the variable
#'and each Datasets.
#'data <- lonlat_temp$exp$data
#'lon <- lonlat_temp$exp$coords$lon
#'lat <- lonlat_temp$exp$coords$lat
#'coords <- list(lon = lon, lat = lat)
#'Datasets <- lonlat_temp$exp$attrs$Datasets
#'varname <- 'tas'
#'Dates <- lonlat_temp$exp$attrs$Dates
#'metadata <- lonlat_temp$exp$attrs$Variable$metadata
#'SaveExp(data = data, destination = destination, coords = coords,
#' Datasets = Datasets, varname = varname, Dates = Dates,
#' metadata = metadata, single_file = TRUE)
#'@import ncdf4
#'@importFrom s2dv Reorder InsertDim
#'@import multiApply
#'@importFrom ClimProjDiags Subset
SaveExp <- function(data, destination, Dates, coords = NULL,
Datasets = NULL, varname = NULL, metadata = NULL,
startdates = NULL, dat_dim = 'dataset', sdate_dim = 'sdate',
ftime_dim = 'time', var_dim = 'var', memb_dim = 'member',
single_file = TRUE, extra_string = NULL) {
## Initial input parameter checks:
# data
if (is.null(data)) {
stop("Parameter 'data' cannot be NULL.")
}
dimnames <- names(dim(data))
if (is.null(dimnames)) {
stop("Parameter 'data' must be an array with named dimensions.")
}
# destination
if (!is.character(destination) & length(destination) > 1) {
stop("Parameter 'destination' must be a character string of one element ",
"indicating the name of the file (including the folder if needed) ",
"where the data will be saved.")
}
# Dates
if (is.null(Dates)) {
stop("Parameter 'Dates' cannot be NULL.")
}
if (!inherits(Dates, "POSIXct") & !inherits(Dates, "Date")) {
stop("Parameter 'Dates' must be of 'POSIXct' or 'Dates' class.")
}
# varname
if (is.null(varname)) {
warning("Parameter 'varname' is NULL. It will be assigned to 'X'.")
varname <- 'X'
} else if (length(varname) > 1) {
multiple_vars <- TRUE
} else {
multiple_vars <- FALSE
}
if (!all(sapply(varname, is.character))) {
stop("Parameter 'varname' must be a character string with the ",
"variable names.")
}
## Coordinates
# longitude and latitude
if (!is.null(coords)) {
if (!all(names(coords) %in% dimnames)) {
coords <- coords[-which(!names(coords) %in% dimnames)]
}
for (i_coord in dimnames) {
if (i_coord %in% names(coords)) {
if (length(coords[[i_coord]]) != dim(data)[i_coord]) {
warning(paste0("Coordinate '", i_coord, "' has different lenght as ",
"its dimension and it will not be used."))
coords[[i_coord]] <- 1:dim(data)[i_coord]
}
} else {
warning(paste0("Coordinate '", i_coord, "' is not provided ",
"and it will be set as index in element coords."))
coords[[i_coord]] <- 1:dim(data)[i_coord]
}
}
} else {
## Dimensions checks:
# Spatial coordinates
if (!any(dimnames %in% .KnownLonNames()) |
!any(dimnames %in% .KnownLatNames())) {
stop("Spatial coordinate names do not match any of the names accepted by ",
"the package.")
}
lon_dim <- dimnames[which(dimnames %in% .KnownLonNames())]
lat_dim <- dimnames[which(dimnames %in% .KnownLatNames())]
if (length(lon_dim) > 1) {
warning("Found more than one longitudinal dimension. Only the first one ",
"will be used.")
warning("Found more than one latitudinal dimension. Only the first one ",
"will be used.")
if (!is.character(ftime_dim)) {
stop("Parameter 'ftime_dim' must be a character string.")
}
if (!all(ftime_dim %in% names(dim(data)))) {
stop("Parameter 'ftime_dim' is not found in 'data' dimension.")
warning("Parameter 'ftime_dim' has length greater than 1 and ",
"only the first element will be used.")
if (!is.character(sdate_dim)) {
stop("Parameter 'sdate_dim' must be a character string.")
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
if (!all(sdate_dim %in% names(dim(data)))) {
stop("Parameter 'sdate_dim' is not found in 'data' dimension.")
}
if (length(sdate_dim) > 1) {
warning("Parameter 'sdate_dim' has length greater than 1 and ",
"only the first element will be used.")
sdate_dim <- sdate_dim[1]
}
# memb_dim
if (!is.null(memb_dim)) {
if (!is.character(memb_dim)) {
stop("Parameter 'memb_dim' must be a character string.")
}
if (!all(memb_dim %in% names(dim(data)))) {
stop("Parameter 'memb_dim' is not found in 'data' dimension. Set it ",
"as NULL if there is no member dimension.")
}
if (length(memb_dim) > 1) {
warning("Parameter 'memb_dim' has length greater than 1 and ",
"only the first element will be used.")
memb_dim <- memb_dim[1]
}
}
# dat_dim
if (!is.null(dat_dim)) {
if (!is.character(dat_dim)) {
stop("Parameter 'dat_dim' must be a character string.")
}
if (!all(dat_dim %in% names(dim(data)))) {
stop("Parameter 'dat_dim' is not found in 'data' dimension. Set it ",
"as NULL if there is no Datasets dimension.")
}
if (length(dat_dim) > 1) {
warning("Parameter 'dat_dim' has length greater than 1 and ",
"only the first element will be used.")
dat_dim <- dat_dim[1]
}
} else {
data <- InsertDim(data, posdim = 1, lendim = 1, name = "dataset")
dimnames <- names(dim(data))
dat_dim <- 'dataset'
}
n_datasets <- dim(data)[dat_dim]
# var
if (!is.null(var_dim)) {
if (!is.character(var_dim)) {
stop("Parameter 'var_dim' must be a character string.")
}
if (!all(var_dim %in% names(dim(data)))) {
stop("Parameter 'var_dim' is not found in 'data' dimension. Set it ",
"as NULL if there is no variable dimension.")
}
if (length(var_dim) > 1) {
warning("Parameter 'var_dim' has length greater than 1 and ",
"only the first element will be used.")
var_dim <- var_dim[1]
}
} else {
data <- InsertDim(data, posdim = 1, lendim = 1, name = "var")
dimnames <- names(dim(data))
var_dim <- 'var'
n_vars <- dim(data)[var_dim]
# Dates dimension check
if (is.null(dim(Dates))) {
stop("Parameter 'Dates' must have dimension names.")
}
if (all(names(dim(Dates)) == c(ftime_dim, sdate_dim)) |
all(names(dim(Dates)) == c(sdate_dim, ftime_dim))) {
if (is.null(startdates)) {
startdates <- Subset(Dates, along = ftime_dim, 1, drop = 'selected')
} else if ((!inherits(startdates, "POSIXct") & !inherits(startdates, "Date")) &&
(!is.character(startdates) | (all(nchar(startdates) != 10) &
all(nchar(startdates) != 8) & all(nchar(startdates) != 6)))) {
warning("Parameter 'startdates' should be a character string containing ",
"the start dates in the format 'yyyy-mm-dd', 'yyyymmdd', 'yyyymm', ",
"'POSIXct' or 'Dates' class.")
startdates <- Subset(Dates, along = ftime_dim, 1, drop = 'selected')
}
} else {
stop("Parameter 'Dates' must have start date dimension and ",
# level
if (any(names(dim(data)) == 'level')) {
stop("Ask for saving 3Dim fields to the mantainer.")
# extra_string
if (!is.null(extra_string)) {
if (!is.character(extra_string)) {
stop("Parameter 'extra_string' must be a character string.")
}
}
# Datasets names
if (is.null(Datasets)) {
warning("Parameter 'Datasets' is NULL. Files will be saved with a ",
"directory name of 'XXX'.")
Datasets <- rep('XXX', n_datasets )
}
## From Load
if (inherits(Datasets, 'list')) {
Datasets <- names(Datasets)
if (n_datasets > length(Datasets)) {
warning("Dimension 'Datasets' in 'data' is greater than those listed in ",
"element 'Datasets' and the first element will be reused.")
Datasets <- c(Datasets, rep(Datasets[1], n_datasets - length(Datasets)))
} else if (n_datasets < length(Datasets)) {
warning("Dimension 'Datasets' in 'data' is smaller than those listed in ",
"element 'Datasets' and only the firsts elements will be used.")
Datasets <- Datasets[1:n_datasets]
dimnames <- names(dim(data))
alldims <- c(dat_dim, var_dim, sdate_dim, lon_dim, lat_dim, memb_dim, ftime_dim)
if (!all(dimnames %in% alldims)) {
stop("Parameter 'data' has extra unknown dimensions that are not accepted ",
"by the function yet.")
} else if (!all(alldims %in% dimnames)) {
stop("Parameter 'data' don't have all dimensions needed: dat_dim, var_dim, ",
"sdate_dim, lon_dim, lat_dim, memb_dim, ftime_dim.")
}
# Reorder
if (any(dimnames != alldims)) {
data <- Reorder(data, alldims)
}
# Dimensions definition
defined_dims <- NULL
filedims <- names(dim(data))[which(!names(dim(data)) %in% c(dat_dim, var_dim,
sdate_dim, ftime_dim))]
for (i_coord in filedims) {
dim_info <- list()
# vals
if (i_coord %in% names(coords)) {
dim_info[['vals']] <- as.vector(coords[[i_coord]])
} else {
dim_info[['vals']] <- 1:dim(data)[i_coord]
}
# name
dim_info[['name']] <- i_coord
# len
dim_info[['len']] <- as.numeric(dim(data)[i_coord])
# unlim
dim_info[['unlim']] <- FALSE
# create_dimvar
dim_info[['create_dimvar']] <- TRUE
## metadata
if (i_coord %in% names(metadata)) {
if ('variables' %in% names(attributes(metadata[[i_coord]]))) {
# from Start: 'lon' or 'lat'
attrs <- attributes(metadata[[i_coord]])[['variables']][[i_coord]]
i_coord_info <- attrs[!sapply(attrs, inherits, 'list')]
} else if (inherits(metadata[[i_coord]], 'list')) {
i_coord_info <- metadata[[i_coord]]
} else if (!is.null(attributes(metadata[[i_coord]]))) {
i_coord_info <- attributes(metadata[[i_coord]])
} else {
stop("Metadata is not correct.")
}
# len
if ('size' %in% names(i_coord_info)) {
if (i_coord_info[['size']] != dim(data)[i_coord]) {
dim_info[['original_len']] <- i_coord_info[['size']]
}
# units
if (!('units' %in% names(i_coord_info))) {
dim_info[['units']] <- ''
} else {
dim_info[['units']] <- i_coord_info[['units']]
}
# calendar
if (!('calendar' %in% names(i_coord_info))) {
dim_info[['calendar']] <- NA
} else {
dim_info[['calendar']] <- i_coord_info[['calendar']]
}
# longname
if ('long_name' %in% names(i_coord_info)) {
dim_info[['longname']] <- i_coord_info[['long_name']]
} else if ('longname' %in% names(i_coord_info)) {
dim_info[['longname']] <- i_coord_info[['longname']]
# extra information
if (!is.null(names(i_coord_info))) {
extra_info_dim[[i_coord]] <- i_coord_info
}
# units
dim_info[['units']] <- "adim"
# longname
# calendar
dim_info[['calendar']] <- NA
new_dim <- list(ncdim_def(name = dim_info[['name']], units = dim_info[['units']],
vals = dim_info[['vals']], unlim = dim_info[['unlim']],
create_dimvar = dim_info[['create_dimvar']],
calendar = dim_info[['calendar']],
longname = dim_info[['longname']]))
names(new_dim) <- i_coord
defined_dims <- c(defined_dims, new_dim)
defined_vars <- list()
if (!single_file) {
for (i in 1:n_datasets) {
path <- file.path(destination, Datasets[i], varname)
for (j in 1:n_vars) {
dir.create(path[j], recursive = TRUE)
sdates <- gsub("-", "", startdates)
dim(sdates) <- c(length(sdates))
data_subset <- Subset(data, along = c(dat_dim, var_dim),
Apply(data = list(data_subset, sdates, Dates),
target_dims = list(c(memb_dim, ftime_dim, lat_dim, lon_dim),
NULL, ftime_dim),
fun = .saveExp, ftime_dim = ftime_dim, defined_dims = defined_dims,
varname = varname[j],
metadata_var = metadata[[varname[j]]],
destination = path[j], extra_info_dim = extra_info_dim,
extra_string = extra_string)
}
}
} else {
# Datasets definition
new_dim <- list(ncdim_def(name = dat_dim, units = "adim",
vals = 1 : dim(data)[dat_dim],
longname = 'Datasets', create_dimvar = TRUE))
defined_dims <- c(new_dim, defined_dims)
extra_info_dim[[dat_dim]] <- list(Datasets = paste(Datasets, collapse = ', '))
# sdate definition
sdates <- Subset(Dates, along = ftime_dim, 1, drop = 'selected')
differ <- as.numeric((sdates - sdates[1])/3600)
new_dim <- list(ncdim_def(name = sdate_dim, units = paste('hours since', sdates[1]),
vals = differ,
longname = sdate_dim, create_dimvar = TRUE))
names(new_dim) <- sdate_dim
defined_dims <- c(defined_dims, new_dim)
extra_info_dim[[sdate_dim]] <- list(sdates = paste(sdates, collapse = ', '))
# ftime definition
ftime_dates <- Subset(Dates, along = sdate_dim, 1, drop = 'selected')
differ <- as.numeric((ftime_dates - ftime_dates[1])/3600)
dim_time <- list(ncdim_def(name = ftime_dim, units = paste('hours since', Dates[1]),
vals = differ, calendar = 'proleptic_gregorian',
longname = ftime_dim, unlim = TRUE))
names(dim_time) <- ftime_dim
defined_dims <- c(defined_dims, dim_time)
# var definition
defined_vars <- list()
for (j in 1:n_vars) {
i_var_info <- metadata[[varname[j]]][!sapply(metadata[[varname[j]]], inherits, 'list')]
# units
if ('units' %in% names(i_var_info)) {
var_info[['units']] <- i_var_info[['units']]
i_var_info[['units']] <- NULL
# dim
var_info[['dim']] <- defined_dims
# missval
if ('missval' %in% names(i_var_info)) {
var_info[['missval']] <- i_var_info[['missval']]
i_var_info[['missval']] <- NULL
} else {
var_info[['missval']] <- NULL
}
# longname
if (any(c('longname', 'long_name') %in% names(i_var_info))) {
longname <- names(i_var_info)[which(names(i_var_info) %in% c('longname', 'long_name'))]
var_info[['longname']] <- i_var_info[[longname]]
i_var_info[[longname]] <- NULL
} else {
}
# prec
if ('prec' %in% names(i_var_info)) {
var_info[['prec']] <- i_var_info[['prec']]
i_var_info[['prec']] <- NULL
} else {
prec <- typeof(data)
if (prec == 'character') {
var_info[['prec']] <- 'char'
}
if (any(prec %in% c('short', 'float', 'double', 'integer', 'char', 'byte'))) {
var_info[['prec']] <- prec
} else {
var_info[['prec']] <- 'double'
}
}
# extra information
if (!is.null(names(i_var_info))) {
extra_info_var[[varname[j]]] <- i_var_info
}
new_var <- list(ncvar_def(name = var_info[['name']],
units = var_info[['units']],
dim = var_info[['dim']],
missval = var_info[['missval']],
longname = var_info[['longname']],
prec = var_info[['prec']]))
defined_vars <- c(defined_vars, new_var)
}
if (is.null(extra_string)) {
file_name <- paste0(varname[j], "_", extra_string, ".nc")
}
full_filename <- file.path(destination, file_name)
file_nc <- nc_create(full_filename, defined_vars)
if (is.null(var_dim)) {
ncvar_put(file_nc, varname, vals = data)
for (j in 1:n_vars) {
ncvar_put(file_nc, defined_vars[[j]]$name,
vals = Subset(data, var_dim, j, drop = 'selected'))
# Additional dimension attributes
for (dim in names(defined_dims)) {
if (dim %in% names(extra_info_dim)) {
for (info_dim in names(extra_info_dim[[dim]])) {
ncatt_put(file_nc, dim, info_dim, as.character(extra_info_dim[[dim]][[info_dim]]))
}
}
}
# Additional dimension attributes
for (var in names(defined_vars)) {
if (var %in% names(extra_info_var)) {
for (info_var in names(extra_info_var[[var]])) {
ncatt_put(file_nc, var, info_var, as.character(extra_info_var[[var]][[info_var]]))
}
}
}
nc_close(file_nc)
.saveExp <- function(data, sdates, dates, ftime_dim, varname, units, defined_dims,
metadata_var, destination, extra_info_dim = NULL,
extra_string = NULL) {
differ <- as.numeric((dates - dates[1])/3600)
dim_time <- list(ncdim_def(name = ftime_dim, units = paste('hours since', Dates[1]),
vals = differ, calendar = 'proleptic_gregorian',
longname = ftime_dim, unlim = TRUE))
names(dim_time) <- ftime_dim
defined_dims <- c(defined_dims, dim_time)
## Define var metadata
var_info <- NULL
extra_info_var <- NULL
i_var_info <- metadata_var[!sapply(metadata_var, inherits, 'list')]
# name
# units
if ('units' %in% names(i_var_info)) {
var_info[['units']] <- i_var_info[['units']]
i_var_info[['units']] <- NULL
} else {
var_info[['units']] <- ''
}
# dim
var_info[['dim']] <- defined_dims
# missval
if ('missval' %in% names(i_var_info)) {
var_info[['missval']] <- i_var_info[['missval']]
i_var_info[['missval']] <- NULL
} else {
var_info[['missval']] <- NULL
}
# longname
if (any(c('longname', 'long_name') %in% names(i_var_info))) {
longname <- names(i_var_info)[which(names(i_var_info) %in% c('longname', 'long_name'))]
var_info[['longname']] <- i_var_info[[longname]]
i_var_info[[longname]] <- NULL
} else {
if ('prec' %in% names(i_var_info)) {
var_info[['prec']] <- i_var_info[['prec']]
i_var_info[['prec']] <- NULL
prec <- typeof(data)
if (prec == 'character') {
var_info[['prec']] <- 'char'
}
if (any(prec %in% c('short', 'float', 'double', 'integer', 'char', 'byte'))) {
var_info[['prec']] <- prec
} else {
var_info[['prec']] <- 'double'
}
}
# extra information
if (!is.null(names(i_var_info))) {
extra_info_var <- i_var_info
datanc <- ncvar_def(name = var_info[['name']],
units = var_info[['units']],
dim = var_info[['dim']],
missval = var_info[['missval']],
longname = var_info[['longname']],
prec = var_info[['prec']])
file_name <- paste0(varname, "_", sdates, ".nc")
file_name <- paste0(varname, "_", extra_string, "_", sdates, ".nc")
full_filename <- file.path(destination, file_name)
file_nc <- nc_create(full_filename, datanc)
ncvar_put(file_nc, datanc, data)
# Additional attributes
for (dim in names(defined_dims)) {
if (dim %in% names(extra_info_dim)) {
for (info_dim in names(extra_info_dim[[dim]])) {
ncatt_put(file_nc, dim, info_dim, as.character(extra_info_dim[[dim]][[info_dim]]))
}
}
}
# Additional dimension attributes
if (!is.null(extra_info_var)) {
for (info_var in names(extra_info_var)) {
ncatt_put(file_nc, varname, info_var, as.character(extra_info_var[[info_var]]))