R/ppmclass.R

Defines functions tweak.coefs hardcoredist.ppm Window.ppm as.owin.ppm as.ppm.ppm as.ppm as.interact.ppm nobs.ppm unitname.ppm model.images.ppm model.images PPMmodelmatrix model.matrix.ippm model.matrix.ppm modelFrameGam model.frame.ppm AIC.ppm labels.ppm terms.ppm formula.ppm pseudoR2.ppm pseudoR2 logLik.ppm deviance.ppm emend valid.ppm valid getppmOriginalCovariates getppmdatasubset getglmsubset getglmdata getglmfit hasglmfit coef.ppm dummy.ppm data.ppm quad.ppm versionstring.ppm print.ppm is.ppm

Documented in AIC.ppm as.interact.ppm as.owin.ppm as.ppm as.ppm.ppm coef.ppm data.ppm deviance.ppm dummy.ppm emend formula.ppm getglmdata getglmfit getglmsubset getppmdatasubset getppmOriginalCovariates hardcoredist.ppm hasglmfit is.ppm labels.ppm logLik.ppm modelFrameGam model.frame.ppm model.images model.images.ppm model.matrix.ippm model.matrix.ppm nobs.ppm PPMmodelmatrix print.ppm pseudoR2 pseudoR2.ppm quad.ppm terms.ppm tweak.coefs unitname.ppm valid valid.ppm versionstring.ppm Window.ppm

#
#	ppmclass.R
#
#	Class 'ppm' representing fitted point process models.
#
#
#	$Revision: 2.150 $	$Date: 2021/06/29 02:28:26 $
#
#       An object of class 'ppm' contains the following:
#
#            $method           model-fitting method (currently "mpl")
#
#            $coef             vector of fitted regular parameters
#                              as given by coef(glm(....))
#
#            $trend            the trend formula
#                              or NULL 
#
#            $interaction      the interaction family 
#                              (an object of class 'interact') or NULL
#
#            $Q                the quadrature scheme used
#
#            $maxlogpl         the maximised value of log pseudolikelihood
#
#            $internal         list of internal calculation results
#
#            $correction       name of edge correction method used
#            $rbord            erosion distance for border correction (or NULL)
#
#            $the.call         the originating call to ppm()
#
#            $the.version      version of mpl() which yielded the fit
#
#
#------------------------------------------------------------------------

is.ppm <- function(x) { inherits(x, "ppm") }

