tests/testthat/test-shapeConstr.R

################################################################################
#
# Test that the shapeConstr method works
#
################################################################################

# Libraries containing splines
library(splines)
suppressMessages(library(dlnm))

#----- Generate data ------

# Parameters
n <- 1000
nsim <- 5

# Generate a simple parabolic relationship
#set.seed(1)
X <- seq(0, 1, length.out = n)
# eta <- log(10) + (x-.3)^2
eta <- log(10) - .5 * sin(X * 1.6 * pi)

# Generate several
set.seed(5)
Y <- replicate(nsim, rpois(n, exp(eta)))

#-------------------------
# Tests
#-------------------------

# Parameters
tested_bases <- c("ps", "bs", "ns")
p <- 10

#----- Monotone increasing

for (b in tested_bases){

  # Basis
  basis <- do.call(b, list(x = X, df = p))

  # Generate constraint matrix
  Cmat <- shapeConstr(basis, shape = "inc")

  # Fit model
  res <- apply(Y, 2, function(y) glm(y ~ basis, family = "quasipoisson",
    method = "cirls.fit", Cmat = list(basis = Cmat)) |>
      predict())

  # Test results
  test_that(paste0("Monotone increasing constraints work with ", b), {
    expect_true(all(diff(res) >= -sqrt(.Machine$double.eps)))
  })
  matplot(X, res, type = "l", col = "grey", main = paste0("Increasing: ", b))
  lines(X, eta, lwd = 2)
}

#----- Monotone decreasing

for (b in tested_bases){

  # Basis
  basis <- do.call(b, list(x = X, df = p))

  # Generate constraint matrix
  Cmat <- shapeConstr(basis, shape = "dec")

  # Fit model
  res <- apply(Y, 2, function(y) glm(y ~ basis, family = "quasipoisson",
    method = "cirls.fit", Cmat = list(basis = Cmat)) |>
      predict())

  # Test results
  test_that(paste0("Monotone decreasing constraints work with ", b), {
    expect_true(all(diff(res) <= sqrt(.Machine$double.eps)))
  })
  matplot(X, res, type = "l", col = "grey", main = paste0("Decreasing: ", b))
  lines(X, eta, lwd = 2)
}


#----- Convex

for (b in tested_bases){

  # Basis
  basis <- do.call(b, list(x = X, df = p))

  # Generate constraint matrix
  Cmat <- shapeConstr(basis, shape = "cvx")

  # Fit model
  res <- apply(Y, 2, function(y) glm(y ~ basis, family = "quasipoisson",
    method = "cirls.fit", Cmat = list(basis = Cmat)) |>
      predict())

  # Test results
  test_that(paste0("Convex constraints work with ", b), {
    expect_true(all(diff(res, diff = 2) >= -sqrt(.Machine$double.eps)))
  })
  matplot(X, res, type = "l", col = "grey", main = paste0("Convex: ", b))
  lines(X, eta, lwd = 2)
}


#----- Concave

for (b in tested_bases){

  # Basis
  basis <- do.call(b, list(x = X, df = p))

  # Generate constraint matrix
  Cmat <- shapeConstr(basis, shape = "ccv")

  # Fit model
  res <- apply(Y, 2, function(y) glm(y ~ basis, family = "quasipoisson",
    method = "cirls.fit", Cmat = list(basis = Cmat)) |>
      predict())

  # Test results
  test_that(paste0("Concave constraints work with ", b), {
    expect_true(all(diff(res, diff = 2) <= sqrt(.Machine$double.eps)))
  })
  matplot(X, res, type = "l", col = "grey", main = paste0("Concave: ", b))
  lines(X, eta, lwd = 2)
}

#-------------------------
# Test the method for onebasis
#-------------------------

# Test that we get the right constraint matrix
test_that("We get the right constraint matrix with `onebasis`", {
for (b in tested_bases){

  # Basis
  basis1 <- onebasis(X, fun = b, df = p)
  basis2 <- do.call(b, list(x = X, df = p))

  # Generate constraint matrix
  Cmat1 <- shapeConstr(basis1, shape = "inc")
  Cmat2 <- shapeConstr(basis2, shape = "inc")

  # Test equality
  expect_true(all.equal(Cmat1, Cmat2))
}})

