tests/testthat/test.fullmatch.R

################################################################################
# Fullmatch tests
################################################################################

context("fullmatch function")

source("utilities.R")

test_that("No cross strata matches", {
  # test data
  d <- data.frame(Z = rep(c(0,1), 4),
                  B = rep(c(0,1), each = 4))
  distances <- 1 + exactMatch(Z ~ B, data=d)

  res <- fullmatch(distances, data=d)
  expect_false(any(res[1:4] %in% res[5:8]))
})

test_that("Basic Matches", {
  d <- data.frame(position = rep(1:4, each = 4),
                  z = rep(0:1, 8),
                  rownames=letters[1:16])
  dist <- match_on(z ~ position, inv.scale.matrix = diag(1), data=d)

  res.mat <- fullmatch(dist, data=d)
  res.ism <- fullmatch(as.InfinitySparseMatrix(dist), data=d)
  expect_equivalent(res.mat, res.ism)

  allin <- exactMatch(rep(1, 16), structure(d$z, names=rownames(d)))
  expect_equivalent(fullmatch(dist + allin, data=d), res.mat)
  expect_equivalent(fullmatch(as.InfinitySparseMatrix(dist) + allin, data=d), res.mat)

  # Now that we know they are all the same, check that we got what we
  # want.  While this is not explicitly blocked, the position vector
  # should completely determine who matches whom (though for the same
  # level, this test is agnostic about which pairs should be matched.
  for (i in 1:4) {
    in.level <- rownames(d)[d$position == i] # possibly reorders factor
    not.in.level <- rownames(d)[d$position != i]
    expect_false(any(res.mat[in.level] %in% res.mat[not.in.level]))
  }
})

test_that("Checks input", {
  # no dimnames, bad!
  m <- matrix(1:8, nrow = 2, ncol = 4)
  # Throw both an error and a warning about lack of data
  expect_warning(expect_error(fullmatch(m)))
  expect_warning(expect_error(fullmatch(as.InfinitySparseMatrix(m))))
  # then to make sure it was the names
  dimnames(m) <- list(treated = c("A", "B"),
                      control = c("C", "D", "E", "F"))
  # Still should warn about lack of data
  expect_warning(expect_is(fullmatch(m), "factor")) # no error expected
  expect_warning(expect_true(all(names(fullmatch(m)) %in% LETTERS[1:6])))
  expect_warning(expect_false(any(is.na(fullmatch(m)))))

  # add only colnames
  m <- matrix(1:8, nrow = 2, ncol = 4)
  colnames(m) <- LETTERS[3:6]
  expect_warning(expect_error(fullmatch(m)))
  expect_warning(expect_error(fullmatch(as.InfinitySparseMatrix(m))))

  # repeat for rownames
  m <- matrix(1:8, nrow = 2, ncol = 4)
  rownames(m) <- LETTERS[1:2]
  expect_warning(expect_error(fullmatch(m)))
  expect_warning(expect_error(fullmatch(as.InfinitySparseMatrix(m))))

  # a logical matrix should case an error
  ml <- matrix(rep(c(TRUE, FALSE), 2), nrow = 2, ncol = 2, dimnames =
    list(letters[1:2], letters[3:4]))

  expect_warning(expect_error(fullmatch(ml)))
  ml <- replace(ml, 1:4, as.numeric(ml))
  expect_warning(expect_is(fullmatch(ml), "factor"))

  # row and columns share names
  dimnames(ml) <- list(letters[1:2], letters[2:3])
  expect_warning(expect_error(fullmatch(ml)))

  # the min, max, and omit must be same length as the number of
  # subproblems, which might be more than 1 if using exactMatch, e.g.

  m <- matrix(1, nrow = 2, ncol = 2, dimnames = list(c("a", "b"),
                                                     c('d', 'e')))

  expect_warning(expect_error(fullmatch(m, min.controls = c(0,0))))
  expect_warning(expect_error(fullmatch(m, max.controls = c(Inf,Inf))))
  expect_warning(expect_error(fullmatch(m, omit.fraction = c(1, 1))))

  B <- rep(1:5, each = 2)
  names(B) <- letters[1:10]
  em <- exactMatch(B, rep(c(0,1), 5))
  expect_warning(expect_error(fullmatch(em, min.controls = c(0,0))))
  expect_warning(expect_error(fullmatch(em, max.controls = c(Inf,Inf))))
  expect_warning(expect_error(fullmatch(em, omit.fraction = c(1, 1))))

})