print.ppm <-
function(x, ...,
         what=c("all", "model", "trend", "interaction", "se", "errors")) {

  verifyclass(x, "ppm")

  misswhat <- missing(what) 

  opts <- c("model", "trend", "interaction", "se", "errors")
  what <- match.arg(what, c("all", opts), several.ok=TRUE)
  if("all" %in% what) what <- opts

  np <- length(coef(x))
  terselevel <- spatstat.options("terse")
  digits <- getOption('digits')
  
  ## Determine whether SE is required 
  want.SE <- force.SE <- force.no.SE <- FALSE
  if(!misswhat && ("se" %in% what)) {
    ## SE was explicitly requested
    force.SE <- TRUE
  } else {
    ## Default rule: compute SE only if the model is Poisson
    switch(spatstat.options("print.ppm.SE"),
           always = { force.SE <- TRUE }, 
           never  = { force.no.SE <- TRUE },
           poisson = {
             want.SE <- is.poisson(x) && waxlyrical("extras", terselevel)
           })
  }
  do.SE <- (want.SE || force.SE) && !force.no.SE
  if(do.SE) {
    ## Check whether able to compute SE
    unable.SE <- (np == 0) || any(x$fitter %in% "gam") ||
      !is.null(x$internal$VB) || 
      (any(x$method %in% "mppm") && is.null(x$varcov))
    ## resolve
    if(force.SE && unable.SE) 
      warning("Unable to compute variances for this model", call.=FALSE)
    do.SE <- do.SE && !unable.SE
  }

  s <- summary.ppm(x, quick=if(do.SE) FALSE else "no variances")
        
  notrend <-    s$no.trend
#  stationary <- s$stationary
  poisson <-    s$poisson
  markeddata <- s$marked
  multitype  <- s$multitype
        
#  markedpoisson <- poisson && markeddata
  csr <- poisson && notrend && !markeddata

  special <- csr && all(c("model", "trend") %in% what)
  if(special) {
    ## ---------- Trivial/special cases -----------------------
    splat("Stationary Poisson process")
    cat("Intensity:", signif(s$trend$value, digits), fill=TRUE)
  } else {
    ## ----------- Print model type -------------------
    if("model" %in% what) {
      splat(s$name)
      parbreak(terselevel)
        
      if(markeddata) mrk <- s$entries$marks
      if(multitype) {
        splat(paste("Possible marks:",
                    commasep(sQuote(levels(mrk)))))
        parbreak(terselevel)
      }
    }
    ## ----- trend --------------------------
    if("trend" %in% what) {
      if(!notrend) {
        splat("Log",
              if(poisson) "intensity: " else "trend: ",
              pasteFormula(s$trend$formula))
        parbreak(terselevel)
      }

      if(waxlyrical('space', terselevel) || !do.SE) {
        ## print trend coefficients, unless redundant and space is tight
        tv <- s$trend$value
      
        if(length(tv) == 0) 
          splat("[No trend coefficients]")
        else {
          thead <- paste0(s$trend$label, ":")
          if(is.list(tv)) {
            splat(thead)
            for(i in seq_along(tv))
              print(tv[[i]])
          } else if(is.numeric(tv) && length(tv) == 1) {
            ## single number: append to end of current line
            tvn <- names(tv)
            tveq <- if(is.null(tvn)) "\t" else paste(" ", tvn, "= ")
            splat(paste0(thead, tveq, signif(tv, digits)))
          } else {
            ## some other format 
            splat(thead)
            print(tv)
          }
        }
        parbreak(terselevel)
      }
    }

    if(waxlyrical("space", terselevel) &&
       !is.null(cfa <- s$covfunargs) && length(cfa) > 0) {
      cfafitter <- s$cfafitter
      if(is.null(cfafitter)) {
        cat("Covariate", "function", "arguments", "(covfunargs)",
            "provided:", fill=TRUE)
      } else {
        cat("Irregular", "parameters", "(covfunargs)",
            "fitted", "by", paste0(sQuote(cfafitter), ":"),
            fill=TRUE)
      }
      for(i in seq_along(cfa)) {
        cat(paste(names(cfa)[i], "= "))
        cfai <- cfa[[i]]
        if(is.numeric(cfai) && length(cfai) == 1) {
          cfai <- signif(cfai, digits)
          cat(paste(cfai, "\n"))
        } else print(cfai)
      }
    }
  }
  
  # ---- Interaction ----------------------------

  if("interaction" %in% what) {
    if(!poisson) {
      print(s$interaction, family=FALSE, banner=FALSE, 
            brief=!waxlyrical("extras"))
      parbreak(terselevel)
    }
  }
  
  # ----- parameter estimates with SE and 95% CI --------------------
  if(waxlyrical("extras", terselevel) && ("se" %in% what) && (np > 0)) {
    if(!is.null(cose <- s$coefs.SE.CI)) {
      print(cose, digits=digits)
    } else if(do.SE) {
      # standard error calculation failed
      splat("Standard errors unavailable; variance-covariance matrix is singular")
    } else if(!force.no.SE) {
      # standard error was voluntarily omitted
      if(waxlyrical('space', terselevel))
        splat("For standard errors, type coef(summary(x))\n")
    }
  }
  
  # ---- Warnings issued in mpl.prepare  ---------------------

  if(waxlyrical("errors", terselevel) && "errors" %in% what) {
    probs <- s$problems
    if(!is.null(probs) && is.list(probs) && (length(probs) > 0)) 
      lapply(probs,
             function(x) {
               if(is.list(x) && !is.null(p <- x$print))
                 splat(paste("Problem:\n", p, "\n\n"))
             })
    
    if(s$old)
      warning(paste("Model fitted by old spatstat version", s$version))
        
  # ---- Algorithm status ----------------------------

    fitter <- s$fitter
    converged <- s$converged
    if(!is.null(fitter) && fitter %in% c("glm", "gam") && !converged)
      splat("*** Fitting algorithm for", sQuote(fitter),
            "did not converge ***")
  }

  if(waxlyrical("extras", terselevel) && s$projected) {
    parbreak()
    splat("Fit was emended to obtain a valid point process model")
  }

  if(identical(s$valid, FALSE) && waxlyrical("errors", terselevel)) {
    parbreak()
    splat("*** Model is not valid ***")
    if(!all(is.finite(s$entries$coef))) {
      splat("*** Some coefficients are NA or Inf ***")
    } else {
      splat("*** Interaction parameters are outside valid range ***")
    }
  } else if(is.na(s$valid) && waxlyrical("extras", terselevel)) {
    parbreak()
    splat("[Validity of model could not be checked]")
  }
  
  return(invisible(NULL))
}

# Extract version string from ppm object

versionstring.ppm <- function(object) {
  verifyclass(object, "ppm")
  v <- object$version
  if(is.null(v) || !is.list(v))
    v <- list(major=1, minor=3, release=4)
  vs <- paste(v$major, ".", v$minor, "-", v$release, sep="")
  return(vs)
}

