R/leverage.R

Defines functions shift.influence.ppm shift.leverage.ppm Smooth.influence.ppm Smooth.leverage.ppm mean.leverage.ppm integral.influence.ppm integral.leverage.ppm print.influence.ppm print.leverage.ppm Window.influence.ppm as.owin.influence.ppm as.owin.leverage.ppm as.ppp.influence.ppm as.function.leverage.ppm as.im.leverage.ppm plot.influence.ppm contour.leverage.ppm persp.leverage.ppm plot.leverage.ppm ppmDerivatives ppmInfluenceEngine dfbetas.ppmInfluence influence.ppmInfluence leverage.ppmInfluence ppmInfluence dfbetas.ppm influence.ppm leverage.ppm leverage

Documented in as.function.leverage.ppm as.im.leverage.ppm as.owin.influence.ppm as.owin.leverage.ppm as.ppp.influence.ppm contour.leverage.ppm dfbetas.ppm dfbetas.ppmInfluence influence.ppm influence.ppmInfluence integral.influence.ppm integral.leverage.ppm leverage leverage.ppm leverage.ppmInfluence mean.leverage.ppm persp.leverage.ppm plot.influence.ppm plot.leverage.ppm ppmDerivatives ppmInfluence ppmInfluenceEngine print.influence.ppm print.leverage.ppm shift.influence.ppm shift.leverage.ppm Smooth.influence.ppm Smooth.leverage.ppm Window.influence.ppm

#
#  leverage.R
#
#  leverage and influence
#
#  $Revision: 1.121 $ $Date: 2020/12/19 05:25:06 $
#

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

leverage.ppm <- function(model, ...,
                         drop=FALSE, iScore=NULL, iHessian=NULL, iArgs=NULL)
{
  fitname <- short.deparse(substitute(model))
  a <- ppmInfluence(model, what="leverage", drop=drop,
                    iScore=iScore, iHessian=iHessian, iArgs=iArgs,
                    ...,
                    fitname=fitname)
  return(a$leverage)
}

influence.ppm <- function(model, ...,
                          drop=FALSE, iScore=NULL, iHessian=NULL, iArgs=NULL)
{
  fitname <- short.deparse(substitute(model))
  a <- ppmInfluence(model, what="influence", drop=drop,
                    iScore=iScore, iHessian=iHessian, iArgs=iArgs,
                    ...,
                    fitname=fitname)
  return(a$influence)
}

dfbetas.ppm <- function(model, ...,
                        drop=FALSE, iScore=NULL, iHessian=NULL, iArgs=NULL) {
  fitname <- short.deparse(substitute(model))
  a <- ppmInfluence(model, what="dfbetas", drop=drop,
                         iScore=iScore, iHessian=iHessian, iArgs=iArgs,
                     ...,
                    fitname=fitname)
  return(a$dfbetas)
}

ppmInfluence <- function(fit,
                         what=c("leverage", "influence", "dfbetas"),
                         ..., 
                         iScore=NULL, iHessian=NULL, iArgs=NULL,
                         drop=FALSE,
                         fitname=NULL) {
  stuff <- ppmInfluenceEngine(fit, what=what,
                          ..., 
                          iScore=iScore, iHessian=iHessian, iArgs=iArgs,
                          drop=drop, fitname=fitname)
  fnam <- c("fitname", "fit.is.poisson")
  result <- list()
  if("lev" %in% names(stuff)) {
    lev <- stuff[c(fnam, "lev")]
    class(lev) <- "leverage.ppm"
    result$leverage <- lev
  }
  if("infl" %in% names(stuff)) {
    infl <- stuff[c(fnam, "infl")]
    class(infl) <- "influence.ppm"
    result$influence <- infl
  }
  if(!is.null(dfb <- stuff$dfbetas)) {
    attr(dfb, "info") <- stuff[fnam]
    result$dfbetas <- dfb
  }
  other <- setdiff(names(stuff), c("lev", "infl", "dfbetas"))
  result[other] <- stuff[other]
  class(result) <- "ppmInfluence"
  return(result)
}

leverage.ppmInfluence <- function(model, ...) { model$leverage }
influence.ppmInfluence <- function(model, ...) { model$influence }
dfbetas.ppmInfluence <- function(model, ...) { model$dfbetas }

## ...............  main workhorse ....................................

