修改R中的glm函数以采用用户指定的链接函数

10
在R中的glm中,Gamma系列的默认链接函数为inverseidentitylog。现在对于我的特定问题,我需要使用响应变量Y的伽玛回归和一个修改过的链接函数log(E(Y)-1))。因此,我考虑修改R中的一些glm相关函数。有几个函数可能与此相关,我正在寻求任何有过这方面经验的人的帮助。
例如,Gamma函数的定义如下:
function (link = "inverse") 
{
  linktemp <- substitute(link)
  if (!is.character(linktemp)) 
    linktemp <- deparse(linktemp)
  okLinks <- c("inverse", "log", "identity")
  if (linktemp %in% okLinks) 
    stats <- make.link(linktemp)
  else if (is.character(link)) 
    stats <- make.link(link)
  else {
    if (inherits(link, "link-glm")) {
      stats <- link
      if (!is.null(stats$name)) 
        linktemp <- stats$name
    }
    else {
      stop(gettextf("link \"%s\" not available for gamma family; available links are %s", 
                    linktemp, paste(sQuote(okLinks), collapse = ", ")), 
           domain = NA)
    }
  }
  variance <- function(mu) mu^2
  validmu <- function(mu) all(mu > 0)
  dev.resids <- function(y, mu, wt) -2 * wt * (log(ifelse(y == 
                                                            0, 1, y/mu)) - (y - mu)/mu)
  aic <- function(y, n, mu, wt, dev) {
    n <- sum(wt)
    disp <- dev/n
    -2 * sum(dgamma(y, 1/disp, scale = mu * disp, log = TRUE) * 
               wt) + 2
  }
  initialize <- expression({
    if (any(y <= 0)) stop("non-positive values not allowed for the 'gamma' family")
    n <- rep.int(1, nobs)
    mustart <- y
  })
  simfun <- function(object, nsim) {
    wts <- object$prior.weights
    if (any(wts != 1)) 
      message("using weights as shape parameters")
    ftd <- fitted(object)
    shape <- MASS::gamma.shape(object)$alpha * wts
    rgamma(nsim * length(ftd), shape = shape, rate = shape/ftd)
  }
  structure(list(family = "Gamma", link = linktemp, linkfun = stats$linkfun, 
                 linkinv = stats$linkinv, variance = variance, dev.resids = dev.resids, 
                 aic = aic, mu.eta = stats$mu.eta, initialize = initialize, 
                 validmu = validmu, valideta = stats$valideta, simulate = simfun), 
            class = "family")
}

此外,为了使用命令glm(y ~ log(mu), family = Gamma(link = MyLink)),我是否还需要修改glm.fit函数?谢谢!
更新和新问题 根据@Ben Bolker的评论,我们需要编写一个名为vlog的新链接函数(真实名称为"log(exp(y)-1)")。我发现make.link函数可能需要进行此类修改。它被定义为:
function (link) 
{
  switch(link, logit = {
    linkfun <- function(mu) .Call(C_logit_link, mu)
    linkinv <- function(eta) .Call(C_logit_linkinv, eta)
    mu.eta <- function(eta) .Call(C_logit_mu_eta, eta)
    valideta <- function(eta) TRUE
  }, 

  ...

  }, log = {
    linkfun <- function(mu) log(mu)
    linkinv <- function(eta) pmax(exp(eta), .Machine$double.eps)
    mu.eta <- function(eta) pmax(exp(eta), .Machine$double.eps)
    valideta <- function(eta) TRUE
  }, 

  ...

  structure(list(linkfun = linkfun, linkinv = linkinv, mu.eta = mu.eta, 
                 valideta = valideta, name = link), class = "link-glm")
}

我的问题是:如果我们想要将这个链接函数vlog永久地添加到glm中,以便在每个R会话中,我们可以直接使用glm(y~x,family=Gamma(link="log(exp(y)-1)")),那么我们应该使用fix(make.link),然后将vlog的定义添加到其主体中吗?或者fix()只能在当前R会话中执行此操作?再次感谢!
还有一件事:我意识到可能需要修改另一个函数。它是Gamma,定义为
function (link = "inverse") 
{
  linktemp <- substitute(link)
  if (!is.character(linktemp)) 
    linktemp <- deparse(linktemp)
  okLinks <- c("inverse", "log", "identity")
  if (linktemp %in% okLinks) 
    stats <- make.link(linktemp)
  else if (is.character(link)) 
    stats <- make.link(link)
  else {
    if (inherits(link, "link-glm")) {
      stats <- link
      if (!is.null(stats$name)) 
        linktemp <- stats$name
    }
    else {
      stop(gettextf("link \"%s\" not available for gamma family; available links are %s", 
                    linktemp, paste(sQuote(okLinks), collapse = ", ")), 
           domain = NA)
    }
  }
  variance <- function(mu) mu^2
  validmu <- function(mu) all(mu > 0)
  dev.resids <- function(y, mu, wt) -2 * wt * (log(ifelse(y == 
                                                            0, 1, y/mu)) - (y - mu)/mu)
  aic <- function(y, n, mu, wt, dev) {
    n <- sum(wt)
    disp <- dev/n
    -2 * sum(dgamma(y, 1/disp, scale = mu * disp, log = TRUE) * 
               wt) + 2
  }
  initialize <- expression({
    if (any(y <= 0)) stop("non-positive values not allowed for the 'gamma' family")
    n <- rep.int(1, nobs)
    mustart <- y
  })
  simfun <- function(object, nsim) {
    wts <- object$prior.weights
    if (any(wts != 1)) 
      message("using weights as shape parameters")
    ftd <- fitted(object)
    shape <- MASS::gamma.shape(object)$alpha * wts
    rgamma(nsim * length(ftd), shape = shape, rate = shape/ftd)
  }
  structure(list(family = "Gamma", link = linktemp, linkfun = stats$linkfun, 
                 linkinv = stats$linkinv, variance = variance, dev.resids = dev.resids, 
                 aic = aic, mu.eta = stats$mu.eta, initialize = initialize, 
                 validmu = validmu, valideta = stats$valideta, simulate = simfun), 
            class = "family")
}

