STL <- function(x, y=NULL, data=d, filter=NULL,
                ts_format=NULL, ts_unit=NULL, ts_agg=c("sum","mean"),
                show_range=FALSE, robust=FALSE, quiet=FALSE, do_plot=TRUE,
                pdf_file=NULL, width=6.5, height=6) {
  ts_agg <-  match.arg(ts_agg)

  x.name <- deparse(substitute(x))
  y.name <- deparse(substitute(y))

  df.name <- deparse(substitute(data))
  options(dname = df.name)

  # subset filter
  if (!missing(filter)) {
    txt <- .filter(deparse(substitute(filter)))
    r <- eval(str2expression(txt), envir=data, enclos=parent.frame())
    r <- r & !is.na(r)  # set missing for a row to FALSE
    nr.before <- nrow(data)
    if (any(r))
      data <- data[r,,drop=FALSE]
    if (!quiet) {
      cat("filter: ",  txt, "\n-----\n")
      cat("Rows of data before filtering: ", nr.before, "\n")
      cat("Rows of data after filtering:  ", nrow(data), "\n\n")
    }
  }  # end filter

  x.in.global <- df.or.global(x.name, df.name, role="x", fun.name="STL")

  if (!x.in.global)  {
    data.vars <- as.list(seq_along(data))
    names(data.vars) <- names(data)

    ind <- eval(substitute(x), envir=data.vars)  # col num of x var
    x.date <- data[, ind, drop=FALSE]

    # see if x.date has char string values to convert to type Date
    if (is.character(x.date[1,1])) {
      if (!is.null(ts_format))  # specify the date format
        x.date[,1] <- as.Date(x.date[,1], format=ts_format)
      else  # see if a date, and if so, infer the date format
        x.date <- date.infer(x.date, quiet)
    }

    is_ordered <- all(diff(x.date[,1]) >= 0) 
    if (!is_ordered) {
      cat("\n"); stop(call.=FALSE, "\n------\n",
        "The date variable, ", x.name, ", must be ordered across all data.\n\n",
        "Perhaps you have multiple groups of ", y.name, ".\n",
        "If so, use the parameter  filter  to focus on just one group.\n\n")
    }

    ind <- eval(substitute(y), envir=data.vars)  # col num of y var
    y.data <- data[, ind]

    #   if not specified, get the existing ts_unit
    #   if specified, aggregate the values of y.data over x
    tsdata <- .plt.time(x.date, y.data, by.call=NULL, x.name, n.by=0,
                        ts_unit, ts_agg)
    ts_unit <- tsdata$ts_unit
    x.date <- tsdata$x.call[,1]
    y.data <- tsdata$y.call[,1]
    y.ts <- .tsMake(x.date, y.data, ts_unit)  # convert data to ts form

    is_ts <- FALSE
  }  # end x is not in global

  else if (is.ts(x)) {
    is_ts <- TRUE
    y.ts <- x
    tsPull <- tsExtract(x)
    y.data <- tsPull$y
    y.name <- tsPull$y.name
  }

  else {  # data=NULL but x is a global object that is not a time series
    cat("\n"); stop(call.=FALSE, "\n------\n",
      "With  data=NULL, the variable  ", x.name, "  must be a time series\n",
      "  (ts) object. Otherwise, read the data into a data frame and\n",
      "  specify the date and value variables.\n\n")
  }

  # compute components
  decomp <- stl(y.ts, s.window="periodic", robust=robust)

  # set up pdf_file if needed
  if (do_plot && !is.null(pdf_file)) {
    if (!grepl("\\.pdf$", pdf_file))
      pdf_file <- paste(pdf_file, ".pdf", sep="")
    grDevices::pdf(file=pdf_file, width=width, height=height, onefile=FALSE)
  }

  if (do_plot)
    plot(decomp, col.range="lightgoldenrod")

  if (do_plot && !is.null(pdf_file)) {
    dev.off()
    if (!quiet) .showfile(pdf_file, "STL decomposition")
  }

  # extract components from the stl() decomposition
  seasonal <- decomp$time.series[, "seasonal"]
  trend <- decomp$time.series[, "trend"]
  error <- decomp$time.series[, "remainder"]

  if (!quiet) {

    # calculate the range of y and of each component
    range.y <- diff(range(y.data, na.rm=TRUE))
    range.seasonal <- diff(range(seasonal, na.rm=TRUE))
    range.trend <- diff(range(trend, na.rm=TRUE))
    range.error <- diff(range(error, na.rm=TRUE))

    # calculate the variance of y and of each component
    var.y <- var(y.data, na.rm = TRUE)
    var.seasonal <- var(seasonal, na.rm =TRUE)
    var.trend <- var(trend, na.rm=TRUE)
    var.error <- var(error, na.rm=TRUE)

    # calculate the proportion of variance explained by each component
    prop.seasonal <- var.seasonal / var.y
    prop.trend <- var.trend / var.y
    prop.error <- var.error / var.y

    # display the results
    cat("\nTotal variance of ", y.name, ": ", var.y, "\n", sep="")
    cat("Proportion of variance for components:\n")
    cat("  seasonality ---", .fmt(prop.seasonal,3), "\n")
    cat("  trend ---------", .fmt(prop.trend, 3), "\n")
    cat("  remainder -----", .fmt(prop.error, 3), "\n")

    if (show_range) {
      cat("\nRange of ", y.name, ": ", range.y, "\n", sep="")
      cat("Range of components:\n")
      cat("  seasonality ---", .fmt(range.seasonal,3), "\n")
      cat("  trend ---------", .fmt(range.trend, 3), "\n")
      cat("  remainder -----", .fmt(range.error, 3), "\n")
    }

    cat("\n")

  }  # end !quiet

  # output data structure
  te <- tsExtract(trend)
  if (!is_ts)
    de <- .toFmtDate(te[[1]], ts_unit)  # e.g., 2020-01-01 to 2020 Q1
  else
    de <- te[[1]]
  te <- as.numeric(te[[2]])   # trend component
  se <- tsExtract(seasonal)
  se <- as.numeric(se[[2]])   # seasonal component
  ee <- tsExtract(error)
  ee <- as.numeric(ee[[2]])   # error component
  out <- data.frame(de, y.data, te, se, ee)
  if (is_ts) {
    x.name <- "date"
    y.name <- "data"  # not recovered
  }
  names(out) <- c(x.name, y.name, "trend", "season", "error")
  return(invisible(out))

}