# Extract quadrature scheme

quad.ppm <- function(object, drop=FALSE, clip=FALSE) {
  if(!is.ppm(object)) {
    if(is.kppm(object)) object <- object$po else
    if(is.lppm(object)) object <- object$fit else
    stop("object is not of class ppm, kppm or lppm")
  }
  Q <- object$Q
  if(is.null(Q))
    return(Q)
  if(drop || clip) {
    ok <- getglmsubset(object)
    if(!is.null(ok))
      Q <- Q[ok]
  }
  if(clip && object$correction == "border") {
    Wminus <- erosion(as.owin(object), object$rbord)
    Q <- Q[Wminus]
  }
  return(Q)
}

data.ppm <- function(object) { 
  verifyclass(object, "ppm")
  object$Q$data
}

dummy.ppm <- function(object, drop=FALSE) { 
  return(quad.ppm(object, drop=drop)$dummy)
}
  
# method for 'coef'
coef.ppm <- function(object, ...) {
  verifyclass(object, "ppm")
  object$coef
}

hasglmfit <- function(object) {
  return(!is.null(object$internal$glmfit))
}

getglmfit <- function(object) {
  verifyclass(object, "ppm")
  glmfit <- object$internal$glmfit
  if(is.null(glmfit))
      return(NULL)
  if(object$method != "mpl")
    glmfit$coefficients <- object$coef
  return(glmfit)
}

getglmdata <- function(object, drop=FALSE) {
  verifyclass(object, "ppm")
  gd <- object$internal$glmdata
  if(!drop) return(gd)
  return(gd[getglmsubset(object), , drop=FALSE])
}

getglmsubset <- function(object) {
  gd <- object$internal$glmdata
  if(object$method=="logi")
    return(gd$.logi.ok)
  return(gd$.mpl.SUBSET)
}

getppmdatasubset <- function(object) {
  # Equivalent to getglmsubset(object)[is.data(quad.ppm(object))]
  # but also works for models fitted exactly, etc
  #
  if(object$method %in% c("mpl", "ho")) {
    sub <- getglmsubset(object)
    if(!is.null(sub)) {
      Z <- is.data(quad.ppm(object))
      return(sub[Z])
    }
  }
  X <- data.ppm(object)
  sub <- if(object$correction == "border") {
    (bdist.points(X) >= object$rbord)
  } else rep(TRUE, npoints(X))
  return(sub)
}


getppmOriginalCovariates <- function(object) {
  df <- as.data.frame(as.ppp(quad.ppm(object)))
  cova <- object$covariates
  if(length(cova) > 0) {
    df2 <- mpl.get.covariates(object$covariates,
                              union.quad(quad.ppm(object)),
                              "quadrature points",
                              object$covfunargs)
    df <- cbind(df, df2)
  } 
  return(df)
}
  
# ??? method for 'effects' ???

valid <- function(object, ...) {
  UseMethod("valid")
}

valid.ppm <- function(object, warn=TRUE, ...) {
  verifyclass(object, "ppm")
  coeffs <- coef(object)
  # ensure all coefficients are fitted, and finite
  if(!all(is.finite(coeffs)))
    return(FALSE)
  # inspect interaction
  inte <- object$interaction
  if(is.poisson(object))
    return(TRUE) # Poisson process
  # extract fitted interaction coefficients
  Vnames <- object$internal$Vnames
  IsOffset <- object$internal$IsOffset  
  Icoeffs <- coeffs[Vnames[!IsOffset]]
  # check interaction
  checker <- inte$valid
  if(is.null(checker) || !newstyle.coeff.handling(inte)) {
    if(warn) warning("Internal error: unable to check validity of model")
    return(NA)
  }
  #' remove prefix to obtain coefficient names expected by interaction
  if(npre <- sum(nchar(object$internal$vnameprefix)))
    names(Icoeffs) <- substring(names(Icoeffs), npre+1L)
  answer <- checker(Icoeffs, inte)
  return(answer)
}

emend <- function(object, ...) {
  UseMethod("emend")
}