我认为我们还需要修改

okLinks <- c("inverse", "log", "identity")

为了

okLinks <- c("inverse", "log", "identity", "log(exp(y)-1)")

?


我不理解所有这些额外的复杂性。如下所示的示例中,只要已定义vlog,就可以通过glm(...,family=Gamma(link=vlog())来适配交替链接模型。您可以将vlog放在.R文件中,并在每个会话中使用source()引用它,或者创建一个小包来定义该函数。如果您想要的话,也可以将其放在R配置文件中,但是在每个R脚本中使用source("vlog.R")可能更加透明。我认为Gamma()不需要修改(请参见我的答案)。 - Ben Bolker
我猜如果你坚持按名称调用链接函数,你就必须执行上述所有额外的黑客操作,但我不明白为什么不使用family=Gamma(link=vlog())... - Ben Bolker
@BenBolker:是的,我试过你的代码了,它们完美地工作了!也许我的额外问题更普遍,关于如何永久包含用户定义选项来“修复”一个R函数。我将在我的软件包中包含vlog函数。再次感谢你的帮助;-) - alittleboy
1
我建议你从R源代码中复制函数(这样可以包含任何相关注释),并将其合并到你加载的软件包中,以遮盖基本版本。这是一个足够不同的任务,你应该将其作为一个单独的问题提出来。 - Ben Bolker
@BenBolker:没错 - 我会将它作为一个单独的问题发布;-) - alittleboy
2个回答

15

我基本上遵循的是示例中的形式 ?family,该示例显示了一个用户指定的链接,格式为qlogis(mu^(1/days))

我们希望有一个链接的格式为 eta = log(exp(y)-1) (所以反函数是 y=log(exp(eta)+1),且 mu.eta = dy/d(eta) = 1/(1+exp(-eta)))

vlog <- function() {
    ## link
    linkfun <- function(y) log(exp(y)-1)
    ## inverse link
    linkinv <- function(eta)  log(exp(eta)+1)
    ## derivative of invlink wrt eta
    mu.eta <- function(eta) { 1/(exp(-eta) + 1) }
    valideta <- function(eta) TRUE
    link <- "log(exp(y)-1)"
    structure(list(linkfun = linkfun, linkinv = linkinv,
                   mu.eta = mu.eta, valideta = valideta, 
                   name = link),
              class = "link-glm")
}

基本检查:

vv <- vlog()
vv$linkfun(vv$linkinv(27))  ## check invertibility
library("numDeriv")
all.equal(grad(vv$linkinv,2),vv$mu.eta(2))  ## check derivative

例子:

set.seed(101)
n <- 1000                       
x <- runif(n)
sh <- 2                        
y <- rgamma(n,scale=vv$linkinv(2+3*x)/sh,shape=sh)
glm(y~x,family=Gamma(link=vv))                       
## 
## Call:  glm(formula = y ~ x, family = Gamma(link = vv))
## 
## Coefficients:
## (Intercept)            x  
##       1.956        3.083  
## 
## Degrees of Freedom: 999 Total (i.e. Null);  998 Residual
## Null Deviance:       642.2 
## Residual Deviance: 581.8     AIC: 4268 
## 

非常感谢您的评论!我还有一个问题,并更新了我的原始帖子。希望您也能帮我解决这个问题;-) - alittleboy
如果还有人在寻找这个,我创建了一个小的代码片段来执行基于Ben Bolker的回答的约束逻辑回归。我不确定起始值,但也许它可以帮助某些人: https://gist.github.com/frbl/1411ee1df13154bd22092a7894503eb2 - frbl
你能帮忙解决这个问题吗?https://stackoverflow.com/questions/59810332/how-to-speed-up-the-analysis-of-anova-in-r - user10072460

3

尝试使用gnlm::gnlr()。 使用Ben Bolker示例中的xysh :

library(gnlm)
# custom link / inverse 
custom_inv <- function(eta)  log(exp(eta)+1)
library(gnlm)
gnlr(y=y,
     distribution = "gamma",
     mu = ~ custom_inv(beta0 + beta1*x),
     pmu = list(beta0=0, beta1=0),
     pshape=sh
)
# Location parameters:
#        estimate      se
# beta0     1.956  0.1334
# beta1     3.083  0.2919
# 
# Shape parameters:
#       estimate       se
# p[1]     0.625  0.04133

1
请检查以下链接,其中涉及到伽马回归的问题:https://dev59.com/cn4QtIcB2Jgan1znpV-0 - J AK

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接