ppmInfluenceEngine <- function(fit,
                         what=c("leverage", "influence", "dfbetas",
                           "score", "derivatives", "increments", "all"),
                         ...,
                         iScore=NULL, iHessian=NULL, iArgs=NULL,
                         drop=FALSE,
                         method=c("C", "interpreted"),
                         fine=FALSE,
                         precomputed=list(),
                         sparseOK=TRUE,
                         fitname=NULL,
                         multitypeOK=FALSE,
                         entrywise = TRUE,
                         matrix.action = c("warn", "fatal", "silent"),
                         dimyx=NULL, eps=NULL,
                         geomsmooth = TRUE) {
  if(is.null(fitname)) 
    fitname <- short.deparse(substitute(fit))

  ## type of calculation to be performed
  method <- match.arg(method)
  what <- match.arg(what, several.ok=TRUE)
  if("all" %in% what)
    what <- c("leverage", "influence", "dfbetas",
              "score", "derivatives", "increments")
  matrix.action <- match.arg(matrix.action)

  influencecalc <- any(what %in% c("leverage", "influence", "dfbetas"))
  hesscalc <- influencecalc || any(what == "derivatives")
  sparse <- sparseOK 
  target <- paste(what, collapse=",")
  
  ## ...........  collect information about the model .................
  
  stopifnot(is.ppm(fit))

  #' ensure object contains GLM fit
  if(!hasglmfit(fit)) {
    fit <- update(fit, forcefit=TRUE)
    precomputed <- list()
  }

  #' type of interpoint interaction
  fit.is.poisson <- is.poisson(fit)
  hasInf <- !fit.is.poisson && !identical(fit$interaction$hasInf, FALSE)

  #' estimating function 
  fitmethod <- fit$method
  logi <- (fitmethod == "logi")
  pseudo <- (fitmethod == "mpl")
  if(!logi && !pseudo) {
    warning(paste("Model was fitted with method =", dQuote(fitmethod),
                  "but is treated as having been fitted by maximum",
		  if(fit.is.poisson) "likelihood" else "pseudolikelihood",
		  "for leverage/influence calculation"),
	    call.=FALSE)
    pseudo <- TRUE
  }

  ## Detect presence of irregular parameters
  if(is.null(iArgs))
    iArgs <- fit$covfunargs
  gotScore <- !is.null(iScore)
  gotHess <- !is.null(iHessian)
  needHess <- gotScore && hesscalc  # may be updated later
  if(!gotHess && needHess)
    stop("Must supply iHessian", call.=FALSE)

  #' ...................  evaluate basic terms ....................
  
  ## extract values from model, using precomputed values if given
  theta  <- precomputed$coef   %orifnull% coef(fit)
  lampos <- precomputed$lambda %orifnull% fitted(fit, ignore.hardcore=hasInf,
                                                      check=FALSE)
  mom    <- precomputed$mom    %orifnull% model.matrix(fit, splitInf=hasInf)

  ## 'lampos' is positive part of cif
  ## 'lam' is full model cif including zeroes
  lam <- lampos
  zerocif <- attr(mom, "-Inf") %orifnull% logical(nrow(mom))
  anyzerocif <- any(zerocif)
  if(hasInf && anyzerocif)
    lam[zerocif] <- 0
  
  p <- length(theta)
  Q <- quad.ppm(fit)
  w <- w.quad(Q)
  loc <- union.quad(Q)
  isdata <- is.data(Q)
  mt <- is.multitype(loc)
  if(length(w) != length(lam))
    stop(paste("Internal error: length(w) = ", length(w),
               "!=", length(lam), "= length(lam)"),
         call.=FALSE)

  ## smoothing bandwidth and resolution for smoothed images of densities
  smallsigma <- if(!mt) avenndist(loc) else max(sapply(split(loc), avenndist))
  ## previously used 'maxnndist' instead of 'avenndist'
  if(is.null(dimyx) && is.null(eps)) 
    eps <- sqrt(prod(sidelengths(Frame(loc))))/256

  #' ...............  evaluate Hessian of regular parameters ................

  ## domain of composite likelihood
  ## (e.g. eroded window in border correction)
  inside <- getglmsubset(fit) %orifnull% rep(TRUE, npoints(loc))
  
  ## extract negative Hessian matrix of regular part of log composite likelihood
  ##  hess = negative Hessian H
  ##  fgrad = Fisher-scoring-like gradient G = estimate of E[H]

  if(logi) {
    ## ..............    logistic composite likelihood ......................
    ## Intensity of dummy points
    rho <- fit$Q$param$rho %orifnull% intensity(as.ppp(fit$Q))
    logiprob <- lampos / (lampos + rho)
    vclist <- vcov(fit, what = "internals", fine=fine, matrix.action="silent")
    hess <- vclist$Slog
    fgrad <- vclist$fisher
    invhess <- if(is.null(hess)) NULL else checksolve(hess, "silent")
    invfgrad <- if(is.null(fgrad)) NULL else checksolve(fgrad, "silent")
    if(is.null(invhess) || is.null(invfgrad)) {
      #' use more expensive estimate of variance terms
      vclist <- vcov(fit, what = "internals", fine=TRUE,
                     matrix.action=matrix.action)
      hess <- vclist$Slog
      fgrad <- vclist$fisher
      #' try again - exit if really singular
      invhess <- checksolve(hess, matrix.action, "Hessian", target)
      invfgrad <- checksolve(fgrad, matrix.action, "gradient matrix", target)
    }
#    vc <- invhess %*% (vclist$Sigma1log+vclist$Sigma2log) %*% invhess
  } else {
    ## ..............  likelihood or pseudolikelihood ....................
    invfgrad <- vcov(fit, hessian=TRUE, fine=fine, matrix.action="silent")
    fgrad <- hess <- if(is.null(invfgrad) || anyNA(invfgrad)) NULL else
                     checksolve(invfgrad, "silent")
    if(is.null(fgrad)) {
      invfgrad <- vcov(fit, hessian=TRUE, fine=TRUE,
                       matrix.action=matrix.action)
      fgrad <- hess <- checksolve(invfgrad, matrix.action, "Hessian", target)
    }
  }

  #' ...............  augment Hessian  ...................

  ## evaluate additional (`irregular') components of score, if any
  iscoremat <- ppmDerivatives(fit, "gradient", iScore, loc, covfunargs=iArgs)
  gotScore <- !is.null(iscoremat)
  needHess <- gotScore && hesscalc
  if(!gotScore) {
    REG <- 1:ncol(mom)
  } else {
    ## count regular and irregular parameters
    nreg <- ncol(mom)
    nirr <- ncol(iscoremat)
    ## add extra columns to model matrix
    mom <- cbind(mom, iscoremat)
    REG <- 1:nreg
    IRR <- nreg + 1:nirr
    ## evaluate additional (`irregular') entries of Hessian
    ihessmat <- if(!needHess) NULL else
                ppmDerivatives(fit, "hessian", iHessian, loc, covfunargs=iArgs)
    if(gotHess <- !is.null(ihessmat)) {
      ## recompute negative Hessian of log PL and its mean
      fgrad <- hessextra <- matrix(0, ncol(mom), ncol(mom))
    } else if(needHess && length(iArgs)) {
      nami <- names(iArgs)
      stop(paste("Unable to compute iHess, the",
                 ngettext(length(nami), "component", "components"),
                 "of the Hessian matrix for the irregular",
                 ngettext(length(nami), "parameter", "parameters"),
                 commasep(sQuote(names(iArgs)))),
           call.=FALSE)
    }
    if(pseudo) {
      ## ..............  likelihood or pseudolikelihood ....................
      switch(method,
             interpreted = {
               for(i in seq(loc$n)) {
                 # weight for integrand
                 wti <- lam[i] * w[i]
                 if(all(is.finite(wti))) {
                   # integral of outer product of score 
                   momi <- mom[i, ]
                   v1 <- outer(momi, momi, "*") * wti
                   if(all(is.finite(v1)))
                     fgrad <- fgrad + v1
                   # integral of Hessian
                   # contributions nonzero for irregular parameters
                   if(gotHess) {
                     v2 <- matrix(as.numeric(ihessmat[i,]), nirr, nirr) * wti
                     if(all(is.finite(v2)))
                       hessextra[IRR, IRR] <- hessextra[IRR, IRR] + v2
                   }
                 }
               }
               # subtract sum over data points
               if(gotHess) {
                 for(i in which(isdata)) {
                   v2 <- matrix(as.numeric(ihessmat[i,]), nirr, nirr) 
                   if(all(is.finite(v2)))
                     hessextra[IRR, IRR] <- hessextra[IRR, IRR] - v2
                 }
                 hess <- fgrad + hessextra
                 invhess <- checksolve(hess, matrix.action, "Hessian", target)
               } else {
                 invhess <- hess <- NULL
               }
             },
             C = {
               wlam <- lam * w
               fgrad <- sumouter(mom, wlam)
               if(gotHess) {
                 # integral term
                 isfin <- is.finite(wlam) & matrowall(is.finite(ihessmat))
                 vintegral <-
                   if(all(isfin)) wlam %*% ihessmat else
                               wlam[isfin] %*% ihessmat[isfin,, drop=FALSE]
                 # sum over data points
                 vdata <- .colSums(ihessmat[isdata, , drop=FALSE],
                                   sum(isdata), ncol(ihessmat),
                                   na.rm=TRUE)
                 vcontrib <- vintegral - vdata
                 hessextra[IRR, IRR] <-
                   hessextra[IRR, IRR] + matrix(vcontrib, nirr, nirr)
                 hess <- fgrad + hessextra
                 invhess <- checksolve(hess, matrix.action, "Hessian", target)
               } else {
                 invhess <- hess <- NULL
               }
             })
    } else {
      ## ..............  logistic composite likelihood ....................
      switch(method,
             interpreted = {
	       oweight <- logiprob * (1 - logiprob)
	       hweight <- ifelse(isdata, -(1 - logiprob), logiprob)
               for(i in seq(loc$n)) {
                 ## outer product of score 
                 momi <- mom[i, ]
                 v1 <- outer(momi, momi, "*") * oweight[i]
                 if(all(is.finite(v1)))
                   fgrad <- fgrad + v1
		 ## Hessian term
                 ## contributions nonzero for irregular parameters
                 if(gotHess) {
                   v2 <- hweight[i] *
		         matrix(as.numeric(ihessmat[i,]), nirr, nirr)
                   if(all(is.finite(v2)))
                     hessextra[IRR, IRR] <- hessextra[IRR, IRR] + v2
                 }
               }
	       if(gotHess) {
                 hess <- fgrad + hessextra
                 invhess <- checksolve(hess, matrix.action, "Hessian", target)
               } else {
                 invhess <- hess <- NULL
	       }
             },
             C = {
	       oweight <- logiprob * (1 - logiprob)
	       hweight <- ifelse(isdata, -(1 - logiprob), logiprob)
               fgrad <- sumouter(mom, oweight)
               if(gotHess) {
                 # Hessian term
                 isfin <- is.finite(hweight) & matrowall(is.finite(ihessmat))
                 vcontrib <-
                   if(all(isfin)) hweight %*% ihessmat else
                               hweight[isfin] %*% ihessmat[isfin,, drop=FALSE]
                 hessextra[IRR, IRR] <-
                   hessextra[IRR, IRR] + matrix(vcontrib, nirr, nirr)
                 hess <- fgrad + hessextra
                 invhess <- checksolve(hess, matrix.action, "Hessian", target)
               } else {
                 invhess <- hess <- NULL
               }
             })
    }
    invfgrad <- checksolve(fgrad, matrix.action, "gradient matrix", target)
  }
  
  if(!needHess) {
    if(pseudo){
    hess <- fgrad
    invhess <- invfgrad
    }
  }
  #
  ok <- NULL
  if(drop) {
    ok <- complete.cases(mom)
    if(all(ok)) {
      ok <- NULL
    } else {
      if((nbad <- sum(isdata[!ok])) > 0)
        warning(paste("NA value of canonical statistic at",
                      nbad, ngettext(nbad, "data point", "data points")),
                call.=FALSE)
      Q <- Q[ok]
      mom <- mom[ok, , drop=FALSE]
      loc <- loc[ok]
      lam <- lam[ok]
      w   <- w[ok]
      isdata <- isdata[ok]
      inside <- inside[ok]
    }
  } 

  # ........  start assembling result .....................
  result <- list(fitname=fitname, fit.is.poisson=fit.is.poisson)

  if(any(c("score", "derivatives") %in% what)) {
    ## calculate the composite score
    rawmean <- if(logi) logiprob else (lam * w)
    rawresid <- isdata - rawmean
    score <- matrix(rawresid, nrow=1) %*% mom

    if("score" %in% what)
      result$score <- score
    if("derivatives" %in% what) 
      result$deriv <- list(mom=mom, score=score,
                           fgrad=fgrad, invfgrad=invfgrad,
                           hess=hess, invhess=invhess)
    if(all(what %in% c("score", "derivatives")))
      return(result)
  }


  ## :::::::::::::::  compute second order terms :::::::::::::


  ##  >>>  set model matrix to zero outside the domain <<<
  mom[!inside, ] <- 0
  
  ## compute effect of adding/deleting each quadrature point
  if(fit.is.poisson) {
    ##  ........ Poisson case ..................................
    eff <- mom
    ddS <- ddSintegrand <- NULL
  } else {
    ## ........  Gibbs case ....................................
    ## initialise
    eff <- mom
    ## second order interaction terms
    ##    columns index the point being added/deleted
    ##    rows index the points affected
    ## goal is to compute these effect matrices:
    eff.data <- eff.back  <- matrix(0, nrow(eff), ncol(eff),
                                    dimnames=dimnames(eff))
    ## 
    U <- union.quad(Q)
    nU <- npoints(U)
    ## decide whether to split into blocks
    nX <- Q$data$n
    nD <- Q$dummy$n
    bls <- quadBlockSizes(nX, nD, p, announce=TRUE)
    nblocks    <- bls$nblocks
    nperblock  <- bls$nperblock
    ##
    if(nblocks > 1 && ("increments" %in% what)) {
      warning("Oversize quadrature scheme: cannot return array of increments",
              call.=FALSE)
      what <- setdiff(what, "increments")
    }
    R <- reach(fit)
    ## indices into original quadrature scheme
    whichok <- if(!is.null(ok)) which(ok) else seq_len(nX+nD) 
    whichokdata <- whichok[isdata]
    whichokdummy <- whichok[!isdata]
    
    ## {{{{{{{{{{{{{   L O O P   }}}}}}}}}}}}}
    ## loop 
    for(iblock in 1:nblocks) {
      first <- min(nD, (iblock - 1) * nperblock + 1)
      last  <- min(nD, iblock * nperblock)
      # corresponding subset of original quadrature scheme
      if(!is.null(ok) || nblocks > 1) {
        ## subset for which we will compute the effect
        current <- c(whichokdata, whichokdummy[first:last])
        ## find neighbours, needed for calculation
        other <- setdiff(seq_len(nU), current)
        crx <- crosspairs(U[current], U[other], R, what="indices")
        nabers <- other[unique(crx$j)]
        ## subset actually requested
        requested <- c(current, nabers)
        ## corresponding stuff ('B' for block)
        isdataB <- isdata[requested]
        changesignB <- ifelse(isdataB, -1, 1)
        zerocifB <- zerocif[requested]
        anyzerocifB <- any(zerocifB)
        momB <- mom[requested, , drop=FALSE]
        lamB <- lam[requested]
        #' unused:
        #'     insideB <- inside[requested]
        #'     lamposB <- lampos[requested]
        if(logi) logiprobB <- logiprob[requested]
        wB <- w[requested]
        currentB <- seq_along(current)
      } else {
        requested <- NULL
        isdataB <- isdata
        changesignB <- ifelse(isdataB, -1, 1)
        zerocifB <- zerocif
        anyzerocifB <- anyzerocif
        momB <- mom
        lamB <- lam
        #'  unused:
        #'     insideB <- inside
	#'     lamposB <- lampos
        if(logi) logiprobB <- logiprob
        wB <- w
      }
      ## compute second order terms 
      ## ddS[i,j, ] = Delta_i Delta_j S(x)
      ddS <- deltasuffstat(fit, restrict = "first", dataonly=FALSE,
                           quadsub=requested, sparseOK=sparse,
			   splitInf=hasInf,
                           force=TRUE, warn.forced=TRUE)
      ## 
      if(is.null(ddS)) {
        warning("Second order interaction terms are not implemented",
                " for this model; they are treated as zero", call.=FALSE)
        break
      } else {
        sparse <- inherits(ddS, "sparse3Darray")
	if(hasInf) {
          deltaInf <- attr(ddS, "deltaInf")
          hasInf <- !is.null(deltaInf)
          if(hasInf) sparse <- sparse && inherits(deltaInf, "sparseMatrix")
	}
        if(gotScore) {
          ## add extra planes of zeroes to second-order model matrix
          ## (zero because the irregular components are part of the trend)
          paddim <- c(dim(ddS)[1:2], nirr)
          if(!sparse) {
            ddS <- abind::abind(ddS, array(0, dim=paddim), along=3)
          } else {
            ddS <- bind.sparse3Darray(ddS,
                                      sparse3Darray(dims=paddim),
                                      along=3)
          }
        }
      }
      ## ^^^^^^^^^^^^^^^^^  second term in DeltaScore ^^^^^^^^^^^^^^^^^^^^
      ## effect of addition/deletion of U[j]
      ## on score contribution from data points (sum automatically restricted to
      ## interior for border correction by earlier call to
      ## deltasuffstat(..., restrict = "first"))
      ddSX <- ddS[isdataB, , , drop=FALSE]
      eff.data.B <- marginSumsSparse(ddSX, c(2,3))
      ## check if any quadrature points have zero conditional intensity;
      ## these do not contribute to this term
      if(anyzerocifB)
        eff.data.B[zerocifB, ] <- 0
      ## save results for current subset of quadrature points 
      if(is.null(requested)) {
        eff.data <- eff.data.B
      } else {
        eff.data[current,] <- as.matrix(eff.data.B[currentB,,drop=FALSE])
      }
      ## 
      rm(ddSX, eff.data.B)
      ## ^^^^^^^^^^^^^^^^^  third term in DeltaScore ^^^^^^^^^^^^^^^^^^^^
      ## effect of addition/deletion of U[j] on integral term in score
      if(!sparse) {
        ## ::::::::::::::: full arrays, simpler code :::::::::::::::::::
        if(pseudo) {
          ## ---------------  likelihood or pseudolikelihood -----------
          ## model matrix after addition/deletion of each U[j]
          ## mombefore[i,j,] <- mom[i,]
          di <- dim(ddS)
          mombefore <- array(apply(momB, 2, rep, times=di[2]), dim=di)
          momchange <- ddS
          momchange[ , isdataB, ] <- - momchange[, isdataB, ]
          momafter <- mombefore + momchange
          ## effect of addition/deletion of U[j] on lambda(U[i], X)
          if(gotScore) {
            lamratio <- exp(tensor::tensor(momchange[,,REG,drop=FALSE],
                                           theta, 3, 1))
          } else {
            lamratio <- exp(tensor::tensor(momchange, theta, 3, 1))
          }
          lamratio <- array(lamratio, dim=dim(momafter))
          if(!hasInf) {
            #' cif is positive
            ddSintegrand <- lamB * (momafter * lamratio - mombefore)
          } else {
            #' cif can be zero
            zerobefore <- matrix(zerocifB, di[1], di[2])
            zerochange <- deltaInf * 1L
            zerochange[ , isdataB] <- - zerochange[ , isdataB]
            zeroafter  <- zerobefore + zerochange
            momZbefore <- mombefore
            momZbefore[ , zerocifB, ] <- 0
            IJK <- unname(which(array(zeroafter == 1, dim=di), arr.ind=TRUE))
            momZafter <- momafter
            momZafter[IJK] <- 0
            momZchange <- momZafter- momZbefore
            ddSintegrand <- lamB * (momZafter * lamratio - momZbefore)
          }
          rm(momchange, mombefore, momafter, lamratio)
        } else {
          ## ---------------  logistic composite likelihood -----------
          stop("Non-sparse method is not implemented for method = 'logi'!")
        }
        gc()
      } else {
        ## ::::::::::::::::::   sparse arrays   ::::::::::::::::::::::::
        if(logi) {
          ## ---------------  logistic composite likelihood -----------
          ## Delta suff. stat. with sign change for data/dummy (sparse3Darray)
          momchange <- ddS
          momchange[ , isdataB, ] <- - momchange[, isdataB, ]
          ## Evaluate theta^T %*% ddS (with sign -1/+1 according to data/dummy)
          ## as triplet sparse matrix
          if(gotScore){
            momchangeeffect <- tensorSparse(momchange[,,REG,drop=FALSE], theta, 3, 1)
          } else{
            momchangeeffect <- tensorSparse(momchange, theta, 3, 1)
          }
          ## Copy to each slice 
          momchangeeffect <- expandSparse(momchangeeffect,
                                          n = dim(ddS)[3], across = 3)
          ijk <- SparseIndices(momchangeeffect)
          ## Entrywise calculations below
          momchange <- as.numeric(momchange[ijk])
          mombefore <- mom[cbind(ijk$i,ijk$k)]
          momafter  <- mombefore + momchange
          ## Transform to change in probability
          expchange <- exp(momchangeeffect$x)
          lamBi <- lamB[ijk$i]
          logiprobBi <- logiprobB[ijk$i]
          changesignBj <- changesignB[ijk$j]
          pchange <- changesignBj*(lamBi * expchange / (lamBi*expchange + rho) - logiprobBi)
          ## Note: changesignBj * momchange == as.numeric(ddS[ijk])
          if(!hasInf) {
            #' cif is positive
            ddSintegrand <- momafter * pchange +
              logiprobBi * changesignBj * momchange
          } else {
            #' cif can be zero
            isdataBj <- isdataB[ijk$j]
            zerobefore <- as.logical(zerocifB[ijk$i])
            zerochange <- as.logical(deltaInf[cbind(ijk$i, ijk$j)])
            zerochange[isdataBj] <- - zerochange[isdataBj]
            zeroafter <- zerobefore + zerochange
            momZbefore <- ifelse(zerobefore, 0, mombefore)
            momZafter  <- ifelse(zeroafter,  0, momafter)
            momZchange <- momZafter - momZbefore
            ddSintegrand <- momZafter * pchange +
              logiprobBi * changesignBj * momZchange
          }
          ddSintegrand <- sparse3Darray(i = ijk$i, j = ijk$j, k = ijk$k,
                                        x = ddSintegrand,
                                        dims = dim(ddS))
        } else{
          ## ---------------  likelihood or pseudolikelihood -----------
          if(entrywise) {
            ## ......  sparse arrays, using explicit indices ......
            momchange <- ddS
            momchange[ , isdataB, ] <- - momchange[, isdataB, ]
            if(gotScore){
              lamratiominus1 <- expm1(tensorSparse(momchange[,,REG,drop=FALSE],
                                              theta, 3, 1))
            } else{
              lamratiominus1 <- expm1(tensorSparse(momchange, theta, 3, 1))
            }
            lamratiominus1 <- expandSparse(lamratiominus1,
                                           n = dim(ddS)[3], across = 3)
            ijk <- SparseIndices(lamratiominus1)
            ## Everything entrywise with ijk now:
            # lamratiominus1 <- lamratiominus1[cbind(ijk$i, ijk$j)]
            lamratiominus1 <- as.numeric(lamratiominus1$x)
            momchange <- as.numeric(momchange[ijk])
            mombefore <- momB[cbind(ijk$i, ijk$k)]
            momafter <- mombefore + momchange
            ## lamarray[i,j,k] <- lam[i]
            lamarray <- lamB[ijk$i]
            if(!hasInf) {
              #' cif is positive
              ddSintegrand <- lamarray * (momafter * lamratiominus1 + momchange)
            } else {
              #' cif can be zero
              isdataBj <- isdataB[ijk$j]
              zerobefore <- as.logical(zerocifB[ijk$i])
              zerochange <- as.logical(deltaInf[cbind(ijk$i, ijk$j)])
              zerochange[isdataBj] <- - zerochange[isdataBj]
              zeroafter <- zerobefore + zerochange
              momZbefore <- ifelse(zerobefore, 0, mombefore)
              momZafter  <- ifelse(zeroafter,  0, momafter)
              momZchange <- momZafter - momZbefore
              ddSintegrand <- lamarray*(momZafter*lamratiominus1 + momZchange)
            }
            ddSintegrand <- sparse3Darray(i = ijk$i, j = ijk$j, k = ijk$k,
                                          x = ddSintegrand,
                                          dims = dim(ddS))
          } else {
            ## ......  sparse array code ......
            ## Entries are required only for pairs i,j which interact.
            ## mombefore[i,j,] <- mom[i,]
            mombefore <- mapSparseEntries(ddS, 1, momB, conform=TRUE, across=3)
            momchange <- ddS
            momchange[ , isdataB, ] <- - momchange[, isdataB, ]
            ##            momafter <- evalSparse3Dentrywise(mombefore + momchange)
            momafter <- mombefore + momchange
            ## lamarray[i,j,k] <- lam[i]
            lamarray <- mapSparseEntries(ddS, 1, lamB, conform=TRUE, across=3)
            if(gotScore){
              lamratiominus1 <- expm1(tensorSparse(momchange[,,REG,drop=FALSE],
                                              theta, 3, 1))
            } else{
              lamratiominus1 <- expm1(tensorSparse(momchange,theta, 3, 1))
            }
            lamratiominus1 <- expandSparse(lamratiominus1,
                                           n = dim(ddS)[3], across = 3)
            ##            ddSintegrand <- evalSparse3Dentrywise(lamarray * (momafter* lamratiominus1 + momchange))
            if(!hasInf) {
              #' cif is positive 
              ddSintegrand <- lamarray * (momafter* lamratiominus1 + momchange)
            } else {
              #' cif has zeroes
              zerobefore <- mapSparseEntries(ddS, 1, zerocifB,
                                             conform=TRUE, across=3)
              zerochange <- mapSparseEntries(ddS, 1, deltaInf,
                                             conform=TRUE, across=2)
              zerochange[,isdataB,] <- - zerochange[,isdataB,]
              zeroafter <- zerobefore + zerochange
              momZbefore <- mombefore
              momZafter  <- momafter
              momZbefore[ , zerocifB , ] <- 0
              IJK <- SparseIndices(zeroafter)
              momZafter[IJK] <- 0
              momZchange <- momZafter - momZbefore
              ddSintegrand <- lamarray*(momZafter*lamratiominus1 + momZchange) 
            }
          }
          rm(lamratiominus1, lamarray, momafter)
        }
        rm(momchange, mombefore)
      }
      if(anyzerocifB) {
        ddSintegrand[zerocifB,,] <- 0
        ddSintegrand[,zerocifB,] <- 0
      }
      ## integrate
      if(logi){
        # eff.back.B <- tensorSparse(ddSintegrand, rep(1, length(wB)), 1, 1)
        eff.back.B <- marginSumsSparse(ddSintegrand, c(2,3))
      } else{
        eff.back.B <- changesignB * tensorSparse(ddSintegrand, wB, 1, 1)
      }
      ## save contribution
      if(is.null(requested)) {
        eff.back <- eff.back.B
      } else {
        eff.back[current,] <- as.matrix(eff.back.B[currentB, , drop=FALSE])
      }
    }
    ## {{{{{{{{{{{{{   E N D   O F   L O O P   }}}}}}}}}}}}}

    ## total
    eff <- eff + eff.data - eff.back
    eff <- as.matrix(eff)
  }
  
  if("increments" %in% what) {
    result$increm <- list(ddS=ddS,
                          ddSintegrand=ddSintegrand,
                          isdata=isdata,
                          wQ=w)
  }
  if(!any(c("leverage", "influence", "dfbetas") %in% what))
    return(result)

  # ............ compute leverage, influence, dfbetas ..............

  if(!is.matrix(invhess))
    stop("Internal error: inverse Hessian not available", call.=FALSE)
  
  # compute basic contribution from each quadrature point
  nloc <- npoints(loc)
  switch(method,
         interpreted = {
           b <- numeric(nloc)
           for(i in seq(nloc)) {
             effi <- eff[i,, drop=FALSE]
             momi <- mom[i,, drop=FALSE]
             b[i] <- momi %*% invhess %*% t(effi)
           }
         },
         C = {
           b <- bilinearform(mom, invhess, eff)
         })
  
  # .......... leverage .............
  
  if("leverage" %in% what) {
    ## values of leverage (diagonal) at points of 'loc'
    h <- b * lam
    ok <- is.finite(h)
    geomsmooth <- geomsmooth && all(h[!isdata & ok] >= 0)
    if(mt)
      h <- data.frame(leverage=h, type=marks(loc))
    levval <- (loc %mark% h)[ok]
    levvaldum <- levval[!isdata[ok]]
    if(!mt) {
      levsmo <- Smooth(levvaldum,
                       sigma=smallsigma,
                       geometric=geomsmooth,
                       dimyx=dimyx, eps=eps)
      levnearest <- nnmark(levvaldum, dimyx=dimyx, eps=eps)
    } else {
      levsplitdum <- split(levvaldum, reduce=TRUE)
      levsmo <- Smooth(levsplitdum,
                       sigma=smallsigma,
                       geometric=geomsmooth,
                       dimyx=dimyx, eps=eps)
      levnearest <- solapply(levsplitdum, nnmark, dimyx=dimyx, eps=eps)
    }
    ## mean level
    if(fit.is.poisson) {
      a <- area(Window(loc)) * markspace.integral(loc)
      levmean <- p/a
    } else {
      levmean <- if(!mt) mean(levnearest) else mean(sapply(levnearest, mean))
    }
    lev <- list(val=levval, smo=levsmo, ave=levmean, nearest=levnearest)
    result$lev <- lev
  }
  # .......... influence .............
  if("influence" %in% what) {
    if(logi){
      X <- loc
      effX <- as.numeric(isdata) * eff - mom * (inside * logiprob)
    } else{
      # values of influence at data points
      X <- loc[isdata]
      effX <- eff[isdata, ,drop=FALSE]
    }
    M <- (1/p) * quadform(effX, invhess)
    if(logi || is.multitype(X)) {
      # result will have several columns of marks
      M <- data.frame(influence=M)
      if(logi) M$isdata <- factor(isdata, levels = c(TRUE, FALSE), labels = c("data", "dummy"))
      if(is.multitype(X)) M$type <- marks(X)
    } 
    V <- X %mark% M
    result$infl <- V
  }
  # .......... dfbetas .............
  if("dfbetas" %in% what) {
    if(logi){
      M <- as.numeric(isdata) * eff - mom * (inside * logiprob)
      M <- t(invhess %*% t(M))
      Mdum <- M
      Mdum[isdata,] <- 0
      Mdum <- Mdum / w.quad(Q)
      DFB <- msr(Q, M[isdata, ], Mdum)
    } else {
      vex <- invhess %*% t(mom)
      dex <- invhess %*% t(eff)
      switch(method,
             interpreted = {
               dis <- con <- matrix(0, nloc, ncol(mom))
               for(i in seq(nloc)) {
                 vexi <- vex[,i, drop=FALSE]
                 dexi <- dex[,i, drop=FALSE]
                 dis[i, ] <- if(isdata[i]) dexi else 0
                 con[i, ] <- if(inside[i]) (- lam[i] * vexi) else 0
               }
             },
             C = {
               dis <- t(dex)
               dis[!isdata,] <- 0
               con <- - lam * t(vex)
               con[(lam == 0 | !inside), ] <- 0
             })
      colnames(dis) <- colnames(con) <- colnames(mom)
      DFB <- msr(Q, dis[isdata, ], con)
    }
    #' add smooth component
    DFB <- augment.msr(DFB, sigma=smallsigma, dimyx=dimyx, eps=eps)
    result$dfbetas <- DFB
  }
  return(result)
}