test_that("fullmatch warns when given a 'within' arg that it's going to ignore", {
    m <- matrix(1, nrow = 2, ncol = 3,
                dimnames = list(c("a", "b"), c('d', 'e', 'f')))
    B <- rep(1:3, each = 2)
    names(B) <- letters[1:6]
    em <- exactMatch(B, rep(c(0,1), 3))
    expect_warning(fullmatch(m, within=em), "gnor")
    expect_warning(fullmatch(as.InfinitySparseMatrix(m), within=em), "gnor")
})

test_that("Reversion Test: Fullmatch handles omit.fraction for matrices", {
  # this bug was discovered while working on pairmatch, but it would appear to be
  # a fullmatch bug, though it might actually be in in subdivstrat or fmatch.

  A <- matrix(c(1,1,Inf,1,1,Inf,1,1,Inf,1,1,Inf), nrow = 3)
  dimnames(A) <- list(1:3, 4:7)
  Ai <- as.InfinitySparseMatrix(A)

  # the omit.fraction values as computed by pairmatch and was the same
  # for both A and Ai

  res.a <- fullmatch(A, min.controls = 1, max.controls = 1, omit.fraction = 0.5, data=data.frame(1:7))
  res.ai <- fullmatch(Ai, min.controls = 1, max.controls = 1, omit.fraction = 0.5, data=data.frame(1:7))

  expect_equivalent(res.a, res.ai)

})

test_that("Reversion Test: Inf entries in matrix", {
  # this was handled in the previous version just fine
  d <- matrix(c(1,2, 3,4, Inf, Inf), nrow = 2, dimnames = list(c(1,2), c(3,4, "U")))
  expect_warning(expect_equal(length(fullmatch(d)), 5))

  # the previous version also returned all 5 entries, not just the matched ones.
  expect_warning(expect_equal(length(fullmatch(as.InfinitySparseMatrix(d))), 5))
})

test_that("Reversion Test: Proper labeling of NAs", {
  # NA was being labeled as 1.NA
  A <- matrix(c(1,1,Inf,1,1,Inf,1,1,Inf,1,1,Inf), nrow = 3)
  dimnames(A) <- list(1:3, 4:7)

  Ai <- as.InfinitySparseMatrix(A)
  res <- fullmatch(Ai, data=data.frame(1:7))

  expect_true(is.na(res[[3]])) # improperly labeled as "1.NA"
  expect_true(!all(is.na(res[-3])))

  # also, entirely failing problems should be labeled with NAs, not subgroup.NA
  # https://github.com/markmfredrickson/optmatch/issues/22
  scores <- c(10,1,10,10,10,10,10,10,10, 1,1,1,1,1,10,10,10)
  B <- c(rep(1, 9), rep(2, 8))
  Z <- c(rep(c(0,1), each = 4), 0, rep(c(0,1), each = 4))

  names(scores) <- names(B) <- names(Z) <- letters[1:17]

  d <- match_on(scores, z = Z, within = exactMatch(Z ~ B))
  expect_warning(res <- pairmatch(caliper(d, 2)))
})