emend.ppm <- project.ppm <- local({
  tracemessage <- function(depth, ...) {
    if(depth == 0) return(NULL)
    spacer <- paste(rep.int("  ", depth), collapse="")
    marker <- ngettext(depth, "trace", paste("trace", depth))
    marker <- paren(marker, "[")
    splat(paste0(spacer, marker, " ", paste(...)))
  }
  leaving <- function(depth) {
    tracemessage(depth, ngettext(depth, "Returning.", "Exiting level."))
  }
  emend.ppm <- function(object, ..., fatal=FALSE, trace=FALSE) {
    verifyclass(object, "ppm")
    fast <- spatstat.options("project.fast")
    # user specifies 'trace' as logical
    # but 'trace' can also be integer representing trace depth
    td <- as.integer(trace)
    trace <- (td > 0)
    tdnext <- if(trace) td+1 else 0
    if(valid.ppm(object)) {
      tracemessage(td, "Model is valid.")
      leaving(td)
      return(object)
    }
    # First ensure trend coefficients are all finite
    coeffs <- coef(object)
    # Which coefficients are trend coefficients
    coefnames  <- names(coeffs)
    internames <- object$internal$Vnames
    trendnames <- coefnames[!(coefnames %in% internames)]
    # Trend terms in trend formula
    trendterms <- attr(terms(object), "term.labels")
    # Mapping from coefficients to terms of GLM
    coef2term  <- attr(model.matrix(object), "assign")
    istrend <- (coef2term > 0) & (coefnames %in% trendnames)
    # Identify non-finite trend coefficients
    bad <- istrend & !is.finite(coeffs)
    if(!any(bad)) {
      tracemessage(td, "Trend terms are valid.")
    } else {
      nbad <- sum(bad)
      tracemessage(td,
                   "Non-finite ",
                   ngettext(nbad,
                            "coefficient for term ",
                            "coefficients for terms "),
                   commasep(sQuote(trendterms[coef2term[bad]])))
      if(fast) {
        # remove first illegal term
        firstbad <- min(which(bad))
        badterm <- trendterms[coef2term[firstbad]]
        # remove this term from model
        tracemessage(td, "Removing term ", sQuote(badterm))
        removebad <- as.formula(paste("~ . - ", badterm), env=object$callframe)
        newobject <- update(object, removebad)
        if(trace) {
          tracemessage(td, "Updated model:")
          print(newobject)
        }
        # recurse
        newobject <- emend.ppm(newobject, fatal=fatal, trace=tdnext)
        # return
        leaving(td)
        return(newobject)
      } else {
        # consider all illegal terms
        bestobject <- NULL
        for(i in which(bad)) {
          badterm <- trendterms[coef2term[i]]
          # remove this term from model
          tracemessage(td, "Considering removing term ", sQuote(badterm))
          removebad <- as.formula(paste("~ . - ", badterm),
                                  env=object$callframe)
          object.i <- update(object, removebad)
          if(trace) {
            tracemessage(td, "Considering updated model:")
            print(object.i)
          }
          # recurse
          object.i <- emend.ppm(object.i, fatal=fatal, trace=tdnext)
          # evaluate logPL
          logPL.i   <- logLik(object.i, warn=FALSE)
          tracemessage(td, "max log pseudolikelihood = ", logPL.i)
          # optimise
          if(is.null(bestobject) || (logLik(bestobject, warn=FALSE) < logPL.i))
            bestobject <- object.i
        }
        if(trace) {
          tracemessage(td, "Best submodel:")
          print(bestobject)
        }
        # return
        leaving(td)
        return(bestobject)
      }
    } 
    # Now handle interaction
    inte <- object$interaction
    if(is.null(inte)) {
      tracemessage(td, "No interaction to check.")
      leaving(td)
      return(object)
    }
    tracemessage(td, "Inspecting interaction terms.")
    proj <- inte$project
    if(is.null(proj)) {
      whinge <- "Internal error: interaction has no projection operator"
      if(fatal) stop(whinge) 
      warning(whinge)
      leaving(td)
      return(object)
    }
    # ensure the same edge correction is used!
    correction <- object$correction
    rbord      <- object$rbord
    # apply projection 
    coef.orig <- coeffs <- coef(object)
    Vnames   <- object$internal$Vnames
    Icoeffs  <- coeffs[Vnames]
    change <- proj(Icoeffs, inte)
    if(is.null(change)) {
      tracemessage(td, "Interaction does not need updating.")
      leaving(td)
      return(object)
    }
    tracemessage(td, "Interaction is not valid.")
    if(is.numeric(change)) {
      tracemessage(td, "Interaction coefficients updated without re-fitting.")
      # old style: 'project' returned a vector of updated coefficients
      Icoeffs <- change
      # tweak interaction coefficients
      object$coef[Vnames] <- Icoeffs
      # recompute fitted interaction
      object$fitin <- NULL
      object$fitin <- fitin(object)
    } else if(is.interact(change)) {
      # new style: 'project' returns an interaction
      if(trace) {
        tracemessage(td, "Interaction changed to:")
        print(change)
      }
      # refit the whole model 
      #      (using the same edge correction)
      #      (and the same quadrature scheme)
      newobject <- update(object, interaction=change,
                          correction=correction, rbord=rbord,
                          forcefit=TRUE,
                          envir=object$callframe)
      if(trace) {
        tracemessage(td, "Updated model:")
        print(newobject)
      }
      # recurse
      newobject <- emend.ppm(newobject, fatal=fatal, trace=tdnext)
      object <- newobject
    } else if(is.list(change) && all(unlist(lapply(change, is.interact)))) {
      # new style: 'project' returns a list of candidate interactions
      nchange <- length(change)
      tracemessage(td, "Considering", nchange,
                   ngettext(nchange, "submodel", "submodels"))
      bestobject <- NULL
      for(i in seq_len(nchange)) {
        change.i <- change[[i]]
        if(trace) {
          tracemessage(td,
                       "Considering", ordinal(i), 
                       "candidate submodel, with interaction:")
          print(change.i)
        }
        # refit the whole model
        object.i <- update(object, interaction=change.i,
                           correction=correction, rbord=rbord,
                           forcefit=TRUE,
                           envir=object$callframe)
        if(trace) {
          tracemessage(td, "Considering", ordinal(i),
                       "candidate updated model:")
          print(object.i)
        }
        # recurse
        object.i <- emend.ppm(object.i, fatal=fatal, trace=tdnext)
        # evaluate logPL
        logPL.i   <- logLik(object.i, warn=FALSE)
        tracemessage(td, "max log pseudolikelihood = ", logPL.i)
        # optimise
        if(is.null(bestobject) || (logLik(bestobject, warn=FALSE) < logPL.i))
          bestobject <- object.i
      }
      # end loop through submodels
      if(trace) {
        tracemessage(td, "Best submodel:")
        print(bestobject)
      }
      object <- bestobject
    } else stop("Internal error: unrecognised format of update")
    object$projected <- TRUE
    object$coef.orig  <- coef.orig
    leaving(td)
    return(object)
  }
  emend.ppm
})