## >>>>>>>>>>>>>>>>>>>>>>>  HELPER FUNCTIONS <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

## extract derivatives from covariate functions
## WARNING: these are not the score components in general

ppmDerivatives <- function(fit, what=c("gradient", "hessian"),
                            Dcovfun=NULL, loc, covfunargs=list()) {
  what <- match.arg(what)
  if(!is.null(Dcovfun)) {
    ## use provided function Dcov to compute derivatives
    Dvalues <- mpl.get.covariates(Dcovfun, loc, covfunargs=covfunargs)
    result <- as.matrix(as.data.frame(Dvalues))
    return(result)
  }
  ## any irregular parameters?
  if(length(covfunargs) == 0)
    return(NULL)
  ## Try to extract derivatives from covariate functions
  ## This often works if the functions were created by symbolic differentiation
  fvalues <- mpl.get.covariates(fit$covariates, loc, covfunargs=covfunargs,
                                need.deriv=TRUE)
  Dlist <- attr(fvalues, "derivatives")[[what]]
  if(length(Dlist) == 0)
    return(NULL)
  switch(what,
         gradient = {
           result <- do.call(cbind, unname(lapply(Dlist, as.data.frame)))
           result <- as.matrix(result)
         },
         hessian = {
           ## construct array containing Hessian matrices
           biga <- do.call(blockdiagarray, Dlist)
           ## flatten matrices 
           result <- matrix(biga, nrow=dim(biga)[1L])
         })
  return(result)
}