test_that("Results are in 'data order'", {
  # https://github.com/markmfredrickson/optmatch/issues/14

  df <- data.frame(z = rep(c(0,1), 5), x = 1:10, y = rnorm(10))
  df$w <- df$y + rnorm(10)
  rownames(df) <- letters[1:10][sample(1:10)]

  # add some NAs to the df:
  df[3, "y"] <- NA

  # mahal based ISM object
  m <- as.matrix(match_on(z ~ x + y + w, data = df))

  # make the first control unit unmatchable
  m[, 1] <- Inf

  res <- fullmatch(m, data = df)

  expect_equal(names(res), rownames(df))

  # shuffle the order of the distance matrix
  m2 <- m[sample(1:5),]
  res <- fullmatch(m2, data = df)

  expect_equal(names(res), rownames(df))

  mm <- as.InfinitySparseMatrix(m)

  res <- fullmatch(mm, data = df)

  expect_equal(names(res), rownames(df))

  # not supplying a data argument is grounds for a warning
  expect_warning(fullmatch(mm), "data")

  # data argument should have useful names attached
  tmp <- as.matrix(df)
  rownames(tmp) <- colnames(tmp) <- NULL
  expect_error(fullmatch(mm, data = tmp), "are not found")

  # Catching issue #56 rather than returning nonsense
  expect_error(fullmatch(mm, data=mm), "are not found")

})

test_that("Complete Inf matrices/ISMs => all NA optmatch object", {
  # Issue 15: https://github.com/markmfredrickson/optmatch/issues/15

  m <- matrix(Inf, nrow = 3, ncol = 4)
  rownames(m) <- LETTERS[1:3]
  colnames(m) <- letters[23:26]

  expect_warning(res.m <- fullmatch(m))

  expect_true(all(is.na(res.m)))

  ism <- as.InfinitySparseMatrix(m)

  expect_warning(res.ism <- fullmatch(ism))

  expect_true(all(is.na(res.ism)))

})

test_that("full() and pair() are alises to _match functions", {

  n <- 14
  test.data <- data.frame(Z = c(rep(0, n/2), rep(1, n/2)),
                          X1 = rnorm(n, mean = 5),
                          X2 = rnorm(n, mean = -2, sd = 2),
                          B = rep(c(0,1), n/2))

  model <- glm(Z ~ X1 + X2, data = test.data, family = binomial())
  dists <- match_on(model)
  expect_equivalent(fullmatch(dists, data=test.data),
                    full(dists, data=test.data))
  expect_equivalent(pairmatch(dists, data=test.data),
                    pair(dists, data=test.data))
})

test_that("fullmatch UI cleanup", {
  n <- 14
  test.data <- data.frame(Z = c(rep(0, n/2), rep(1, n/2)),
                          X1 = rnorm(n, mean = 5),
                          X2 = rnorm(n, mean = -2, sd = 2),
                          B = rep(c(0,1), n/2))

  m <- match_on(Z~X1 + X2, within=exactMatch(Z~B, data=test.data), data=test.data, caliper=2)

  fm.dist <- fullmatch(m, data=test.data)

  fm.form <- fullmatch(Z~X1 + X2, within=exactMatch(Z~B, data=test.data), data=test.data, caliper=2)

  match_equal(fm.dist, fm.form)

  # with "with()"

  fm.with <- with(data=test.data, fullmatch(Z~X1 + X2, within=exactMatch(Z~B), caliper=2))

  match_equal(fm.dist, fm.with)

  # passing a glm
  ps <- glm(Z~X1+X2, data=test.data, family=binomial)

  fm.ps <- fullmatch(ps, data=test.data, caliper=2)

  fm.glm <- fullmatch(glm(Z~X1+X2, data=test.data, family=binomial), data=test.data, caliper=2)
  fm.glm2 <- fullmatch(glm(Z~X1+X2, data=test.data, family=binomial), caliper=2)

  match_equal(fm.ps, fm.glm)

  # passing inherited from glm

  class(ps) <- c("foo", class(ps))

  fm.foo <- fullmatch(ps, data=test.data, caliper=2)

  match_equal(fm.ps, fm.foo)

  # with scores

  ps <- glm(Z~X2, data=test.data, family=binomial)

  m <- match_on(Z ~ X1 + scores(ps), within=exactMatch(Z~B, data=test.data), data=test.data)

  fm.dist <- fullmatch(m, data=test.data)

  fm.form <- fullmatch(Z~ X1 + scores(ps), within=exactMatch(Z~B, data=test.data), data=test.data)

  match_equal(fm.dist, fm.form)

  # passing numeric

  X1 <- test.data$X1
  Z <- test.data$Z

  names(X1) <- row.names(test.data)
  names(Z) <- row.names(test.data)
  fm.vector <- fullmatch(X1,z=Z, data=test.data, caliper=1)
  expect_warning(fm.vector2 <- fullmatch(X1,z=Z, caliper=1))

  m <- match_on(X1, z=Z, caliper=1)
  fm.mi <- fullmatch(m, data=test.data)

  match_equal(fm.vector, fm.mi)

  # function

  n <- 16
  test.data <- data.frame(Z = c(rep(0, n/2), rep(1, n/2)),
                          X1 = rep(c(1,2,3,4), each = n/4),
                          B = rep(c(0,1), n/2))

  sdiffs <- function(index, data, z) {
    abs(data[index[,1], "X1"] - data[index[,2], "X1"])
  }

  result.function <- match_on(sdiffs, z = test.data$Z, data = test.data)

  fm.funcres <- fullmatch(result.function, data=test.data)

  fm.func <- fullmatch(sdiffs, z = test.data$Z, data=test.data)
  expect_error(fullmatch(sdiffs, z = Z), "A data argument must be given when passing a function")

  match_equal(fm.funcres, fm.func)

  # passing bad arguments

  expect_error(fullmatch(test.data), "Invalid input, must be a potential argument to match_on")
  expect_error(fullmatch(TRUE), "Invalid input, must be a potential argument to match_on")


})