# more methods

deviance.ppm <- function(object, ...) {
  force(object)
  satlogpl <- object$satlogpl
  if(is.null(satlogpl)) {
    object <- update(object, forcefit=TRUE)
    satlogpl <- object$satlogpl
  }
  if(is.null(satlogpl) || !is.finite(satlogpl))
    return(NA)
  ll <- do.call(logLik,
                resolve.defaults(list(object=quote(object), absolute=FALSE),
                                 list(...)))
  ll <- as.numeric(ll)
  2 * (satlogpl - ll)
}

logLik.ppm <- function(object, ..., new.coef=NULL, warn=TRUE, absolute=FALSE) {
  if(!is.poisson.ppm(object) && warn) 
    warn.once("ppmLogLik",
              "log likelihood is not available for non-Poisson model;",
              "log pseudolikelihood returned")
  ## degrees of freedom
  nip <- if(!inherits(object, "ippm")) 0 else
           length(attr(object$covfunargs, "free"))
  df <- length(coef(object)) + nip
  ## compute adjustment constant
  if(absolute && object$method %in% c("exact", "mpl", "ho")) {
    X <- data.ppm(object)
    W <- Window(X)
    areaW <-
      if(object$correction == "border" && object$rbord > 0) 
      eroded.areas(W, object$rbord) else area(W)
    constant <- areaW * markspace.integral(X)
  } else constant <- 0
  ##
  if(is.null(new.coef)) {
    ## extract from object
    ll <- object$maxlogpl + constant
    attr(ll, "df") <- df
    class(ll) <- "logLik"
    return(ll)
  } 
  ## recompute for new parameter values
  method <- object$method
  if(method == "exact")
    method <- update(method, forcefit=TRUE)
  Q <- quad.ppm(object, drop=TRUE)
  Z <- is.data(Q)
  cif <- fitted(object, type="cif", new.coef=new.coef, drop=TRUE)
  cifdata <- cif[Z]
  switch(method,
         mpl=,
         exact=,
         ho = {
           w <- w.quad(Q)
           ll <- sum(log(cifdata[cifdata > 0])) - sum(w * cif)
         },
         logi=,
         VBlogi={
           B <- getglmdata(object, drop=TRUE)$.logi.B
           p <- cif/(B+cif)
           ll <- sum(log(p/(1-p))[Z]) + sum(log(1-p)) + sum(log(B[Z]))
         },
         stop(paste("Internal error: unrecognised ppm method:",
                    dQuote(method)))
         )
  ll <- ll + constant
  attr(ll, "df") <- df
  class(ll) <- "logLik"
  return(ll)
}

pseudoR2 <- function(object, ...) {
  UseMethod("pseudoR2")
}