## >>>>>>>>>>>>>>>>  PLOT METHODS <<<<<<<<<<<<<<<<<<<<<

plot.leverage.ppm <- function(x, ...,
                              what=c("smooth", "nearest", "exact"),
                              showcut=TRUE,
                              args.cut=list(drawlabels=FALSE),
                              multiplot=TRUE) {
  what <- match.arg(what)
  fitname <- x$fitname
  defaultmain <- paste("Leverage for", fitname)

  y <- x$lev

  if(what == "exact") {
    #' plot exact quadrature locations and leverage values
    yval <- y$val
    dont.complain.about(yval)
    z <- do.call(plot,
                 resolve.defaults(list(x=quote(yval), multiplot=multiplot),
                                  list(...),
                                  list(main=defaultmain)))
    return(invisible(z))
  }

  smo <- as.im(x, what=what)
  if(is.null(smo)) return(invisible(NULL))
  
  ave <- y$ave
  if(!multiplot && inherits(smo, "imlist")) {
    ave <- ave * length(smo)
    smo <- do.call(harmonise.im, unname(smo))
    ##    smo <- Reduce("+", smo)
    smo <- im.apply(smo, sum, check=FALSE)
    defaultmain <- c(defaultmain, "(sum over all types of point)")
  }
  args.contour <- resolve.defaults(args.cut, list(levels=ave))
  cutinfo <- list(addcontour=showcut,
                  args.contour=args.contour)
  if(is.im(smo)) {
    do.call(plot.im,
            resolve.defaults(list(quote(smo)),
                             cutinfo,
                             list(...),
                             list(main=defaultmain)))
  } else if(inherits(smo, "imlist")) {
    do.call(plot.solist,
            resolve.defaults(list(quote(smo)),
                             cutinfo,
                             list(...),
                             list(main=defaultmain)))
  } 
  invisible(NULL)
}