test_that("NAs in irrelevant data slots don't trip us up", {
  n <- 16
  test.data <- data.frame(Z = c(rep(0, n/2), rep(1, n/2)),
                          X1 = rep(c(1,2,3,4), each = n/4),
                          B = rep(c(0,1), n/2))
  test.data$B[1] <- NA

  expect_equal(length(fullmatch(Z~X1, data=test.data)), n)
})

test_that("Using strata instead of within arguments", {
  data(nuclearplants)

  f1 <- fullmatch(pr ~ cost, within=exactMatch(pr ~ pt, data=nuclearplants),
                  data=nuclearplants)
  f2 <- fullmatch(pr ~ cost + strata(pt), data=nuclearplants)
  f2b <- fullmatch(pr ~ cost, data=nuclearplants)

  expect_true(is(f1, "optmatch"))
  expect_true(is(f2, "optmatch"))
  expect_true(compare_optmatch(f1, f2))
  expect_false(compare_optmatch(f2, f2b))

  # handling more complicated strata calls
  f3 <- fullmatch(pr ~ cost, within=exactMatch(pr ~ pt + ct + ne,
                                               data=nuclearplants),
                  data=nuclearplants)
  f4 <- fullmatch(pr ~ cost + strata(pt) + strata(ct, ne), data=nuclearplants)

  expect_true(compare_optmatch(f3, f4))

  e1 <- exactMatch(pr ~ ne, data=nuclearplants)
  e2 <- exactMatch(pr ~ ne + ct, data=nuclearplants)

  f5 <- fullmatch(pr ~ cost, within=e2, data=nuclearplants)
  f6 <- fullmatch(pr ~ cost + strata(ct), within=e1, data=nuclearplants)
  f7 <- fullmatch(pr ~ cost + strata(ct, ne), data=nuclearplants)

  expect_true(compare_optmatch(f5, f6))
  expect_true(compare_optmatch(f5, f7))

})

