Skip to content

Commit 2ee3692

Browse files
author
OVVO-Financial
committed
NNS 11.0 Beta
1 parent 6496c18 commit 2ee3692

14 files changed

+184
-43
lines changed

DESCRIPTION

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
Package: NNS
22
Type: Package
33
Title: Nonlinear Nonparametric Statistics
4-
Version: 10.9.7
5-
Date: 2024-12-26
4+
Version: 11.0
5+
Date: 2025-01-03
66
Authors@R: c(
77
person("Fred", "Viole", role=c("aut","cre"), email="[email protected]"),
88
person("Roberto", "Spadim", role=c("ctb"))

NNS_10.9.7.tar.gz

-1.17 MB
Binary file not shown.

NNS_10.9.7.zip

-846 KB
Binary file not shown.

NNS_11.0.tar.gz

1.18 MB
Binary file not shown.

NNS_11.0.zip

865 KB
Binary file not shown.

R/ARMA.R

+12-12
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,13 @@ NNS.ARMA <- function(variable,
6767
seasonal.plot = TRUE,
6868
pred.int = NULL){
6969

70-
70+
7171
if(is.numeric(seasonal.factor) && dynamic) stop('Hmmm...Seems you have "seasonal.factor" specified and "dynamic = TRUE". Nothing dynamic about static seasonal factors! Please set "dynamic = FALSE" or "seasonal.factor = FALSE"')
72-
72+
7373
if(any(class(variable)%in%c("tbl","data.table"))) variable <- as.vector(unlist(variable))
7474

7575
if(sum(is.na(variable)) > 0) stop("You have some missing values, please address.")
76-
76+
7777
method <- tolower(method)
7878
if(method == "means") shrink <- FALSE
7979

@@ -99,7 +99,7 @@ NNS.ARMA <- function(variable,
9999

100100
Estimates <- numeric(length = h)
101101

102-
102+
103103
if(is.numeric(seasonal.factor)){
104104
seasonal.plot = FALSE
105105
M <- matrix(seasonal.factor, ncol=1)
@@ -147,9 +147,9 @@ NNS.ARMA <- function(variable,
147147
if(is.character(weights)) Weights <- rep(1/length(lag), length(lag))
148148

149149
}
150-
150+
151151
lin.resid <- list()
152-
152+
153153
# Regression for each estimate in h
154154
for (j in 1:h) {
155155
# Regenerate seasonal.factor if dynamic
@@ -218,7 +218,7 @@ NNS.ARMA <- function(variable,
218218
Lin.Regression.Estimates <- unlist(Lin.Regression.Estimates)
219219

220220
if (method != "means") lin.resid <- mean(abs(Lin.Regression.Estimates - mean(Lin.Regression.Estimates)))
221-
221+
222222
if (method %in% c("means", "shrink")) {
223223
Regression.Estimates_means <- sapply(Component.series, mean)
224224
if (shrink) Lin.Regression.Estimates <- (Lin.Regression.Estimates + Regression.Estimates_means) / 2 else Lin.Regression.Estimates <- Regression.Estimates_means
@@ -231,15 +231,15 @@ NNS.ARMA <- function(variable,
231231
if (method == "lin") Estimates[j] <- sum(Lin.estimates * Weights)
232232

233233
if (method == 'both') Estimates[j] <- mean(c(Lin.estimates, Nonlin.estimates))
234-
234+
235235
if (method == "nonlin") Estimates[j] <- sum(Nonlin.estimates * Weights)
236-
236+
237237

238238
variable <- c(variable, Estimates[j])
239239
FV <- variable
240240
} # j loop
241-
242-
241+
242+
243243
if(!is.null(pred.int)){
244244
PIs <- do.call(cbind, NNS.MC(Estimates, lower_rho = 0, upper_rho = 1, by = .2, exp = 2)$replicates)
245245
lin.resid <- mean(unlist(lin.resid))
@@ -283,7 +283,7 @@ NNS.ARMA <- function(variable,
283283
col = rgb(1, 192/255, 203/255, alpha = 0.5),
284284
border = NA)
285285

286-
286+
287287
lines(OV, type = 'l', lwd = 2, col = 'steelblue')
288288

289289
lines((training.set + 1) : (training.set + h), Estimates, type = 'l', lwd = 2, lty = 1, col = 'red')

R/ARMA_optim.R

+89-16
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#' @param linear.approximation logical; \code{TRUE} (default) Uses the best linear output from \code{NNS.reg} to generate a nonlinear and mixture regression for comparison. \code{FALSE} is a more exhaustive search over the objective space.
1515
#' @param pred.int numeric [0, 1]; 0.95 (default) Returns the associated prediction intervals for the final estimate. Constructed using the maximum entropy bootstrap \link{NNS.meboot} on the final estimates.
1616
#' @param print.trace logical; \code{TRUE} (default) Prints current iteration information. Suggested as backup in case of error, best parameters to that point still known and copyable!
17+
#' @param ncores integer; value specifying the number of cores to be used in the parallelized procedure. If NULL (default), the number of cores to be used is equal to the number of cores of the machine - 1.
1718
#' @param plot logical; \code{FALSE} (default)
1819
#'
1920
#' @return Returns a list containing:
@@ -36,6 +37,7 @@
3637
#'
3738
#' \item{} The number of combinations will grow prohibitively large, they should be kept as small as possible. \code{seasonal.factor} containing an element too large will result in an error. Please reduce the maximum \code{seasonal.factor}.
3839
#'
40+
#' \item{} Set \code{(ncores = 1)} if routine is used within a parallel architecture.
3941
#'}
4042
#'
4143
#'
@@ -68,6 +70,7 @@ NNS.ARMA.optim <- function(variable,
6870
obj.fn = expression( mean((predicted - actual)^2) / (NNS::Co.LPM(1, predicted, actual, target_x = mean(predicted), target_y = mean(actual)) + NNS::Co.UPM(1, predicted, actual, target_x = mean(predicted), target_y = mean(actual)) ) ),
6971
objective = "min",
7072
linear.approximation = TRUE,
73+
ncores = NULL,
7174
pred.int = 0.95,
7275
print.trace = TRUE,
7376
plot = FALSE){
@@ -123,6 +126,28 @@ NNS.ARMA.optim <- function(variable,
123126
seasonal.combs <- current.seasonals <- vector(mode = "list")
124127
current.estimate <- numeric()
125128

129+
if (j == "lin") {
130+
# Determine the number of cores to use
131+
num_cores <- if (is.null(ncores)) {
132+
max(2L, parallel::detectCores() - 1L)
133+
} else {
134+
ncores
135+
}
136+
137+
# Manage cluster creation
138+
cl <- NULL
139+
if (num_cores > 1) {
140+
cl <- tryCatch(
141+
parallel::makeForkCluster(num_cores),
142+
error = function(e) parallel::makeCluster(num_cores)
143+
)
144+
doParallel::registerDoParallel(cl)
145+
invisible(data.table::setDTthreads(1)) # Restrict threading for parallelization
146+
} else {
147+
foreach::registerDoSEQ()
148+
invisible(data.table::setDTthreads(0)) # Default threading
149+
}
150+
}
126151

127152
for(i in 1 : length(seasonal.factor)){
128153
if(i == 1){
@@ -150,15 +175,53 @@ NNS.ARMA.optim <- function(variable,
150175

151176
if(is.null(ncol(seasonal.combs[[i]])) || dim(seasonal.combs[[i]])[2]==0) break
152177

153-
if(j=="lin"){
154-
155-
nns.estimates.indiv <- lapply(1 : ncol(seasonal.combs[[i]]), function(k) {
156-
actual <- tail(variable, h_eval)
157-
if(print.trace) message("Testing seasonal.factor ", paste(unlist(seasonal.combs[[i]][ , k]), ","), "\r", appendLF = FALSE)
158-
predicted <- NNS.ARMA(variable, training.set = training.set, h = h_eval, seasonal.factor = seasonal.combs[[i]][ , k], method = "lin", plot = FALSE, negative.values = negative.values)
159-
160-
return(eval(obj.fn))
161-
})
178+
# if(j=="lin"){
179+
#
180+
# nns.estimates.indiv <- lapply(1 : ncol(seasonal.combs[[i]]), function(k) {
181+
# actual <- tail(variable, h_eval)
182+
# if(print.trace) message("Testing seasonal.factor ", paste(unlist(seasonal.combs[[i]][ , k]), ","), "\r", appendLF = FALSE)
183+
# predicted <- NNS.ARMA(variable, training.set = training.set, h = h_eval, seasonal.factor = seasonal.combs[[i]][ , k], method = "lin", plot = FALSE, negative.values = negative.values)
184+
#
185+
# return(eval(obj.fn))
186+
# })
187+
# }
188+
if (j == "lin") {
189+
# Parallel or sequential computation based on num_cores
190+
nns.estimates.indiv <- if (num_cores > 1) {
191+
parallel::clusterExport(
192+
cl,
193+
varlist = c("variable", "h_eval", "training.set", "seasonal.combs", "i", "obj.fn", "negative.values", "NNS.ARMA", "print.trace"),
194+
envir = environment()
195+
)
196+
parallel::parLapply(cl, 1:ncol(seasonal.combs[[i]]), function(k) {
197+
actual <- tail(variable, h_eval)
198+
predicted <- NNS.ARMA(
199+
variable,
200+
training.set = training.set,
201+
h = h_eval,
202+
seasonal.factor = seasonal.combs[[i]][, k],
203+
method = "lin",
204+
plot = FALSE
205+
)
206+
eval(obj.fn)
207+
})
208+
} else {
209+
lapply(1:ncol(seasonal.combs[[i]]), function(k) {
210+
actual <- tail(variable, h_eval)
211+
predicted <- NNS.ARMA(
212+
variable,
213+
training.set = training.set,
214+
h = h_eval,
215+
seasonal.factor = seasonal.combs[[i]][, k],
216+
method = "lin",
217+
plot = FALSE
218+
)
219+
eval(obj.fn)
220+
})
221+
}
222+
223+
# Ensure output is unlisted
224+
nns.estimates.indiv <- unlist(nns.estimates.indiv)
162225
}
163226

164227
if(j=="nonlin" && linear.approximation){
@@ -174,14 +237,14 @@ NNS.ARMA.optim <- function(variable,
174237
if(j=="both" && linear.approximation){
175238
# Find the min (obj.fn) for a given seasonals sequence
176239
actual <- tail(variable, h_eval)
177-
240+
178241
lin.predicted <- NNS.ARMA(variable, training.set = training.set, h = h_eval, seasonal.factor = unlist(overall.seasonals[[1]]), method = "lin", plot = FALSE, negative.values = negative.values)
179242
predicted <- both.predicted <- (lin.predicted + nonlin.predicted) / 2
180-
243+
181244
nns.estimates.indiv <- eval(obj.fn)
182245
}
183246

184-
247+
185248
nns.estimates.indiv <- unlist(nns.estimates.indiv)
186249

187250
if(objective=='min') nns.estimates.indiv[is.na(nns.estimates.indiv)] <- Inf else nns.estimates.indiv[is.na(nns.estimates.indiv)] <- -Inf
@@ -235,6 +298,16 @@ NNS.ARMA.optim <- function(variable,
235298

236299
} # for i in 1:length(seasonal factor)
237300

301+
if (j == "lin") {
302+
# Clean up cluster
303+
if (!is.null(cl)) {
304+
parallel::stopCluster(cl)
305+
doParallel::stopImplicitCluster()
306+
invisible(data.table::setDTthreads(0)) # Restore threading
307+
invisible(gc(verbose = FALSE)) # Clean up memory
308+
}
309+
}
310+
238311
previous.seasonals[[which(c("lin",'nonlin','both')==j)]] <- current.seasonals
239312
previous.estimates[[which(c("lin",'nonlin','both')==j)]] <- current.estimate
240313

@@ -287,7 +360,7 @@ NNS.ARMA.optim <- function(variable,
287360
}
288361
} else {
289362
nns.weights <- NULL
290-
363+
291364
errors <- predicted - actual
292365
bias <- gravity(na.omit(errors))
293366
if(is.na(bias)) bias <- 0
@@ -401,7 +474,7 @@ NNS.ARMA.optim <- function(variable,
401474
model.results <- NNS.ARMA(OV, h = h_oos, seasonal.factor = nns.periods, method = nns.method, plot = FALSE, negative.values = negative.values, weights = nns.weights, shrink = nns.shrink) - bias
402475
}
403476

404-
477+
405478
lower_PIs <- model.results - abs(LPM.VaR((1-pred.int)/2, 0, errors)) - abs(bias)
406479
upper_PIs <- model.results + abs(UPM.VaR((1-pred.int)/2, 0, errors)) + abs(bias)
407480

@@ -411,7 +484,7 @@ NNS.ARMA.optim <- function(variable,
411484
lower_PIs <- pmax(0, lower_PIs)
412485
upper_PIs <- pmax(0, upper_PIs)
413486
}
414-
487+
415488
if(plot){
416489
if(is.null(h_oos)) xlim <- c(1, max((training.set + h))) else xlim <- c(1, max((n + h)))
417490

@@ -449,7 +522,7 @@ NNS.ARMA.optim <- function(variable,
449522
legend("topleft", legend = c("Variable", "Internal Validation", "Forecast"),
450523
col = c("steelblue", "red", "red"), lty = c(1, 2, 1), bty = "n", lwd = 2)
451524
}
452-
525+
453526

454527

455528

R/Internal_Functions.R

+72-9
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,82 @@ factor_2_dummy_FR <- function(x){
3535
}
3636

3737
### Generator for 1:length(lag) vectors in NNS.ARMA
38-
generate.vectors <- function(x, l){
39-
Component.index <- Component.series <- list()
40-
41-
for (i in 1:length(l)){
42-
CS <- rev(x[seq(length(x)+1, 1, -l[i])])
43-
CS <- CS[!is.na(CS)]
44-
Component.series[[paste('Series.', i, sep = "")]] <- CS
45-
Component.index[[paste('Index.', i, sep = "")]] <- (1 : length(CS))
46-
}
38+
generate.vectors <- function(x, l) {
39+
Component.series <- lapply(l, function(lag) {
40+
rev.series <- rev(x[seq(length(x) + 1, 1, -lag)])
41+
rev.series[!is.na(rev.series)]
42+
})
43+
44+
Component.index <- lapply(Component.series, seq_along)
45+
4746
return(list(Component.index = Component.index, Component.series = Component.series))
4847
}
4948

5049

50+
generate.lin.vectors <- function(x, l, h = 1) {
51+
original.index <- seq_along(x)
52+
augmented.index <- c(original.index, tail(original.index,1) + (1:h))
53+
max_fcast <- min(h, l)
54+
# Generate lagged components by applying lag across the augmented index
55+
Component.series <- lapply(1:max_fcast, function(i) {
56+
start.index <- length(x) + i # Shift by 1 each time
57+
rev.series <- rev(x[seq(start.index, 1, -l)]) # Reverse the sequence with a lag of 12
58+
rev.series[!is.na(rev.series)] # Remove any NA values
59+
})
60+
61+
Component.index <- lapply(Component.series, seq_along)
62+
63+
# Initialize forecast.index and forecast.values
64+
forecast.index <- vector("list", length(l))
65+
forecast.values <- vector("list", length(l))
66+
67+
# Generate forecast.index for 1:h
68+
max_fcast <- min(h, l)
69+
forecast.index <- create_recycled_list(1:h, max_fcast)
70+
forecast.index <- forecast.index[!sapply(forecast.index, is.null)]
71+
72+
73+
# Generate forecast values based on the last value in Component.index
74+
forecast.values.raw <- lapply(1:h, function(i) {
75+
# Recycle the index if h > l
76+
recycled_index <- (i - 1) %% l + 1 # Cycle through 1 to l
77+
78+
# Get the last value from the corresponding Component.index
79+
last_value <- tail(Component.index[[recycled_index]], 1)
80+
81+
# Calculate the forecast increment
82+
forecast_increment <- ceiling(i / l)
83+
84+
# Generate the forecast value
85+
forecast_value <- last_value + forecast_increment
86+
return(forecast_value)
87+
})
88+
89+
forecast.values <- create_recycled_list(unlist(forecast.values.raw), l)
90+
forecast.values <- forecast.values[!sapply(forecast.values, is.null)]
91+
92+
return(list(
93+
Component.index = Component.index,
94+
Component.series = Component.series,
95+
forecast.values = forecast.values,
96+
forecast.index = forecast.index
97+
))
98+
}
99+
100+
create_recycled_list <- function(values, list_length) {
101+
# Initialize an empty list to store the values
102+
result <- vector("list", list_length)
103+
104+
# Recycle the values by repeating them across the list length
105+
for (i in seq_along(values)) {
106+
index <- (i - 1) %% list_length + 1
107+
result[[index]] <- c(result[[index]], values[i])
108+
}
109+
110+
return(result)
111+
}
112+
113+
51114
### Weight and lag function for seasonality in NNS.ARMA
52115
ARMA.seas.weighting <- function(sf,mat){
53116
M <- mat

R/NNS_VAR.R

+1
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ NNS.VAR <- function(variables,
201201
obj.fn = obj.fn,
202202
objective = objective,
203203
print.trace = FALSE,
204+
ncores = 1,
204205
negative.values = min(variable_interpolation)<0, h = h)
205206

206207
variable_extrapolation <- b$results

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44

55

6-
[![packageversion](https://img.shields.io/badge/NNS%20version-10.9.7-blue.svg?style=flat-square)](https://github.com/OVVO-Financial/NNS/commits/NNS-Beta-Version) [![Licence](https://img.shields.io/badge/licence-GPL--3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html)
6+
[![packageversion](https://img.shields.io/badge/NNS%20version-11.0-blue.svg?style=flat-square)](https://github.com/OVVO-Financial/NNS/commits/NNS-Beta-Version) [![Licence](https://img.shields.io/badge/licence-GPL--3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html)
77

88
<h2 style="margin: 0; padding: 0; border: none; height: 40px;"></h2>
99

@@ -56,7 +56,7 @@ Please see https://github.com/OVVO-Financial/NNS/blob/NNS-Beta-Version/examples/
5656
title = {NNS: Nonlinear Nonparametric Statistics},
5757
author = {Fred Viole},
5858
year = {2016},
59-
note = {R package version 10.9.7},
59+
note = {R package version 11.0},
6060
url = {https://CRAN.R-project.org/package=NNS},
6161
}
6262
```

0 commit comments

Comments
 (0)