persp.leverage.ppm <- function(x, ..., what=c("smooth", "nearest"),
                               main, zlab="leverage") {
  if(missing(main)) main <- deparse(substitute(x))
  what <- match.arg(what)
  y <- as.im(x, what=what)
  if(is.null(y)) return(invisible(NULL))
  if(is.im(y)) return(persp(y, main=main, ..., zlab=zlab))
  pa <- par(ask=TRUE)
  lapply(y, persp, main=main, ..., zlab=zlab)
  par(pa)
  return(invisible(NULL))
}
  
contour.leverage.ppm <- function(x, ...,
                                 what=c("smooth", "nearest"),
                                 showcut=TRUE,
                                 args.cut=list(col=3, lwd=3, drawlabels=FALSE),
                                 multiplot=TRUE) {
  defaultmain <- paste("Leverage for", x$fitname)
  smo <- as.im(x, what=what)
  y <- x$lev
  ave <- y$ave
  if(!multiplot && inherits(smo, "imlist")) {
    ave <- ave * length(smo)
    smo <- do.call(harmonise.im, unname(smo))
    ##    smo <- Reduce("+", smo)
    smo <- im.apply(smo, sum, check=FALSE)
    defaultmain <- c(defaultmain, "(sum over all types of point)")
  }
  
  argh1 <- resolve.defaults(list(...),
                            list(main=defaultmain))
  argh2 <- resolve.defaults(args.cut,
                            list(levels=ave),
                            list(...))

  if(is.im(smo)) {
    #' single panel
    out <- do.call(contour, append(list(x=smo), argh1))
    if(showcut)
      do.call(contour, append(list(x=smo, add=TRUE), argh2))
  } else if(inherits(smo, "imlist")) {
    #' multiple panels
    argh <- append(list(x=smo, plotcommand ="contour"), argh1)
    if(showcut) {
      argh <- append(argh,
                     list(panel.end=function(i, y, ...) contour(y, ...),
                          panel.end.args=argh2))
    } 
    out <- do.call(plot.solist, argh) 
  } else {
    warning("Unrecognised format")
    out <- NULL
  }
  return(invisible(out))
}