pseudoR2.slrm <- pseudoR2.ppm <- function(object, ..., keepoffset=TRUE) {
  dres <- deviance(object, ..., warn=FALSE)
  nullfmla <- . ~ 1
  if(keepoffset && has.offset.term(object)) {
    off <- attr(model.depends(object), "offset")
    offterms <- row.names(off)[apply(off, 1, any)]
    if(length(offterms)) {
      nullrhs <- paste(offterms, collapse=" + ") 
      nullfmla <- as.formula(paste(". ~ ", nullrhs))
    }
  } 
  nullmod <- update(object, nullfmla, forcefit=TRUE)
  dnul <- deviance(nullmod, warn=FALSE)
  return(1 - dres/dnul)
}

formula.ppm <- function(x, ...) {
  return(x$trend)
}

terms.ppm <- function(x, ...) {
  terms(x$terms, ...)
}

labels.ppm <- function(object, ...) {
  # extract fitted trend coefficients
  co <- coef(object)
  Vnames <- object$internal$Vnames
  is.trend <- !(names(co) %in% Vnames)
  # model terms
  tt <- terms(object)
  lab <- attr(tt, "term.labels")
  if(length(lab) == 0)
    return(character(0))
  # model matrix
  mm <- model.matrix(object)
  ass <- attr(mm, "assign")
  # 'ass' associates coefficients with model terms
  # except ass == 0 for the Intercept
  coef.ok <- is.finite(co)
  relevant <- (ass > 0) & is.trend
  okterms <- unique(ass[coef.ok & relevant])
  return(lab[okterms])
}

AIC.ppm <- function(object, ..., k=2, takeuchi=TRUE) {
  ll <- logLik(object, warn=FALSE)
  pen <- attr(ll, "df")
  if(takeuchi && !is.poisson(object)) {
    vv <- vcov(object, what="internals")
    logi <- (object$method == "logi")
    J  <- with(vv, if(!logi) Sigma else (Sigma1log+Sigma2log))
    H  <- with(vv, if(!logi) A1 else Slog)
    ## Takeuchi penalty = trace of J H^{-1} = trace of H^{-1} J
    JiH <- try(solve(H, J), silent=TRUE)
    if(!inherits(JiH, "try-error")) 
      pen <- sum(diag(JiH))
  } 
  return(- 2 * as.numeric(ll) + k * pen)
}

extractAIC.ppm <- function (fit, scale = 0, k = 2, ..., takeuchi=TRUE)
{
  edf <- length(coef(fit))
  aic <- AIC(fit, k=k, takeuchi=takeuchi)
  c(edf, aic)
}

#
# method for model.frame

model.frame.ppm <- function(formula, ...) {
  object <- formula
  gf <- getglmfit(object)
  if(is.null(gf)) {
    warning("Model re-fitted with forcefit=TRUE")
    object <- update(object, forcefit=TRUE)
    gf <- getglmfit(object)
  }
  argh <- resolve.defaults(list(formula=quote(gf)),
                           list(...),
                           list(data = getglmdata(object),
                                subset = TRUE))
  result <- switch(object$fitter,
                   gam = do.call(modelFrameGam, argh),
                   do.call(model.frame, argh))
  return(result)
}

#' a hacked version of model.frame.glm that works for gam objects (mgcv)
modelFrameGam <- function(formula, ...) {
  dots <- list(...)
  nargs <- dots[match(c("data", "na.action", "subset"), names(dots), 
                      0L)]
  if (length(nargs) || is.null(formula$model)) {
    fcall <- formula$call
#    fcall$method <- "model.frame"
    fcall[[1L]] <- quote(mgcv::gam)
    fcall[names(nargs)] <- nargs
    env <- environment(formula$terms)
    if (is.null(env)) 
      env <- parent.frame()
    refut <- eval(fcall, env)
    refut$model
  } else formula$model
}

#
# method for model.matrix

model.matrix.ppm <- function(object,
                             data=model.frame(object, na.action=NULL),
                             ..., Q=NULL, keepNA=TRUE) {
  if(missing(data)) data <- NULL
  PPMmodelmatrix(object, data=data, ..., Q=Q, keepNA=keepNA)
}

model.matrix.ippm <- function(object,
                              data=model.frame(object, na.action=NULL),
                              ..., Q=NULL, keepNA=TRUE, irregular=FALSE) {
  if(missing(data)) data <- NULL 
  PPMmodelmatrix(object, data=data, ...,
                 Q=Q, keepNA=keepNA, irregular=irregular)
}

