Skip to content

Commit

Permalink
Merge pull request #7 from inesortega/v1.1.1
Browse files Browse the repository at this point in the history
V1.1.0 - Updates after CRAN submission
  • Loading branch information
inesortega authored Sep 20, 2023
2 parents 0d73069 + 8aaac45 commit b252bd1
Show file tree
Hide file tree
Showing 15 changed files with 520 additions and 18 deletions.
1 change: 0 additions & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ importFrom(reticulate,conda_binary)
importFrom(reticulate,conda_create)
importFrom(reticulate,conda_list)
importFrom(reticulate,install_miniconda)
importFrom(reticulate,py_available)
importFrom(reticulate,py_config)
importFrom(reticulate,py_module_available)
importFrom(reticulate,use_condaenv)
Expand Down
1 change: 0 additions & 1 deletion R/NeuralGAM.R
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
#' @importFrom keras compile
#' @importFrom tensorflow set_random_seed
#' @importFrom stats predict lm
#' @importFrom reticulate py_available
#' @importFrom magrittr %>%
#' @importFrom formula.tools lhs rhs
#' @export
Expand Down
2 changes: 1 addition & 1 deletion R/build_feature_NN.R
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ build_feature_NN <-
stop("Argument \"num_units\" is missing, with no default")
}

if (!is.numeric(num_units) & !(is.vector(num_units))) {
if (!is.numeric(num_units)) {
stop("Argument \"num_units\" must be an integer or a vector of integers")
}

Expand Down
5 changes: 3 additions & 2 deletions R/install.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
#' Python requirements to run neuralGAM (Tensorflow and Keras).
#'
#' Miniconda and related environments are generated in the user's cache directory
#' given by tools::R_user_dir('neuralGAM', 'cache').
#' @return NULL
#' given by tools::R_user_dir('neuralGAM', 'cache') if no previous installation of
#' miniconda is found on the system.
#' @return No return value.
#' @export
#' @importFrom reticulate py_module_available conda_binary install_miniconda py_config use_condaenv conda_list conda_create
#' @importFrom tensorflow install_tensorflow
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ install_neuralGAM()

## Sample usage

In the following example, we use synthetic data to showcase the performance of neuralGAM by fitting a model with a single layer with 1024 units.
In the following example, we use synthetic data to showcase the performance of neuralGAM by fitting a model with a single layer with 1024 units. The complete documentation is available on the [project's webpage]([url](https://inesortega.github.io/neuralGAM/))

```
n <- 24500
Expand Down
2 changes: 2 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ sudo: false
cache: packages
after_success:
- Rscript -e 'covr::codecov()'
ignore:
- "R/install.R"
33 changes: 32 additions & 1 deletion cran-comments.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,38 @@ This version fixes a policy violation regarding writing the package Python depen

In this version, the package is loaded without performing the Python dependencies installation, warning the user using a package start-up message that required dependencies are not found. To assist the user with Python dependencies installation, we have included a new function `install_neuralGAM()` which helps the user set up a working Python environment using `miniconda`.

We have included a instruction on the tests to skip them on CRAN since a working Python installation with the required dependencies is not guaranteed on CRAN machines.
We have included a instruction on the tests and examples to skip them on CRAN since a working Python installation with the required dependencies is not guaranteed on CRAN machines. Tests have been included for all the functions of the library.

## CRAN comments after initial submission

> Please add \value to .Rd files regarding exported methods and explain the functions results in the documentation. Please write about the structure of the output (class) and also what the output means. (If a function does not return a value, please document that too, e.g. \value{No return value, called for side effects} or similar)
>Missing Rd-tags:
> install_neuralGAM.Rd: \value
Added return value to install_neuralGAM.Rd

>\dontrun{} should only be used if the example really cannot be executed (e.g. because of missing additional software, missing API keys, ...) by the user. That's why wrapping examples in \dontrun{} adds the comment ("# Not run:") as a warning for the user. Does not seem necessary. Please replace \dontrun with \donttest.
\dontrun is needed since the library needs additional software (Python dependencies which can be installed using `install_neuralGAM()`), following a similar strategy as other R packages with Python dependencies such as (Keras)[https://github.com/rstudio/keras] and (Tensorflow)[https://github.com/rstudio/tensorflow].

> Please unwrap the examples if they are executable in < 5 sec, or replace dontrun{} with \donttest{}.
As in the previous case, examples cannot be unwrapped since additional software is needed to run the tests.

## Local test execution results

ℹ Testing neuralGAM
✔ | F W S OK | Context
✔ | 7 | build_feature_NN [14.4s]
✔ | 6 | dev
✔ | 6 | diriv
✔ | 35 | formula
✔ | 5 | inv_link
✔ | 7 | link
✔ | 11 | NeuralGAM [6.9s]
✔ | 7 | weight

[ FAIL 0 | WARN 0 | SKIP 0 | PASS 84 ]

## Local R CMD check results

Expand Down
6 changes: 5 additions & 1 deletion man/install_neuralGAM.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 40 additions & 10 deletions tests/testthat/test-NeuralGAM.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ skip_if_no_keras <- function() {
) skip("keras not available for testing...")
}

# Test if function throws error for missing smooth terms
test_that("neuralGAM throws an error for missing smooth terms", {
skip_if_no_keras()

Expand All @@ -22,7 +21,6 @@ test_that("neuralGAM throws an error for missing smooth terms", {
num_units = 10))
})

# Test if function throws error for non-numeric num_units
test_that("neuralGAM throws an error for non-numeric num_units", {
skip_if_no_keras()

Expand All @@ -34,7 +32,6 @@ test_that("neuralGAM throws an error for non-numeric num_units", {
num_units = "abc"))
})

# Test if function throws error for num_units < 1
test_that("neuralGAM throws an error for num_units < 1", {
skip_if_no_keras()

Expand All @@ -46,7 +43,6 @@ test_that("neuralGAM throws an error for num_units < 1", {
num_units = 0))
})

# Test if function throws error for non-numeric learning_rate
test_that("neuralGAM throws an error for non-numeric learning_rate", {
skip_if_no_keras()

Expand All @@ -60,7 +56,6 @@ test_that("neuralGAM throws an error for non-numeric learning_rate", {
))
})

# Test if function throws error for invalid family
test_that("neuralGAM throws an error for invalid family", {
skip_if_no_keras()

Expand All @@ -73,7 +68,6 @@ test_that("neuralGAM throws an error for invalid family", {
family = "abc"))
})

# Test if function throws error for invalid loss
test_that("neuralGAM throws an error for invalid loss", {
skip_if_no_keras()

Expand All @@ -82,7 +76,6 @@ test_that("neuralGAM throws an error for invalid loss", {
expect_error(neuralGAM(formula, data, num_units = 10, loss = -1))
})

# Test if function throws error for invalid kernel initializer
test_that("neuralGAM throws an error for invalid kernel_initializer", {
skip_if_no_keras()

Expand All @@ -96,7 +89,6 @@ test_that("neuralGAM throws an error for invalid kernel_initializer", {
))
})

# Test if function throws error for invalid bias initializer
test_that("neuralGAM throws an error for invalid bias_initializer", {
skip_if_no_keras()

Expand All @@ -110,8 +102,7 @@ test_that("neuralGAM throws an error for invalid bias_initializer", {
))
})

# Test if function runs OK main example
test_that("neuralGAM runs OK", {
test_that("neuralGAM runs OK with single hidden layer", {
skip_if_no_keras()

formula <- y ~ s(x)
Expand All @@ -123,3 +114,42 @@ test_that("neuralGAM runs OK", {
expect_equal(round(ngam$mse,4), 0.5655)
})

test_that("neuralGAM runs OK with deep architecture", {
skip_if_no_keras()

formula <- y ~ s(x)
seed <- 10
set.seed(seed)
data <- data.frame(x = 1:10, y = rnorm(10))

ngam <- neuralGAM(formula,
data,
num_units = c(10,10),
seed = seed,
max_iter_backfitting = 1,
max_iter_ls = 1)
expect_equal(round(ngam$mse,4), 0.5207)
})


test_that("neuralGAM runs OK with binomial response", {
skip_if_no_keras()

n <- 10
formula <- y ~ s(x)
seed <- 10
set.seed(seed)
eta0 <- rnorm(n)
true_eta <- exp(eta0)/(1 + exp(eta0)) # generate probs

data <- data.frame(x = 1:10, y = rbinom(n, 1, true_eta))

ngam <- neuralGAM(formula,
data,
num_units = 10,
seed = seed,
family = "binomial")

expect_equal(round(ngam$mse,4), 0.221)
})

55 changes: 55 additions & 0 deletions tests/testthat/test-build_feature_NN.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
library(testthat)
library(reticulate)

skip_if_no_keras <- function() {

if (!tryCatch(
reticulate::py_module_available("keras"),
error = function(e) return(FALSE)
)
) skip("keras not available for testing...")
}

# Test case 1: function throws error for missing num_units
test_that("build_feature_NN throws an error for missing num_units", {
skip_if_no_keras()

# Missing num_units
expect_error(neuralGAM:::build_feature_NN())
})

# num_units is not numeric or vector of integers
test_that("Invalid num_units argument", {
skip_if_no_keras()
expect_error(neuralGAM:::build_feature_NN(num_units = "string"))
})

# num_units is not numeric or vector of integers
test_that("Invalid num_units argument", {
skip_if_no_keras()
expect_error(neuralGAM:::build_feature_NN(num_units = c("string", "string2")))
})

# learning_rate is not numeric
test_that("Invalid learning_rate argument", {
skip_if_no_keras()
expect_error(neuralGAM:::build_feature_NN(num_units = 10, learning_rate = "string"))
})

# name is not NULL or character string
test_that("Invalid name argument", {
skip_if_no_keras()
expect_error(neuralGAM:::build_feature_NN(num_units = 10, name = 123))
})

# Valid function call
test_that("Valid function call", {
skip_if_no_keras()
testthat::expect_no_error(neuralGAM:::build_feature_NN(num_units = 10, learning_rate = 0.001, name = "test"))
})

# Valid function call
test_that("Valid function call with deep layer", {
skip_if_no_keras()
testthat::expect_no_error(neuralGAM:::build_feature_NN(num_units = c(10,20), learning_rate = 0.001, name = "test"))
})
86 changes: 86 additions & 0 deletions tests/testthat/test-dev.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
library(testthat)
library(reticulate)

skip_if_no_keras <- function() {

if (!tryCatch(
reticulate::py_module_available("keras"),
error = function(e) return(FALSE)
)
) skip("keras not available for testing...")
}

# Test case 1: Check the neuralGAM:::deviance for gaussian family
test_that("neuralGAM:::deviance for gaussian family should be correctly calculated", {
skip_if_no_keras()

family <- "gaussian"
muhat <- c(0.1, 0.5, 0.9)
y <- c(0.2, 0.6, 0.8)
expected_output <- mean((y - muhat)^2)
actual_output <- neuralGAM:::dev(muhat, y, family)
expect_equal(actual_output, expected_output)
})

# Test case 2: Check the neuralGAM:::deviance for binomial family
test_that("neuralGAM:::deviance for binomial family should be correctly calculated", {
skip_if_no_keras()

family <- "binomial"
muhat <- c(0.2, 0.7, 0.99)
y <- c(0, 1, 1)

muhat[muhat < 0.0001] <- 0.0001
muhat[muhat > 0.9999] <- 0.9999

entrop <- rep(0, length(y))
ii <- (1 - y) * y > 0
if (sum(ii, na.rm = TRUE) > 0) {
entrop[ii] <- 2 * (y[ii] * log(y[ii])) +
((1 - y[ii]) * log(1 - y[ii]))
} else {
entrop <- 0
}
entadd <- 2 * (y * log(muhat)) + ((1 - y) * log(1 - muhat))
expected_output <- sum(entrop - entadd, na.rm = TRUE)

actual_output <- neuralGAM:::dev(muhat, y, family)
expect_equal(actual_output, expected_output)
})

# Test case 3: Check for missing 'muhat' argument
test_that("Function should throw an error for missing 'muhat' argument", {
skip_if_no_keras()

family <- "gaussian"
y <- c(0.2, 0.6, 0.8)
expect_error(neuralGAM:::dev(y = y, family = family))
})

# Test case 4: Check for missing 'y' argument
test_that("Function should throw an error for missing 'y' argument", {
skip_if_no_keras()

family <- "gaussian"
muhat <- c(0.1, 0.5, 0.9)
expect_error(neuralGAM:::dev(muhat = muhat, family = family))
})

# Test case 5: Check for missing 'family' argument
test_that("Function should throw an error for missing 'family' argument", {
skip_if_no_keras()

muhat <- c(0.1, 0.5, 0.9)
y <- c(0.2, 0.6, 0.8)
expect_error(neuralGAM:::dev(muhat = muhat, y = y))
})

# Test case 6: Check for unsupported family
test_that("Function should throw an error for unsupported 'family'", {
skip_if_no_keras()

family <- "poisson"
muhat <- c(0.1, 0.5, 0.9)
y <- c(0.2, 0.6, 0.8)
expect_error(neuralGAM:::dev(muhat, y, family))
})
Loading

0 comments on commit b252bd1

Please sign in to comment.