plot.influence.ppm <- function(x, ..., multiplot=TRUE) {
  fitname <- x$fitname
  defaultmain <- paste("Influence for", fitname)
  y <- x$infl
  if(multiplot && isTRUE(ncol(marks(y)) > 1)) {
    # apart from the influence value, there may be additional columns of marks
    # containing factors: {type of point}, { data vs dummy in logistic case }
    ma <- as.data.frame(marks(y))
    fax <- sapply(ma, is.factor)
    nfax <- sum(fax)
    if(nfax == 1) {
      # split on first available factor, and remove this factor
      y <- split(y, reduce=TRUE)
    } else if(nfax > 1) {
      # several factors: split according to them all, and remove them all
      f.all <- do.call(interaction, ma[fax])
      z <- y %mark% ma[,!fax]
      y <- split(z, f.all)
    }
  }
  do.call(plot,
          resolve.defaults(list(quote(y)),
                           list(...),
                           list(main=defaultmain,
                                multiplot=multiplot,
                                which.marks=1)))
}

## >>>>>>>>>>>>>>>>  CONVERSION METHODS <<<<<<<<<<<<<<<<<<<<<


as.im.leverage.ppm <- function(X, ..., what=c("smooth", "nearest")) {
  what <- match.arg(what)
  y <- switch(what,
              smooth = X$lev$smo,
              nearest = X$lev$nearest)
  if(is.null(y))
    warning(paste("Data for", sQuote(what), "image are not available:",
                  "please recompute the leverage using the current spatstat"),
            call.=FALSE)
  return(y) # could be either an image or a list of images
}