PPMmodelmatrix <- function(object,
                           data = NULL, 
                           ...,
                           subset, Q=NULL, keepNA=TRUE, irregular=FALSE,
                           splitInf=FALSE) {
  # handles ppm and ippm			      
  data.given <- !is.null(data)
  irregular <- irregular && inherits(object, "ippm") && !is.null(object$iScore)
  if(splitInf && !data.given && is.null(Q)) {
    #' force re-computation
    Q <- quad.ppm(object)
  }
  if(!is.null(Q)) {
    if(data.given) stop("Arguments Q and data are incompatible")
    if(!inherits(Q, c("ppp", "quad")))
      stop("Q should be a point pattern or quadrature scheme")
    if(is.ppp(Q)) Q <- quad(Q, Q[FALSE])
    ## construct Berman-Turner frame
    needed <- c("trend", "interaction", "covariates", "covfunargs",
                "correction", "rbord")
    bt <- do.call(bt.frame,
                  c(list(Q), object[needed], list(splitInf=splitInf)))
    forbid <- bt$forbid
    ## compute model matrix
    mf <- model.frame(bt$fmla, bt$glmdata, ...)
    mm <- model.matrix(bt$fmla, mf, ...)
    ass <- attr(mm, "assign")
    if(irregular) {
      ## add irregular score components
      U <- union.quad(Q)
      mi <- sapply(object$iScore, do.call,
                   args=append(list(x=U$x, y=U$y), object$covfunargs),
                   envir=environment(terms(object)))
      if(nrow(mi) != nrow(mm))
        stop("Internal error: incorrect number of rows in iScore")
      mm <- cbind(mm, mi)
    }
    ## subset
    if(!missing(subset)) {
      ok <- eval(substitute(subset), envir=bt$glmdata)
      mm <- mm[ok, , drop=FALSE]
      if(!is.null(forbid)) forbid <- forbid[ok]
    }
    ## remove NA's ?
    if(!keepNA) {
      ok <- complete.cases(mm)
      mm <- mm[ok, , drop=FALSE]
      if(!is.null(forbid)) forbid <- forbid[ok]
    }
    attr(mm, "assign") <- ass 
    attr(mm, "-Inf") <- forbid
    return(mm)
  }

  #' extract GLM fit 
  gf <- getglmfit(object)
  if(is.null(gf)) {
    warning("Model re-fitted with forcefit=TRUE")
    object <- update(object, forcefit=TRUE)
    gf <- getglmfit(object)
    if(is.null(gf))
      stop("internal error: unable to extract a glm fit")
  }

  if(data.given) {
    #' new data. Must contain the Berman-Turner variables as well.
    bt <- list(.mpl.Y=1, .mpl.W=1, .mpl.SUBSET=TRUE)
    if(any(forgot <- !(names(bt) %in% names(data)))) 
      data <- do.call(cbind, append(list(data), bt[forgot]))
    mm <- model.matrix(gf, data=data, ..., subset=NULL)
    ass <- attr(mm, "assign")
    if(irregular) {
      ## add irregular score components 
      mi <- sapply(object$iScore, do.call,
                   args=append(list(x=data$x, y=data$y), object$covfunargs),
                   envir=environment(terms(object)))
      if(nrow(mi) != nrow(mm))
        stop("Internal error: incorrect number of rows in iScore")
      mm <- cbind(mm, mi)
      attr(mm, "assign") <- ass
    }
    if(inherits(gf, "gam")) 
      attr(mm, "assign") <- gf$assign
    return(mm)
  }

  scrambled <- object$scrambled %orifnull% FALSE
  ## if TRUE, this object was produced by 'subfits' using jittered covariate

  if(!keepNA && !irregular && !scrambled) {
    # extract model matrix of glm fit object
    # restricting to its 'subset' 
    mm <- model.matrix(gf, ...)
    if(inherits(gf, "gam")) 
      attr(mm, "assign") <- gf$assign
    return(mm)
  }
  
  ## extract model matrix for all cases
  gd <- getglmdata(object, drop=FALSE) 
  if(!scrambled) {
    ## 'gf' was fitted to correct data. Use internals.
    mm <- model.matrix(gf, ..., subset=NULL, na.action=NULL)
    ass <- attr(mm, "assign")
  } else {
    ## 'gf' was originally fitted using jittered data:
    ## Use correct data given by 'gd'
    ## Temporarily add scrambled data to avoid singular matrices etc
    gds <- object$internal$glmdata.scrambled
    gdplus <- rbind(gd, gds)
    mm <- model.matrix(gf, ..., data=gdplus, subset=NULL, na.action=NULL)
    ass <- attr(mm, "assign")
    ## Now remove rows corresponding to scrambled data
    mm <- mm[seq_len(nrow(gd)), , drop=FALSE]
    attr(mm, "assign") <- ass
  } 
  cn <- colnames(mm)
  if(nrow(mm) != nrow(gd)) {
    # can occur if covariates include NA's or interaction is -Inf
    insubset <- getglmsubset(object)
    isna <- is.na(insubset) | !insubset
    if(sum(isna) + nrow(mm) == nrow(gd)) {
      # insert rows of NA's
      mmplus <- matrix( , nrow(gd), ncol(mm))
      mmplus[isna, ] <- NA
      mmplus[!isna, ] <- mm
      mm <- mmplus
      attr(mm, "assign") <- ass
    } else 
    stop("internal error: model matrix does not match glm data frame")
  }
  if(irregular) {
    ## add irregular score components 
    U <- union.quad(quad.ppm(object, drop=FALSE))
    mi <- sapply(object$iScore, do.call,
                 args=append(list(x=U$x, y=U$y), object$covfunargs),
	  envir=environment(terms(object)))
    if(nrow(mi) != nrow(mm))
      stop("Internal error: incorrect number of rows in iScore")
    mm <- cbind(mm, mi)
    attr(mm, "assign") <- ass
    cn <- c(cn, colnames(mi))
  }
  ## subset
  if(!missing(subset)) {
    ok <- eval(substitute(subset), envir=gd)
    mm <- mm[ok, , drop=FALSE]
    attr(mm, "assign") <- ass
  }
  ## remove NA's
  if(!keepNA) {
    mm <- mm[complete.cases(mm), , drop=FALSE]
    attr(mm, "assign") <- ass
  }
  if(inherits(gf, "gam")) 
    attr(mm, "assign") <- gf$assign
  colnames(mm) <- cn
  return(mm)
}