test_that("strata in GLMs", {

  data(nuclearplants)

  f1 <- fullmatch(glm(pr ~ t1 + ne, data=nuclearplants, family=binomial),
                  within=exactMatch(pr ~ ne, data=nuclearplants),
                  data=nuclearplants)
  f2 <- fullmatch(glm(pr ~ t1 + strata(ne),
                      data=nuclearplants, family=binomial),
                  data=nuclearplants)

  expect_true(is(f1, "optmatch"))
  expect_true(is(f2, "optmatch"))
  expect_true(compare_optmatch(f1, f2))

  f3 <- fullmatch(glm(pr ~ t1 + ne + interaction(ct,pt),
                      data=nuclearplants, family=binomial),
                  within=exactMatch(pr ~ ne + ct*pt, data=nuclearplants),
                  data=nuclearplants)
  f4 <- fullmatch(glm(pr ~ t1 + strata(ne) + strata(ct, pt),
                      data=nuclearplants, family=binomial),
                  data=nuclearplants)

  expect_true(compare_optmatch(f3, f4))

  e1 <- exactMatch(pr ~ ne, data=nuclearplants)
  e2 <- exactMatch(pr ~ ne + ct, data=nuclearplants)

  f5 <- fullmatch(glm(pr ~ cost + ne + ct, data=nuclearplants),
                  within=e2, data=nuclearplants)
  f6 <- fullmatch(glm(pr ~ cost + ne + strata(ct), data=nuclearplants),
                  within=e1, data=nuclearplants)
  f7 <- fullmatch(glm(pr ~ cost + strata(ct) + strata(ne), data=nuclearplants),
                  data=nuclearplants)

  expect_true(compare_optmatch(f5, f6))
  expect_true(compare_optmatch(f5, f7))

  # strata(a,b) is equivalent to interaction(a,b)
  f8 <- fullmatch(glm(pr ~ cost + strata(ct,ne), data=nuclearplants),
                  data=nuclearplants)
  suppressWarnings(
    f9 <- fullmatch(glm(pr ~ cost + interaction(ne, ct) + strata(ct),
                        data=nuclearplants),
                    within=e1, data=nuclearplants)
  )
  f10 <- fullmatch(glm(pr ~ cost + interaction(ne,ct), data=nuclearplants),
                   within=e2, data=nuclearplants)
  f11 <- fullmatch(glm(pr ~ cost + ne*ct, data=nuclearplants),
                   within=e2, data=nuclearplants)
  # f9 is a bit weird because of the double inclusion of ct, and is an
  # unlikely way for users to enter code, but the extra ct is of
  # course ignored.

  expect_true(compare_optmatch(f8, f9))
  expect_true(compare_optmatch(f8, f10))
  expect_true(compare_optmatch(f10, f11))

})

test_that("matched.distances attr removed per #57", {
  data(nuclearplants)

  f1 <- fullmatch(glm(pr ~ t1 + ne, data=nuclearplants, family=binomial),
                  within=exactMatch(pr ~ ne, data=nuclearplants),
                  data=nuclearplants)

  expect_true(is.null(attr(f1, "matched.distances")))
})

# Helper function for issue #123 - making sure NA's in
# treatment vector properly apply to match and propogate
# down to attributes
NA_checker <- function(match, NAvals) {
  expect_true(all(is.na(match[NAvals])))
  expect_true(all(!is.na(match[-NAvals])))
  for (attr in c("contrast.group", "subproblem")) {
    vals <- attr(match, attr)
    expect_true(all(is.na(vals[NAvals])))
    expect_true(all(!is.na(vals[-NAvals])))
  }
}

test_that("#123: Supporting NA's in treatment, fullmatch.formula", {
  data <- data.frame(z = rep(0:1, each = 5),
                     x = rnorm(10), fac=rep(c(rep("a",2), rep("b",3)),2))
  f <- fullmatch(z ~ x, data = data)
  expect_true(all(!is.na(f)))

  # Now add an NA

  data$z[1] <- NA
  f <- fullmatch(z ~ x, data = data)
  expect_equal(length(f), nrow(data))
  NA_checker(f, 1)

  f <- fullmatch(z ~ x + strata(fac), data = data)
  expect_equal(length(f), nrow(data))
  NA_checker(f, 1)

  data$z[c(2,5,6,7)] <- NA
  f <- fullmatch(z ~ x, data = data)
  expect_equal(length(f), nrow(data))
  NA_checker(f, c(1, 2, 5, 6, 7))
})