as.function.leverage.ppm <- function(x, ...) {
  X <- x$lev$val
  S <- ssf(unmark(X), marks(X))
  return(as.function(S))
}

as.ppp.influence.ppm <- function(X, ...) {
  return(X$infl)
}

as.owin.leverage.ppm <- function(W, ..., fatal=TRUE) {
  y <- as.im(W)
  if(inherits(y, "imlist")) y <- y[[1L]]
  as.owin(y, ..., fatal=fatal)
}

as.owin.influence.ppm <- function(W, ..., fatal=TRUE) {
  as.owin(as.ppp(W), ..., fatal=fatal)
}

domain.leverage.ppm <- domain.influence.ppm <-
  Window.leverage.ppm <- Window.influence.ppm <-
  function(X, ...) { as.owin(X) } 


## >>>>>>>>>>>>>>>>  PRINT METHODS <<<<<<<<<<<<<<<<<<<<<

print.leverage.ppm <- function(x, ...) {
  splat("Point process leverage function")
  fitname <- x$fitname
  splat("for model:", fitname)
  lev <- x$lev
  splat("\nExact values:")
  print(lev$val)
  splat("\nSmoothed values:")
  print(lev$smo)
  ## for compatibility we retain the x$fit usage
  if(x$fit.is.poisson %orifnull% is.poisson(x$fit))
    splat("\nAverage value:", lev$ave)
  return(invisible(NULL))
}