# Test for basis with no method
test_that("We get the default method for unknown `onebasis`", {
  strbasis <- onebasis(X, fun = "strata", df = p)
  expect_warning(Cmat <- shapeConstr(strbasis, shape = "pos"))
  expect_identical(Cmat, diag(p), ignore_attr = TRUE)
})


#-------------------------
# Antonio's test
#-------------------------

# Generate data
x1 <- 1:100
mu <- x1/100 - (x1/100)^2 - 0.3*(x1/100)^3
set.seed(13041975)
y <- mu + rnorm(100,0,0.1)

# Spline bases
ks <- 1:4*20
X <- ns(x1, knots = ks)

# Fit
Cmat <- shapeConstr(X, shape = c("dec", "ccv"))
m <- glm(y ~ X, method = cirls.fit, Cmat = list(X=Cmat))
pred <- predict(m)

# Formal test
test_that("More challengin test for ns", {
  expect_true(all(diff(pred, diff = 2) <= sqrt(.Machine$double.eps)))
  expect_true(all(diff(pred) <= sqrt(.Machine$double.eps)))
})

# Plot
plot(x1,y)
lines(x1,mu,type="l",col=1,lwd=2)
lines(x1,pred,lwd=2, col=3)

#-------------------------
# Factor
#-------------------------

# Parameters
n <- 1000
nsim <- 50

# Generate a simple parabolic relationship
X <- sample(1:10, n, replace = T)
# eta <- log(10) + (x-.3)^2
eta <- - .5 * sin(X * 1.6 * pi / 10)

# Generate several
Y <- rpois(n, exp(eta))

#----- Factor

# Transform as factor
Xf <- factor(X)

# Constraint matrix
Cmat <- shapeConstr(Xf, "inc")

# Basic test
treat <- glm(Y ~ Xf, family = "quasipoisson", method = "cirls.fit",
  Cmat = list(Xf = Cmat))
plot(X, eta, pch = 16, ylim = range(c(eta, predict(treat))))
points(X, predict(treat), pch = 15, col = 3)

# When intercept is "included" in the factor
Cmat0 <- shapeConstr(Xf, "inc", intercept = TRUE)
int <- glm(Y ~ 0 + Xf, family = "quasipoisson", method = "cirls.fit",
  Cmat = list(Xf = Cmat0))
plot(X, eta, pch = 16, ylim = range(c(eta, predict(int))))
points(X, predict(int), pch = 15, col = 3)

# Automatic addition of intercept
int2 <- glm(Y ~ 0 + Xf, family = "quasipoisson", method = "cirls.fit",
 constr = ~ shape(Xf, "inc"))
plot(X, eta, pch = 16, ylim = range(c(eta, predict(int2))))
points(X, predict(int2), pch = 15, col = 3)

# Helmert contrasts
Xf2 <- Xf
contrasts(Xf2) <- "contr.helmert"
Cmat2 <- shapeConstr(Xf2, "inc")
helm <- glm(Y ~ Xf2, family = "quasipoisson", method = "cirls.fit",
  Cmat = list(Xf2 = Cmat2))
plot(X, eta, pch = 16, ylim = range(c(eta, predict(helm))))
points(X, predict(helm), pch = 15, col = 3)
# model.matrix(helm)

# Tests
test_that("Shape constraints on factors work", {
  expect_true(all(
    diff(predict(treat, newdata = data.frame(Xf = levels(Xf)))) >=
      -sqrt(.Machine$double.eps)))
  expect_true(all(
    diff(predict(int, newdata = data.frame(Xf = levels(Xf)))) >=
      -sqrt(.Machine$double.eps)))
  expect_true(all(
    diff(predict(int2, newdata = data.frame(Xf = levels(Xf)))) >=
      -sqrt(.Machine$double.eps)))
  expect_true(all(
    diff(predict(helm, newdata = data.frame(Xf2 = levels(Xf2)))) >=
      -sqrt(.Machine$double.eps)))
})


###############################################################################
# Constraints on subdomains
###############################################################################

