Skip to content

Commit

Permalink
Changes to core functions: Consolidated API call to one URL and fixed…
Browse files Browse the repository at this point in the history
… issue with plot for single series.
  • Loading branch information
MMenchero committed Dec 12, 2023
1 parent 1e7ef43 commit 85d8c84
Show file tree
Hide file tree
Showing 13 changed files with 154 additions and 118 deletions.
48 changes: 48 additions & 0 deletions .github/workflows/pkgdown.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
release:
types: [published]
workflow_dispatch:

name: pkgdown

jobs:
pkgdown:
runs-on: ubuntu-latest
# Only restrict concurrency for non-PR jobs
concurrency:
group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }}
env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
permissions:
contents: write
steps:
- uses: actions/checkout@v3

- uses: r-lib/actions/setup-pandoc@v2

- uses: r-lib/actions/setup-r@v2
with:
use-public-rspm: true

- uses: r-lib/actions/setup-r-dependencies@v2
with:
extra-packages: any::pkgdown, local::.
needs: website

- name: Build site
run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE)
shell: Rscript {0}

- name: Deploy to GitHub pages 🚀
if: github.event_name != 'pull_request'
uses: JamesIves/[email protected]
with:
clean: false
branch: gh-pages
folder: docs
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ Depends:
R (>= 2.10)
LazyData: true
Imports:
data.table,
dplyr,
ggplot2,
httr2,
lubridate,
rlang,
tidyr,
tidyselect,
tsibble
Suggests:
httptest2,
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ importFrom(lubridate,ymd)
importFrom(lubridate,ymd_hms)
importFrom(rlang,.data)
importFrom(tidyr,pivot_longer)
importFrom(tidyselect,everything)
importFrom(tsibble,is_tsibble)
1 change: 1 addition & 0 deletions R/nixtlaR-package.R
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#' @importFrom lubridate ymd_hms
#' @importFrom rlang .data
#' @importFrom tidyr pivot_longer
#' @importFrom tidyselect everything
#' @importFrom tsibble is_tsibble
## usethis namespace: end
NULL
49 changes: 23 additions & 26 deletions R/timegpt_anomaly_detection.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,16 @@
#'
timegpt_anomaly_detection <- function(df, freq=NULL, id_col=NULL, time_col="ds", target_col="y", level=c(99), clean_ex_first=TRUE, model="timegpt-1"){

# Validation ----
if(!tsibble::is_tsibble(df) & !is.data.frame(df)){
stop("Only tsibbles or data frames are allowed.")
}

# Prepare data ----
url_anomaly <- "https://dashboard.nixtla.io/api/timegpt_multi_series_anomalies"
if(is.null(id_col)){
url_anomaly <- "Write here the url for the single series case"
}else{
url_anomaly <- "https://dashboard.nixtla.io/api/timegpt_multi_series_anomalies"
# create unique_id for single series
df <- df |>
dplyr::mutate(unique_id = "id") |>
dplyr::select(c("unique_id", tidyselect::everything()))
}

data <- .timegpt_data_prep(df, freq, id_col, time_col, target_col)
df <- data$df
freq <- data$freq
y <- data$y

Expand All @@ -38,6 +34,8 @@ timegpt_anomaly_detection <- function(df, freq=NULL, id_col=NULL, time_col="ds",
clean_ex_first = clean_ex_first
)

names(df)[which(names(df) == time_col)] <- "ds"
names(df)[which(names(df) == target_col)] <- "y"
if(any(!(names(df) %in% c("unique_id", "ds", "y")))){
exogenous <- df |>
dplyr::select(-y)
Expand All @@ -51,7 +49,7 @@ timegpt_anomaly_detection <- function(df, freq=NULL, id_col=NULL, time_col="ds",
}

if(length(level) > 1){
message("Multiple levels are not allowed for anomaly detection. Will use the largest.")
message("Multiple levels are not allowed for anomaly detection. Will use the largest level.")
}
level <- as.list(level)
timegpt_data[["level"]] <- level
Expand All @@ -69,26 +67,21 @@ timegpt_anomaly_detection <- function(df, freq=NULL, id_col=NULL, time_col="ds",

# Extract anomalies ----
anomaly <- httr2::resp_body_json(resp_anomaly)
if(is.null(id_col)){
# Write here the code for the single series case once the url is available
res = 42
}else{
anomaly_list <- lapply(anomaly$data$forecast$data, unlist)
res <- data.frame(do.call(rbind, anomaly_list))
colnames(res) <- anomaly$data$forecast$columns
res[,3:ncol(res)] <- lapply(res[,3:ncol(res)], as.numeric)
}
anomaly_list <- lapply(anomaly$data$forecast$data, unlist)
res <- data.frame(do.call(rbind, anomaly_list))
colnames(res) <- anomaly$data$forecast$columns
res[,3:ncol(res)] <- lapply(res[,3:ncol(res)], as.numeric)

# Data transformation ----
if(tsibble::is_tsibble(df)){
res$ds <- switch(freq,
"Y" = as.numeric(substr(res$ds, 1, 4)),
"A" = as.numeric(substr(res$ds, 1, 4)),
"Q" = tsibble::yearquarter(res$ds),
"MS" = tsibble::yearmonth(res$ds),
"W" = tsibble::yearweek(res$ds),
"H" = lubridate::ymd_hms(res$ds),
lubridate::ymd(res$ds) # default (daily or other)
"Y" = as.numeric(substr(res$ds, 1, 4)),
"A" = as.numeric(substr(res$ds, 1, 4)),
"Q" = tsibble::yearquarter(res$ds),
"MS" = tsibble::yearmonth(res$ds),
"W" = tsibble::yearweek(res$ds),
"H" = lubridate::ymd_hms(res$ds),
lubridate::ymd(res$ds) # default (daily or other)
)
if(is.null(id_col)){
res <- tsibble::as_tsibble(res, index="ds")
Expand All @@ -108,6 +101,10 @@ timegpt_anomaly_detection <- function(df, freq=NULL, id_col=NULL, time_col="ds",
colnames(res)[which(colnames(res) == "ds")] <- time_col
if(!is.null(id_col)){
colnames(res)[which(colnames(res) == "unique_id")] <- id_col
}else{
# remove unique_id column
res <- res |>
dplyr::select(-unique_id)
}

return(res)
Expand Down
33 changes: 14 additions & 19 deletions R/timegpt_cross_validation.R
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,15 @@
#'
timegpt_cross_validation <- function(df, h=8, freq=NULL, id_col=NULL, time_col="ds", target_col="y", X_df=NULL, level=NULL, n_windows=1, step_size=NULL, finetune_steps=0, clean_ex_first=TRUE, model="timegpt-1"){

# Validation ----
if(!tsibble::is_tsibble(df) & !is.data.frame(df)){
stop("Only tsibbles or data frames are allowed.")
}

# Prepare data ----
url_cv <- "https://dashboard.nixtla.io/api/timegpt_multi_series_cross_validation"
if(is.null(id_col)){
url_cv <- "Write here the url for the single series case"
}else{
url_cv <- "https://dashboard.nixtla.io/api/timegpt_multi_series_cross_validation"
df <- df |>
dplyr::mutate(unique_id = "id") |>
dplyr::select(c("unique_id", tidyselect::everything()))
}

data <- .timegpt_data_prep(df, freq, id_col, time_col, target_col)
df <- data$df
freq <- data$freq
y <- data$y

Expand All @@ -53,6 +48,7 @@ timegpt_cross_validation <- function(df, h=8, freq=NULL, id_col=NULL, time_col="

if(!is.null(X_df)){
names(X_df)[which(names(X_df) == time_col)] <- "ds"
names(X_df)[which(names(X_df) == target_col)] <- "y"
if(!is.null(id_col)){
names(X_df)[which(names(X_df) == id_col)] <- "unique_id"
}
Expand Down Expand Up @@ -88,16 +84,11 @@ timegpt_cross_validation <- function(df, h=8, freq=NULL, id_col=NULL, time_col="

# Extract cross-validation ----
cv <- httr2::resp_body_json(resp_cv)
if(is.null(id_col)){
# Write here the code for the single series case once the url is available
res = 42
}else{
cv_list <- lapply(cv$data$forecast$data, unlist)
res <- data.frame(do.call(rbind, cv_list))
colnames(res) <- cv$data$forecast$columns
res[,4:ncol(res)] <- lapply(res[,4:ncol(res)], as.numeric)
res$cutoff <- lubridate::ymd_hms(res$cutoff)
}
cv_list <- lapply(cv$data$forecast$data, unlist)
res <- data.frame(do.call(rbind, cv_list))
colnames(res) <- cv$data$forecast$columns
res[,4:ncol(res)] <- lapply(res[,4:ncol(res)], as.numeric)
res$cutoff <- lubridate::ymd_hms(res$cutoff)

# Data transformation ----
if(tsibble::is_tsibble(df)){
Expand Down Expand Up @@ -139,6 +130,10 @@ timegpt_cross_validation <- function(df, h=8, freq=NULL, id_col=NULL, time_col="
colnames(res)[which(colnames(res) == "ds")] <- time_col
if(!is.null(id_col)){
colnames(res)[which(colnames(res) == "unique_id")] <- id_col
}else{
# remove unique_id column
res <- res |>
dplyr::select(-unique_id)
}

return(res)
Expand Down
15 changes: 4 additions & 11 deletions R/timegpt_data_prep.R
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,11 @@
}

# Prepare data
if("unique_id" %in% names(df)){
df <- df[,c("unique_id", "ds", "y")]
y <- list(
columns = names(df),
data = lapply(1:nrow(df), function(i) as.list(df[i,]))
df <- df[,c("unique_id", "ds", "y")]
y <- list(
columns = names(df),
data = lapply(1:nrow(df), function(i) as.list(df[i,]))
)
}else{
# only "ds" and "y" columns
y <- df$y
names(y) <- df$ds
y <- as.list(y)
}

res <- list(freq = freq,
y = y
Expand Down
44 changes: 16 additions & 28 deletions R/timegpt_forecast.R
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,14 @@
#'
timegpt_forecast <- function(df, h=8, freq=NULL, id_col=NULL, time_col="ds", target_col="y", X_df=NULL, level=NULL, finetune_steps=0, clean_ex_first=TRUE, add_history=FALSE, model="timegpt-1"){

# Validation ----
if(!tsibble::is_tsibble(df) & !is.data.frame(df)){
stop("Only tsibbles or data frames are allowed.")
}

# Prepare data ----
url <- "https://dashboard.nixtla.io/api/timegpt_multi_series"

if(is.null(id_col)){
url <- "https://dashboard.nixtla.io/api/timegpt"
}else{
url <- "https://dashboard.nixtla.io/api/timegpt_multi_series"
# create unique_id for single series
df <- df |>
dplyr::mutate(unique_id = "id") |>
dplyr::select(c("unique_id", tidyselect::everything()))
}

data <- .timegpt_data_prep(df, freq, id_col, time_col, target_col)
Expand Down Expand Up @@ -80,26 +78,13 @@ timegpt_forecast <- function(df, h=8, freq=NULL, id_col=NULL, time_col="ds", tar

# Extract forecast ----
fc <- httr2::resp_body_json(resp)

if(is.null(id_col)){
idx <- grep("^(timestamp|value|lo|hi)", names(fc$data))
fc_list <- fc$data[idx]
fcst <- data.frame(lapply(fc_list, unlist), stringsAsFactors=FALSE)
names(fcst) <- names(fc_list)
names(fcst)[1:2] <- c("ds", "TimeGPT")
if(!is.null(level)){
idx_level <- grep("^(lo|hi)", names(fcst))
names(fcst)[idx_level] <- paste0("TimeGPT-", names(fcst)[idx_level])
}
fc_list <- lapply(fc$data$forecast$data, unlist)
fcst <- data.frame(do.call(rbind, fc_list))
names(fcst) <- fc$data$forecast$columns
if(!is.null(level)){
fcst[,3:ncol(fcst)] <- lapply(fcst[,3:ncol(fcst)], as.numeric)
}else{
fc_list <- lapply(fc$data$forecast$data, unlist)
fcst <- data.frame(do.call(rbind, fc_list))
names(fcst) <- fc$data$forecast$columns
if(!is.null(level)){
fcst[,3:ncol(fcst)] <- lapply(fcst[,3:ncol(fcst)], as.numeric)
}else{
fcst$TimeGPT <- as.numeric(fcst$TimeGPT)
}
fcst$TimeGPT <- as.numeric(fcst$TimeGPT)
}

# Data transformation ----
Expand All @@ -119,7 +104,6 @@ timegpt_forecast <- function(df, h=8, freq=NULL, id_col=NULL, time_col="ds", tar
fcst <- tsibble::as_tsibble(fcst, key="unique_id", index="ds")
}
}else{
# If df is a data frame, convert ds to dates
if(freq == "H"){
fcst$ds <- lubridate::ymd_hms(fcst$ds)
}else{
Expand All @@ -131,6 +115,10 @@ timegpt_forecast <- function(df, h=8, freq=NULL, id_col=NULL, time_col="ds", tar
names(fcst)[which(names(fcst) == "ds")] <- time_col
if(!is.null(id_col)){
names(fcst)[which(names(fcst) == "unique_id")] <- id_col
}else{
# remove unique_id column
fcst <- fcst |>
dplyr::select(-unique_id)
}

# Generate fitted values ----
Expand Down
Loading

0 comments on commit 85d8c84

Please sign in to comment.