test_that("#123: Supporting NA's in treatment, fullmatch.numeric", {
  z <- rep(0:1, each = 5)
  x <- rnorm(10)
  names(z) <- names(x) <- 1:10
  expect_warning(f <- fullmatch(x, z = z))
  expect_true(all(!is.na(f)))
  expect_equal(length(f), length(z))

  data <- data.frame(z, x)
  f2 <- fullmatch(x, z = z, data = data)
  expect_equivalent(f[sort(names(f))], f2[sort(names(f2))])

  # Now add an NA

  z[1] <- NA
  expect_warning(f <- fullmatch(x, z = z))
  expect_true(all(!is.na(f)))
  expect_equal(length(f), length(z) - 1)
  expect_false("1" %in% names(f))

  data <- data.frame(z, x)
  f <- fullmatch(x, z = z, data = data)
  expect_equal(length(f), nrow(data))
  NA_checker(f, 1)


  z[c(2,5,6,7)] <- NA
  expect_warning(f <- fullmatch(x, z = z))
  expect_true(all(!is.na(f)))
  expect_equal(length(f), length(z) - 5)
  expect_false("1" %in% names(f))

  data <- data.frame(z, x)
  f <- fullmatch(x, z = z, data = data)
  expect_equal(length(f), nrow(data))
  NA_checker(f, c(1, 2, 5, 6, 7))
})

test_that("#123: Supporting NA's in treatment, fullmatch.function", {

  data <- data.frame(z = rep(0:1, each = 5),
                     x = rnorm(10))

  sdiffs <- function(index, data, z) {
    abs(data[index[,1], "x"] - data[index[,2], "x"])
  }

  f <- fullmatch(sdiffs, z = data$z, data = data)
  expect_equal(length(f), nrow(data))

  data$z[1] <- NA

  f <- fullmatch(sdiffs, z = data$z, data = data)
  expect_equal(length(f), nrow(data))
  NA_checker(f, 1)

  data$z[c(2,5,6,7)] <- NA

  f <- fullmatch(sdiffs, z = data$z, data = data)
  expect_equal(length(f), nrow(data))
  NA_checker(f, c(1, 2, 5, 6, 7))

})

test_that("#123: Supporting NA's in treatment, fullmatch.glm/bigglm", {

  data <- data.frame(z = rep(0:1, each = 10),
                     x = rnorm(20))

  mod <- glm(z ~ x, data = data, family = binomial)

  f <- fullmatch(mod)
  expect_equal(length(f), nrow(data))

  f2 <- fullmatch(mod, data = data)
  expect_equivalent(f, f2)

  data$z[1] <- NA

  mod <- glm(z ~ x, data = data, family = binomial)

  f <- fullmatch(mod)
  expect_equal(length(f), nrow(data))
  NA_checker(f, 1)

  f2 <- fullmatch(mod, data = data)
  expect_equivalent(f, f2)

  data$z[c(2,5,16,17)] <- NA

  mod <- glm(z ~ x, data = data, family = binomial)

  f <- fullmatch(mod)
  expect_equal(length(f), nrow(data))
  NA_checker(f, c(1, 2, 5, 16, 17))

  f2 <- fullmatch(mod, data = data)
  expect_equivalent(f, f2)
})

test_that("symmetry w.r.t. structural requirements (#132)",{


    data <- data.frame(z = c(rep(0,10), rep(1,5)),
                     x = rnorm(15), fac=rep(c(rep("a",2), rep("b",3)),3))
    f0 <- fullmatch(z ~ x, min.c=2, max.c=2, data = data)
    f1 <- fullmatch(!z ~ x, min.c=.5, max.c=.5, data = data)
    match_equivalent(f0, f1)

    f0 <- fullmatch(z ~ x + strata(fac), min.c=2, max.c=2, data = data)
    f1 <- fullmatch(!z ~ x + strata(fac), min.c=.5, max.c=.5, data = data)
    match_equivalent(f0, f1)

})