# #---------------------
# # Test constraining specific domain
# #---------------------
#
# #----- Generate data
#
# n <- 1000
#
# X <- seq(0, 1, length.out = n)
# # eta <- log(10) + (x-.3)^2
# eta <- 5 - exp(X)
#
# # Generate several
# set.seed(6)
# Y <- eta + rnorm(n, sd = .5)
#
# # A P-spline basis
# basis <- ps(x = X, df = 10)
#
# # A sequence of breaks
# brseq <- seq(-0.1, 1.1, by = .05)
#
# test_that("subdomain constraints work", {
#
#   #----- Initial test with a non-decreasing constraint
#   for (br in brseq){
#
#     # Fit a non-decreasing curve above br
#     Cmat1 <- mkDmat(d = 1, s = 1, knots = attr(basis, "knots"),
#       ord = attr(x, "degree") + 1, intercept = attr(basis, "intercept"),
#       lower = br, upper = Inf)
#     curve1 <- glm(Y ~ basis, method = "cirls.fit", Cmat = list(basis = Cmat1)) |>
#       predict()
#
#     # Test it works
#     expect_true(all(!isFALSE(
#       diff(curve1[X > br]) >= -sqrt(.Machine$double.eps))))
#     expect_true(any(!isFALSE(
#       diff(curve1[X < br]) < -sqrt(.Machine$double.eps))))
#
#     # Fit a non-decreasing curve below br
#     Cmat2 <- mkDmat(d = 1, s = 1, knots = attr(basis, "knots"),
#       ord = attr(x, "degree") + 1, intercept = attr(basis, "intercept"),
#       lower = -Inf, upper = br)
#     curve2 <- glm(Y ~ basis, method = "cirls.fit", Cmat = list(basis = Cmat2)) |>
#       predict()
#
#     # Test it works
#     expect_true(all(!isFALSE(
#       diff(curve2[X < br]) >= -sqrt(.Machine$double.eps))))
#     expect_true(any(!isFALSE(
#       diff(curve2[X > br]) < -sqrt(.Machine$double.eps))))
#
#     # Plot the curves
#     plot(X, eta, type = "l")
#     lines(X, curve1, col = 3)
#     lines(X, curve2, col = 4)
#     abline(v = attr(basis, "knots"), lty = 3)
#     abline(v = br, col = 2)
#   }
#
#   #----- Initial test with convexity constraint
#
#   # Testing with first degree constraint
#   for (br in brseq){
#
#     # Fit convex curve below br
#     Cmat1 <- mkDmat(d = 2, s = 1, knots = attr(basis, "knots"),
#       ord = attr(x, "degree") + 1, intercept = attr(basis, "intercept"),
#       lower = br, upper = Inf)
#     curve1 <- glm(Y ~ basis, method = "cirls.fit", Cmat = list(basis = Cmat1)) |>
#       predict()
#
#     # Test it works
#     expect_true(all(!isFALSE(
#       diff(curve1[X > br], diff = 2) >= -sqrt(.Machine$double.eps))))
#     expect_true(any(!isFALSE(
#       diff(curve1[X < br], diff = 2) < -sqrt(.Machine$double.eps))))
#
#     # Fit convex curve above br
#     Cmat2 <- mkDmat(d = 2, s = 1, knots = attr(basis, "knots"),
#       ord = attr(x, "degree") + 1, intercept = attr(basis, "intercept"),
#       lower = -Inf, upper = br)
#     curve2 <- glm(Y ~ basis, method = "cirls.fit", Cmat = list(basis = Cmat2)) |>
#       predict()
#
#     # Test it works
#     expect_true(all(!isFALSE(
#       diff(curve2[X < br], diff = 2) >= -sqrt(.Machine$double.eps))))
#     expect_true(any(!isFALSE(
#       diff(curve2[X > br], diff = 2) < -sqrt(.Machine$double.eps))))
#
#     # Plot curves
#     plot(X, eta, type = "l")
#     lines(X, curve1, col = 3)
#     lines(X, curve2, col = 4)
#     abline(v = attr(basis, "knots"), lty = 3)
#     abline(v = br, col = 2)
#   }
#
# })
#

Try the cirls package in your browser

Any scripts or data that you put into this service are public.

cirls documentation built on Sept. 13, 2025, 1:09 a.m.