print.influence.ppm <- function(x, ...) {
  splat("Point process influence measure")  
  fitname <- x$fitname
  splat("for model:", fitname)
  splat("\nExact values:")
  print(x$infl)
  return(invisible(NULL))
}

## >>>>>>>>>>>>>>>>  SUBSET METHODS <<<<<<<<<<<<<<<<<<<<<

"[.leverage.ppm" <- function(x, i, ..., update=TRUE) {
  if(missing(i)) return(x)
  y <- x$lev
  smoi <- if(is.im(y$smo)) y$smo[i, ...] else solapply(y$smo, "[", i=i, ...)
  if(!inherits(smoi, c("im", "imlist"))) return(smoi)
  y$smo <- smoi
  y$val <- y$val[i, ...]
  if(update) 
    y$ave <- if(is.im(smoi)) mean(smoi) else mean(sapply(smoi, mean))
  x$lev <- y
  return(x)
}

"[.influence.ppm" <- function(x, i, ...) {
  if(missing(i)) return(x)
  y <- x$infl[i, ...]
  if(!is.ppp(y)) return(y)
  x$infl <- y
  return(x)
}

## >>>>>>>>>>>>>>>>  SMOOTHING, INTEGRATION <<<<<<<<<<<<<<<<<<<<<

integral.leverage.ppm <- function(f, domain=NULL, ...) {
  y <- as.im(f, what="nearest")
  z <- if(is.im(y)) {
         integral(y, domain=domain, ...)
       } else if(is.solist(y)) {
         sapply(y, integral, domain=domain, ...)
       } else stop("Internal format is not understood")
  if(length(dim(z))) z <- t(z)
  return(z)
}

integral.influence.ppm <- function(f, domain=NULL, ...) {
  if(!is.null(domain)) {
    if(is.tess(domain)) {
      z <- sapply(tiles(domain), integral, f=f)
      if(length(dim(z))) z <- t(z)
      return(z)
    }
    f <- f[domain]
  }
  #' actual computation
  y <- as.ppp(f)
  return(colSums(as.matrix(marks(y))))
}

mean.leverage.ppm <- function(x, ...) {
  y <- as.im(x, what="nearest")
  mean(y, ...)
}

Smooth.leverage.ppm <- function(X, ...) Smooth(X$lev$val, ...)

Smooth.influence.ppm <- function(X, ...) Smooth(as.ppp(X), ...)

## >>>>>>>>>>>>>>>>  GEOMETRICAL OPERATIONS <<<<<<<<<<<<<<<<<<<<<

shift.leverage.ppm <- function(X, ...) {
  vec <- getlastshift(shift(as.owin(X), ...))
  X$lev$val <- shift(X$lev$val, vec=vec)
  smo <- X$lev$smo
  X$lev$smo <-
    if(is.im(smo)) shift(smo, vec=vec) else solapply(smo, shift, vec=vec)
  return(putlastshift(X, vec))
}

shift.influence.ppm <- function(X, ...) {
  X$infl <- shift(X$infl, ...)
  return(putlastshift(X, getlastshift(X$infl)))
}

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.