test_that("Edge case of only (1,1)-subproblems (#211)",
{
    data  <- data.frame(z=rep(0:1, 2), x=rnorm(4),
                        fac=rep(c("a", "b"), each=2)
                        )
    data_sm  <- subset(data, fac=="a")
    expect_silent(f1  <- fullmatch(setNames(data_sm$x, rownames(data_sm)),
                                   z=data_sm$z,
                                   data = data_sm
                                   )
                  )
    expect_silent(f2  <- fullmatch(z~x+strata(fac), data = data))
})
test_that("Problems w/ fewer controls than treatment don't break mean.controls", {

    data <- data.frame(z = c(rep(0,10), rep(1,5)),
                     x = rnorm(15), fac=rep(c(rep("a",2), rep("b",3)),3))

    f1 <- fullmatch(!z ~ x, min.c=.25, mean.c=.4, max.c=1, data = data)
    expect_true(sum(is.na(f1)) <= 1)
    f2 <- fullmatch(!z ~ x, min.c=.25, max.c=1, omit.fraction=.2, data = data)
    match_equivalent(f1, f2)

    f1 <- suppressWarnings(fullmatch(!z ~ x + strata(fac), min.c=.25,
                                     mean.c=c("a"=.25, "b"=(1/3)),
                                     max.c=1, data = data)
                           ) # Saw warnings here indicating that .fullmatch.with.recovery
# had been entered. Not sure why, and couldn't reproduce interactively. So there *may*
# be a testing bug here; decided to go ahead anyway. (BBH)

    expect_true(sum(is.na(f1)) <= 2)
    f2 <- suppressWarnings(fullmatch(!z ~ x + strata(fac), min.c=.25,
                                     max.c=1, omit.fraction=c("a"=.5, "b"=(1/3)),
                                     data = data)
                           ) # Saw same funny warning here as just above.
    match_equivalent(f1, f2)
})

test_that("accept negative omit.fraction", {

        data <- data.frame(z = c(rep(0,10), rep(1,5)),
                     x = rnorm(15), fac=rep(c(rep("a",2), rep("b",3)),3))

    f1 <- fullmatch(z~x, min.c=1, max.c=1, omit.fraction=.5, data = data)
    f2 <- fullmatch(!z ~ x, min.c=1, max.c=1, omit.fraction=-.5, data = data)
    match_equivalent(f1, f2)

    f1 <- fullmatch(z~x+strata(fac), min.c=1, max.c=1, omit.fraction=.5, data = data)
    f2 <- fullmatch(!z ~ x+strata(fac), min.c=1, max.c=1, omit.fraction=-.5, data = data)
    match_equivalent(f1, f2)


})

test_that("returns min-cost flow solution info", {

        data <- data.frame(z = c(rep(0,10), rep(1,5)),
                     x = rnorm(15), fac=rep(c(rep("a",2), rep("b",3)),3))

    f1 <- fullmatch(z~x, min.c=1, max.c=1, omit.fraction=.5, data = data)
    expect_false(is.null(attr(f1, "MCFSolutions")))
    f2 <- fullmatch(z~x+strata(fac), min.c=1, max.c=1, omit.fraction=.5, data = data)
    expect_false(is.null(attr(f2, "MCFSolutions")))

})