model.images <- function(object, ...) {
  UseMethod("model.images")
}

model.images.ppm <- function(object, W=as.owin(object), ...) {
  X <- data.ppm(object)
#  irregular <- resolve.1.default(list(irregular=FALSE), list(...))
  ## make a quadscheme with a dummy point at every pixel
  Q <- pixelquad(X, W)
  ## compute model matrix
  mm <- model.matrix(object, Q=Q, ...)
  ## retain only the entries for dummy points (pixels)
  mm <- mm[!is.data(Q), , drop=FALSE]
  mm <- as.data.frame(mm)
  ## create template image
  Z <- as.im(attr(Q, "M"))
  ok <- !is.na(Z$v)
  ## make images
  imagenames <- colnames(mm)
  if(!is.multitype(object)) {
    result <- lapply(as.list(mm), replace, list=ok, x=Z)
    result <- as.solist(result)
    names(result) <- imagenames
  } else {
    marx <- marks(Q$dummy)
    mmsplit <- split(mm, marx)
    result <- vector(mode="list", length=length(mmsplit))
    for(i in seq_along(mmsplit))
      result[[i]] <- as.solist(lapply(as.list(mmsplit[[i]]),
                                      replace, list=ok, x=Z))
    names(result) <- names(mmsplit)
    result <- do.call(hyperframe, result)
    row.names(result) <- imagenames
  }
  return(result)
}

unitname.ppm <- function(x) {
  return(unitname(x$Q))
}

"unitname<-.ppm" <- function(x, value) {
  unitname(x$Q) <- value
  return(x)
}

nobs.ppm <- function(object, ...) { npoints(data.ppm(object)) }

as.interact.ppm <- function(object) {
 verifyclass(object, "ppm")
 inte <- object$interaction
 if(is.null(inte))
   inte <- Poisson()
 return(inte)
}

as.ppm <- function(object) {
  UseMethod("as.ppm")
}

as.ppm.ppm <- function(object) {
  object
}

## method for as.owin

as.owin.ppm <- function(W, ..., from=c("points", "covariates"), fatal=TRUE) {
  if(!verifyclass(W, "ppm", fatal=fatal))
    return(NULL)
  from <- match.arg(from)
  datawin <- as.owin(data.ppm(W))
  if(from == "points")
    return(datawin)
  covs <- W$covariates
  isim <- unlist(lapply(covs, is.im))
  if(!any(isim))
    return(datawin)
  cwins <- lapply(covs[isim], as.owin)
  covwin <- do.call(intersect.owin, unname(cwins))
  result <- intersect.owin(covwin, datawin)
  return(result)
}

domain.ppm <- Window.ppm <- function(X, ..., from=c("points", "covariates")) {
  from <- match.arg(from)
  as.owin(X, ..., from=from)
}

hardcoredist.ppm <- function(x, ..., epsilon=0) {
  hardcoredist.fii(fitin(x), ..., epsilon=epsilon)
}

## change the coefficients in a ppm or other model

tweak.coefs <- function(model, new.coef) {
  if(is.null(new.coef)) return(model)
  co <- coef(model)
  check.nvector(new.coef, length(co), things="coefficients", vname="new.coef")
  model$coef.orig <- co
  model$coef <- new.coef
  return(model)
}

Try the spatstat.core package in your browser

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

spatstat.core documentation built on May 18, 2022, 9:05 a.m.