test_that('Hints accepted',{
  set.seed(201905)
  data <- data.frame(z = rep(0:1, each = 5),
                     x = rnorm(10), fac=rep(c(rep("a",2), rep("b",3)),2) )
  mo  <- match_on(z ~ x, data=data)
  f1a <- fullmatch(mo, min.c=.5, max.c=2, data = data, tol=0.1)
  expect_is(attr(f1a, "MCFSolutions"), "FullmatchMCFSolutions")
  expect_silent(fullmatch(mo, min.c=.5, max.c=2, data = data, tol=0.0001, hint=f1a))
  mos <- match_on(z ~ x + strata(fac), data=data)
  f1b <- fullmatch(mos, min.c=.5, max.c=2, data = data, tol=0.1)
  expect_is(attr(f1b, "MCFSolutions"), "FullmatchMCFSolutions")
  expect_warning(fullmatch(mos, min.c=.5, max.c=2, data = data, tol=0.1, hint=f1a), "ignoring")
  expect_silent(fullmatch(mos, min.c=.5, max.c=2, data = data, tol=0.0001, hint=f1b))

  expect_equal(length(summary(mos)$overall$unmatchable$treatment), 0)
  mosc  <- mos + caliper(mos, width=1)
  expect_equal(length(summary(mosc)$overall$unmatchable$treatment), 1)
  expect_silent(f1c  <- fullmatch(mosc, min.c=.5, max.c=2, data = data, tol=0.1, hint=f1b))
  expect_is(attr(f1c, "MCFSolutions"), "FullmatchMCFSolutions")
  ## expect_error(fullmatch(mos, min.c=.5, max.c=2, data = data, tol=0.1, hint=f1c))
  ## (b/c hint is missing price for the treatment node that was excluded by the caliper.
  ##  [But this is no longer an error.])
  ## OTOH it's always been OK to be missing a price for a control node.
  expect_silent(f1d  <- fullmatch(t(mosc), min.c=.5, max.c=2, data = data, tol=0.1))
  expect_is(attr(f1d, "MCFSolutions"), "FullmatchMCFSolutions")
  expect_silent(fullmatch(t(mosc), min.c=.5, max.c=2, data = data, tol=0.1, hint=f1d))
})

test_that("If matching fails, we should give a warning", {
  # One subproblem, matching fails
  expect_warning(fullmatch(pr ~ cost, data = nuclearplants, min = 5, max = 5),
                 "Matching failed")
  # Multiple subproblems, some fail
  expect_warning(fullmatch(pr ~ cost, data = nuclearplants, min = 2, max = 3,
                           within = exactMatch(pr ~ pt, data = nuclearplants)),
                 "subproblem matching failed")
  # Multiple subproblems, all fails
  expect_warning(fullmatch(pr ~ cost, data = nuclearplants, min = 60, max = 60,
                           within = exactMatch(pr ~ pt, data = nuclearplants)),
                 "Matching failed")
})

test_that("LEMON solvers", {

  # While all solves give the same solution to this problem, this needn't be
  # true in all cases, so if this starts randomly erroring, as long as all
  # solvers give reasonable results, this test can be dropped.

  data("nuclearplants")
  f1 <- fullmatch(pr ~ cost + t1, min.controls = 1, max.controls = 3, data = nuclearplants)
  f2 <- fullmatch(pr ~ cost + t1, min.controls = 1, max.controls = 3, data = nuclearplants,
                  solver = "LEMON")
  f3 <- fullmatch(pr ~ cost + t1, min.controls = 1, max.controls = 3, data = nuclearplants,
                  solver = LEMON("CycleCancelling"))
  f4 <- fullmatch(pr ~ cost + t1, min.controls = 1, max.controls = 3, data = nuclearplants,
                  solver = LEMON("CapacityScaling"))
  f5 <- fullmatch(pr ~ cost + t1, min.controls = 1, max.controls = 3, data = nuclearplants,
                  solver = LEMON("CostScaling"))
  f6 <- fullmatch(pr ~ cost + t1, min.controls = 1, max.controls = 3, data = nuclearplants,
                  solver = LEMON("NetworkSimplex"))

  mytol <- .Machine$double.eps^(1/4)
  match_equivalent(f1, f2)
  match_equivalent(f1, f3)
  match_equivalent(f1, f4)
  match_equivalent(f1, f5)
  match_equivalent(f1, f6)

  if (requireNamespace("rrelaxiv", quietly = TRUE)) {
    f7 <- fullmatch(pr ~ cost + t1, min.controls = 1, max.controls = 3,
                    data = nuclearplants, solver = "RELAX-IV")
    match_equivalent(f1, f7)
  }


})

Try the optmatch package in your browser

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

optmatch documentation built on Nov. 16, 2023, 5:06 p.m.