R/nomogram.R

Defines functions Design.levels getOldDesign nomo2.crr nomogram.mk6 nomogram.crr

Documented in nomogram.crr nomogram.mk6

##' Construct a Nomogram for a Competing Risks Regression Model
##'
##' Draws a partial nomogram from a \code{\link[QHScrnomo]{crr.fit}} object that can be used to manually obtain predicted values from from a competing risks regression model.
##'
##' @param fit A model fit by \code{\link[QHScrnomo]{crr.fit}}
##' @param failtime A vector of time points to display failure probability axes for.
##' @param ci Should the failure probability be displayed? Defaults to \code{TRUE}. If \code{FALSE}, the event-free probability is displayed.
##' @param ... Settings of variables to use in constructing axes.
##' If \code{datadist} was in effect, the default is to use \code{pretty(total range, nint)}
##' for continuous variables, and the class levels for discrete ones.
##' For \code{legend.nomabbrev}, \code{...} specifies optional parameters to pass to the legend.
##' Common ones are \code{bty = "n"} to suppress drawing the box. You may want to
##' specify a non-proportionally spaced font (e.g., courier) number if
##' abbreviations are more than one letter long. This will make the abbreviation
##' definitions line up (e.g., specify font = 2, the default for courier).
##' Ignored for print.
##' @param adj.to If \code{datadist} was not defined for all predictors, it is required to define adjustment settings for the undefined ones (e.g. \code{adj.to=list(age=50, sex="female")}).
##' @param lp Set to \code{FALSE} to suppress creation of an axis for scoring \eqn{X\beta}{X beta}. Defaults to \code{TRUE}.
##' @param lp.at If \code{lp=TRUE}, specifies a vector of settings of \eqn{X\beta}{X beta}. Default is to use \code{pretty(range of linear predictors, nint)}.
##' @param lplabel label for linear predictor axis. Default is \code{"Linear Predictor"}.
##' @param fun.at Function values to label on axis. Default \code{fun}
##'   evaluated at \code{lp.at}. If more than one \code{fun} was specified,
##'   using a vector for \code{fun.at} will cause all functions to be evaluated
##'   at the same argument values. To use different values, specify a list of
##'   vectors for \code{fun.at}, with elements corresponding to the different
##'   functions (lists of vectors also applies to \code{fun.lp.at} and
##'   \code{fun.side}).
##' @param fun.lp.at If you want to evaluate one of the functions at a
##'   different set of linear predictor values than may have been used in
##'   constructing the linear predictor axis, specify a vector or list of
##'   vectors of linear predictor values at which to evaluate the function.
##'   This is especially useful for discrete functions. The presence of this
##'   attribute also does away with the need for \code{nomogram} to compute
##'   numerical approximations of the inverse of the function.  It also allows
##'   the user-supplied function to return \code{factor} objects, which is
##'   useful when e.g. a single tick mark position actually represents a range.
##'   If the \code{fun.lp.at} parameter is present, the \code{fun.at} vector
##'   for that function is ignored.
##' @param funlabel Label for \code{fun} axis. If more than one function was
##'   given but \code{funlabel} is of length one, it will be duplicated as needed. If
##'   \code{fun} is a list of functions for which you specified names (see the
##'   final example below), these names will be used as labels.
##' @param fun.side A vector or list of vectors of \code{side} parameters for
##'   the \code{axis} function for labeling function values. Values may be 1 to
##'   position a tick mark label below the axis (the default), or 3 for above
##'   the axis. If for example an axis has 5 tick mark labels and the second
##'   and third will run into each other, specify \code{fun.side=c(1,1,3,1,1)}
##'   (assuming only one function is specified as \code{fun}).
##' @param interact When a continuous variable interacts with a discrete one,
##'   axes are constructed so that the continuous variable moves within the
##'   axis, and separate axes represent levels of interacting factors. For
##'   interactions between two continuous variables, all but the axis variable
##'   must have discrete levels defined in \code{interact}. For discrete
##'   interacting factors, you may specify levels to use in constructing the
##'   multiple axes. For continuous interacting factors, you must do this.
##'   Examples: \code{interact=list(age=seq(10,70,by=10),
##'   treat=c("A","B","D"))}.
##' @param intercept For models such as the ordinal logistic model with
##'   multiple intercepts, specifies which one to use in evaluating the linear
##'   predictor.
##' @param conf.int Confidence levels to display for each scoring. Default is
##'   \code{FALSE} to display no confidence limits. Setting \code{conf.int} to
##'   \code{TRUE} is the same as setting it to \code{c(0.7, 0.9)}, with the
##'   line segment between the 0.7 and 0.9 levels shaded using gray scale.
##' @param col.conf Colors corresponding to \code{conf.int}. Use fractions for
##'   gray scale (for UNIX S-PLUS).
##' @param conf.space A 2-element vector with the vertical range within which
##'   to draw confidence bars, in units of 1=spacing between main bars. Four
##'   heights are used within this range (8 for the linear predictor if more
##'   than 16 unique values were evaluated), cycling them among separate
##'   confidence intervals to reduce overlapping.
##' @param conf.lp Default is \code{"representative"} to group all linear
##'   predictors evaluated into deciles, and to show, for the linear predictor
##'   confidence intervals, only the mean linear predictor within the deciles
##'   along with the median standard error within the deciles. Set
##'   \code{conf.lp="none"} to suppress confidence limits for the linear
##'   predictors, and to \code{"all"} to show all confidence limits.
##' @param est.all To plot axes for only the subset of variables named in
##'   \code{\dots{}}, set \code{est.all=FALSE}. Note: This option only works
##'   when zero has a special meaning for the variables that are omitted from
##'   the graph.
##' @param abbrev Set to \code{TRUE} to use the \code{abbreviate} function to
##'   abbreviate levels of categorical factors, both for labeling tick marks
##'   and for axis titles. If you only want to abbreviate certain predictor
##'   variables, set \code{abbrev} to a vector of character strings containing
##'   their names.
##' @param minlength Applies if \code{abbrev=TRUE}. Is the minimum
##'   abbreviation length passed to the \code{abbreviate} function. If you set
##'   \code{minlength=1}, the letters of the alphabet are used to label tick
##'   marks for categorical predictors, and all letters are drawn no matter how
##'   close together they are. For labeling axes (interaction settings),
##'   \code{minlength=1} causes \code{minlength=4} to be used.
##' @param maxscale Default maximum point score is 100.
##' @param nint Number of intervals to label for axes representing continuous variables. See \code{\link{pretty}}.
##' @param label.every Specify \code{label.every=i} to label on every \code{i}th tick mark.
##' @param force.label Set to \code{TRUE} to force every tick mark intended to
##'   be labeled to have a label plotted (whether the labels run into each
##'   other or not)
##' @param xfrac Fraction of horizontal plot to set aside for axis titles
##' @param cex.axis Character size for tick mark labels
##' @param cex.var Character size for axis titles (variable names)
##' @param col.grid If \code{col.grid=1}, no gray scale is used, but an
##'   ordinary line is drawn. If \code{0<col.grid<1}, a \code{col} (gray
##'   scale) of \code{col.grid} is used to draw vertical reference lines for
##'   major axis divisions and \code{col.grid/2} for minor divisions. The
##'   default is \code{col.grid=FALSE}, i.e., reference lines are omitted.
##'   Specifying \code{col.grid=TRUE} is the same as specifying a gray scale
##'   level of \code{col.grid=.2} (5 for Windows S-PLUS).
##' @param vnames By default, variable labels are used to label axes. Set
##'   \code{vnames="names"} to instead use variable names.
##' @param varname.label In constructing axis titles for interactions, the
##'   default is to add \code{"(interacting.varname=level)"} on the right.
##'   Specify \code{varname.label=FALSE} to instead use \code{"(level)"}.
##' @param varname.label.sep If \code{varname.label=TRUE}, you can change the
##'   separator to something other than \code{=} by specifying this parameter.
##' @param ia.space When multiple axes are draw for levels of interacting
##'   factors, the default is to group combinations related to a main effect.
##'   This is done by spacing the axes for the second to last of these within a
##'   group only 0.7 (by default) of the way down as compared with normal space
##'   of 1 unit.
##' @param tck See \code{tck} under \code{par}.
##' @param lmgp Spacing between numeric axis labels and axis (see \code{par} for \code{mgp})
##' @param omit Vector of character strings containing names of variables for
##'   which to suppress drawing axes. Default is to show all variables.
##' @param naxes Maximum number of axes to allow on one plot. If the nomogram
##'   requires more than one "page", the "Points" axis will be repeated at the
##'   top of each page when necessary.
##' @param points.label A character string giving the axis label for the points scale
##' @param total.points.label A character string giving the axis label for the total points scale
##' @param total.sep.page Set to \code{TRUE} to force the total points and later axes to be placed on a separate page
##' @param total.fun A user-provided function that will be executed before the
##'   total points axis is drawn. Default is not to execute a function. This
##'   is useful e.g. when \code{total.sep.page=TRUE} and you wish to use
##'   \code{locator} to find the coordinates for positioning an abbreviation
##'   legend before it's too late and a new page is started (i.e.,
##'   \code{total.fun=function()print(locator(1))}).
##' @param verbose Set to \code{TRUE} to get printed output detailing how tick
##'   marks are chosen and labeled for function axes. This is useful in seeing
##'   how certain linear predictor values cannot be solved for using inverse
##'   linear interpolation on the (requested linear predictor values, function
##'   values at these lp values). When this happens you will see \code{NA}s in
##'   the \code{verbose} output, and the corresponding tick marks will not
##'   appear in the nomogram.
##' @param total.min Setting the minimal value in the total point axis on the nomogram.
##' @param total.max Setting the maximal value in the total point axis.
##' @param mikeomit The predictor variables specified by their names here will
##'   not be shown in the nomogram. The predicted outcome based on this
##'   reduced nomogram would be the same as if users were using the full
##'   version of the nomogram by entering the some values for the predictors
##'   remaining in the reduced nomogram but adjusted values for the hiden
##'   predictors so that 0 points will be achieved from these hiden predictor
##'   variables in the full nomogram.
##' @return A list of class \code{"nomogram"} that contains information used in
##'   plotting the axes. Please see \code{\link[rms]{nomogram}} for details.
##' @author Changhong Yu, Michael Kattan, Ph.D \cr Department of Quantitative
##'   Health Sciences\cr Cleveland Clinic\cr
##' @author Frank Harrell\cr
##' Department of Biostatistics\cr
##' Vanderbilt University\cr
##' \email{f.harrell@@vanderbilt.edu}
##'
##' @export
##' @seealso \code{\link[rms]{nomogram}} \code{\link[QHScrnomo]{nomogram.mk6}} \code{\link[QHScrnomo]{crr.fit}}
##' @references Banks J: Nomograms. Encylopedia of Statistical Sciences, Vol 6.
##'   Editors: S Kotz and NL Johnson.  New York: Wiley; 1985.
##'
##' Lubsen J, Pool J, van der Does, E: A practical device for the application
##'   of a diagnostic or prognostic function.  Meth. Inform. Med. 17:127--129;
##'   1978.
##'
##' Wikipedia: Nomogram, \url{https://en.wikipedia.org/wiki/Nomogram}.
##'
##' Michael W. Kattan, Glenn Heller and Murray F. Brennan (2003). A
##'   competing-risks nomogram \cr for sarcoma-specific death following local
##'   recurrence. Statistics in Medicine. \code{Stat Med}. 2003;22:3515-3525.
##' @examples
##' data(prostate.dat)
##' dd <- datadist(prostate.dat)
##' options(datadist = "dd")
##' prostate.f <- cph(Surv(TIME_EVENT,EVENT_DOD == 1) ~ TX  + rcs(PSA,3) +
##'            BX_GLSN_CAT +  CLIN_STG + rcs(AGE,3) +
##'            RACE_AA, data = prostate.dat,
##'            x = TRUE, y= TRUE, surv=TRUE,time.inc = 144)
##' prostate.crr <- crr.fit(prostate.f,cencode = 0,failcode = 1)
##' ## make a CRR nomogram
##' nomogram.crr(prostate.crr,failtime = 120,lp=FALSE,
##' funlabel = "Predicted 10-year cumulative incidence")
##'
##' @keywords models regression hplot
##'
nomogram.crr <-
  function(
      fit,
      failtime,
      ci = TRUE,
      ...,
      adj.to,
      lp = TRUE,
      lp.at,
      lplabel = "Linear Predictor",
      fun.at,
      fun.lp.at,
      funlabel = "Predicted Value",
      fun.side,
      interact = NULL,
      intercept = 1,
      conf.int = FALSE,
      col.conf = c(1, 12),
      conf.space = c(0.08, 0.2),
      conf.lp = c("representative", "all", "none"),
      est.all = TRUE,
      abbrev = FALSE,
      minlength = 4,
      maxscale = 100,
      nint = 10,
      label.every = 1,
      force.label = FALSE,
      xfrac = 0.35,
      cex.axis = 0.85,
      cex.var = 1,
      col.grid = FALSE,
      vnames = c("labels", "names"),
      varname.label = TRUE,
      varname.label.sep = "=",
      ia.space = 0.7,
      tck = -0.009,
      lmgp = 0.4,
      omit = NULL,
      naxes,
      points.label = "Points",
      total.points.label = "Total Points",
      total.sep.page = FALSE,
      total.fun,
      verbose = FALSE,
      total.min,
      total.max,
      mikeomit = NULL
    ) {

    # Check for fit
    if(missing(fit))
      stop("Please supply a model fit from crr.fit")

    # Check if the object was fit from crr.fit
    if(!inherits(fit, "cmprsk"))
      stop("The fit is not a 'cmprsk' object fit from crr.fit")

    # Check for a time point
    if(missing(failtime))
      stop("Please specify one or more time points for computing the event probability.")

    # Get the number of axes to draw
    nt <- length(failtime)

    # Extract the rms::cph object
    cph.f <- fit$cph.f

    # Assign the crr.fit coefficients to the rms::cph object
    nm <- names(cph.f$coefficients)
    cph.f$coefficients <- fit$coef
    names(cph.f$coefficients) <- nm

    # Set up list to hold transformation functions
    func <- mapply(list, rep(NA, nt))

    # Set a string for transformation function depending on failure vs. survival probability
    ci_string <- ""
    if(!ci)
      ci_string <- "1-"

    # Iterate the requested time points
    for(i in seq_len(nt)) {

      # Create and parse the string template of the transformation function for this time point
      expr <- parse(text = paste0("function(x)", ci_string, "nomo2.crr(x + cph.f$center, fit, time = ", failtime[i], ")"))

      # Set the function into the list
      func[[i]] <- eval(expr)

    }

    # Pass arguments to workhorse
    nomogram.mk6(
      cph.f,
      ...,
      adj.to = adj.to,
      lp = lp,
      lp.at = lp.at,
      lplabel = lplabel,
      fun = func,
      fun.at = fun.at,
      fun.lp.at = fun.lp.at,
      funlabel = funlabel,
      fun.side = fun.side,
      interact = interact,
      intercept = intercept,
      conf.int = conf.int,
      col.conf = col.conf,
      conf.space = conf.space,
      conf.lp = conf.lp,
      est.all = est.all,
      abbrev = abbrev,
      minlength = minlength,
      maxscale = maxscale,
      nint = nint,
      label.every = label.every,
      force.label = force.label,
      xfrac = xfrac,
      cex.axis = cex.axis,
      cex.var = cex.var,
      col.grid = col.grid,
      vnames = vnames,
      varname.label = varname.label,
      varname.label.sep = varname.label.sep,
      ia.space = ia.space,
      tck = tck,
      lmgp = lmgp,
      naxes = naxes,
      points.label = points.label,
      total.points.label = total.points.label,
      total.sep.page = total.sep.page,
      total.fun = total.fun,
      verbose = verbose,
      total.min = total.min,
      total.max = total.max,
      mikeomit = mikeomit
    )

  }

##' Construct a Nomogram for a Regression Model
##'
##' Draws a partial nomogram that can be used to manually obtain predicted values. This is a modified version of \code{\link[rms]{nomogram}}.
##'
##' @param fit A regression model that was created with \code{library(rms)}
##' in effect, and (usually) with \code{options(datadist = "object.name")} in
##' effect.
##' @param ... Settings of variables to use in constructing axes.
##' If \code{datadist} was in effect, the default is to use
##' \code{pretty(total range, nint)} for continuous variables, and the class
##' levels for discrete ones.
##' For \code{legend.nomabbrev}, \code{\dots} specifies optional parameters to
##' pass to \code{legend}.  Common ones are \code{bty = "n"} to suppress
##' drawing the box.  You may want to specify a non-proportionally spaced font
##' (e.g., courier) number if abbreviations are more than one letter long.
##' This will make the abbreviation definitions line up (e.g., specify
##' \code{font = 2}, the default for courier).  Ignored for \code{print}.
##' @param adj.to   If you didn't define \code{datadist} for all predictors,
##' you will have to define adjustment settings for the undefined ones, e.g.
##' \code{adj.to= list(age = 50, sex = "female")}.
##' @param lp  Set to \code{FALSE} to suppress creation of an axis for scoring
##' \eqn{X\beta}{X beta}.
##' @param lp.at If \code{lp=TRUE}, \code{lp.at} may specify a vector of
##' settings of \eqn{X\beta}{X beta}.
##' Default is to use \code{pretty(range of linear predictors, nint)}.
##' @param lplabel label for linear predictor axis.
##' Default is \code{"Linear Predictor"}.
##' @param fun on another axis.  If more than one transformation is plotted,
##' put them in a list, e.g. \code{list(function(x) x/2, function(x) 2*x)}.
##' Any function values equal to \code{NA} will be ignored.
##' @param fun.at function values to label on axis.  Default \code{fun}
##' evaluated at \code{lp.at}.   If more than one \code{fun} was specified,
##' using a vector for \code{fun.at} will cause all functions to be evaluated
##' at the same argument values.  To use different values, specify a list of
##' vectors for \code{fun.at}, with elements corresponding to the different
##' functions (lists of vectors also applies to \code{fun.lp.at} and
##' \code{fun.side}).
##' @param fun.lp.at If you want to
##' evaluate one of the functions at a different set of linear predictor
##' values than may have been used in constructing the linear predictor axis,
##' specify a vector or list of vectors
##' of linear predictor values at which to evaluate the function.  This is
##' especially useful for discrete functions.  The presence of this attribute
##' also does away with the need for \code{nomogram} to compute numerical
##' approximations of the inverse of the function.  It also allows the
##' user-supplied function to return \code{factor} objects, which is useful
##' when e.g. a single tick mark position actually represents a range.
##' If the \code{fun.lp.at} parameter is present, the \code{fun.at}
##' vector for that function is ignored.
##' @param funlabel label for \code{fun} axis.  If more than one function was
##' given but funlabel is of length one, it will be duplicated as needed.
##' If \code{fun} is a list of functions for which you specified names (see the
##' final example below), these names will be used as labels.
##' @param fun.side a vector or list of vectors of \code{side} parameters for
##' the \code{axis} function for labeling function values.
##' Values may be 1 to position a tick mark label below the axis (the default),
##' or 3 for above the axis.  If for example an axis has 5 tick mark labels
##' and the second and third will run into each other, specify
##' \code{fun.side=c(1,1,3,1,1)} (assuming only one function is specified as
##' \code{fun}).
##' @param interact When a continuous variable interacts with a discrete one,
##' axes are constructed so that the continuous variable moves within the axis,
##' and separate axes represent levels of interacting factors.  For
##' interactions between two continuous variables, all but the axis variable
##' must have discrete levels defined in \code{interact}.
##' For discrete interacting factors, you may specify levels to use in
##' constructing the multiple axes.  For continuous interacting factors,
##' you must do this.  Examples: \code{interact = list(age = seq(10,70,by=10),
##' treat = c("A","B","D"))}.
##' @param intercept for models such as the ordinal logistic model with
##' multiple intercepts, specifies which one to use in evaluating the linear
##' predictor.
##' @param conf.int confidence levels to display for each scoring.
##' Default is \code{FALSE} to display no confidence limits.
##' Setting \code{conf.int} to \code{TRUE} is the same as
##' setting it to \code{c(0.7, 0.9)},
##' with the line segment between the 0.7 and 0.9 levels shaded using
##' gray scale.
##' @param col.conf colors corresponding to \code{conf.int}.
##' Use fractions for gray scale(for UNIX S-PLUS).
##' @param conf.space a 2-element vector with the vertical range within which
##' to draw confidence bars, in units of 1=spacing between main bars.  Four
##' heights are used within this range (8 for the linear predictor if more than
##' 16 unique values were evaluated), cycling them among separate confidence
##' intervals to reduce overlapping
##' @param conf.lp default is \code{"representative"} to group all linear
##' predictors evaluated into deciles, and to show, for the linear predictor
##' confidence intervals, only the mean linear predictor within the deciles
##' along with the median standard error within the deciles.  Set
##' \code{conf.lp = "none"} to suppress confidence limits for the linear
##' predictors, and to \code{"all"} to show all confidence limits.
##' @param est.all To plot axes for only the subset of variables named in
##'  \code{\dots}, set \code{est.all = FALSE}.  Note: This option only works
##' when zero has a special meaning for the variables that are omitted from the
##' graph
##' @param abbrev Set to \code{TRUE} to use the \code{\link{abbreviate}}
##' function to abbreviate levels of categorical factors, both for labeling
##' tick marks and for axis titles. If you only want to abbreviate certain
##' predictor variables, set \code{abbrev} to a vector of character strings
##' containing their names.
##' @param minlength \code{\link{abbreviate}} function. If you set
##' \code{minlength = 1}, the letters of the alphabet are used to label tick
##' marks for categorical predictors, and all letters are drawn no matter how
##' close together they are.  For labeling axes (interaction settings),
##' \code{minlength = 1} causes \code{minlength = 4} to be used
##' @param maxscale default maximum point score is 100
##' @param nint number of intervals to label for axes representing continuous
##' variables.
##' See \code{\link{pretty}}.
##' @param label.every Specify \code{label.every = i} to label on every
##' \code{i}th tick mark
##' @param force.label set to \code{TRUE} to force every tick mark intended to
##' be labeled to have a label plotted (whether the labels run into each other
##' or not)
##' @param xfrac fraction of horizontal plot to set aside for axis titles
##' @param cex.axis character size for tick mark labels
##' @param cex.var character size for axis titles (variable names)
##' @param col.grid If left unspecified, no vertical reference lines are drawn.
##' Specify a vector of length one (to use the same color for both minor and
##' major reference lines) or two (corresponding to the color for the major and
##' minor divisions, respectively) containing colors, to cause vertical
##' reference lines to the top points scale to be drawn.  For R, a good choice
##' is \code{col.grid = gray(c(0.8, 0.95))}.
##' @param vnames By default, variable labels are used to label axes.  Set
##' \code{vnames = "names"} to instead use variable names.
##' @param varname.label In constructing axis titles for interactions, the
##' default is to add \code{(interacting.varname = level)} on the right.
##' Specify \code{varname.label = FALSE} to instead use \code{"(level)"}.
##' @param varname.label.sep If \code{varname.label = TRUE}, you can change the
##' separator to something other than
##' \code{=} by specifying this parameter.
##' @param ia.space When multiple axes are draw for levels of interacting
##' factors, the default is to group combinations related to a main effect.
##' This is done by spacing the axes for the second to last of these
##' within a group only
##' 0.7 (by default) of the way down as compared with normal space of 1 unit.
##' @param tck see \code{tck} under \code{\link{par}}
##' @param tcl length of tick marks in nomogram
##' @param lmgp spacing between numeric axis labels and axis
##' (see \code{\link{par}} for \code{mgp})
##' @param omit vector of character strings containing names of variables for
##' which to suppress drawing axes.  Default is to show all variables.
##' @param naxes  maximum number of axes to allow on one plot.  If the nomogram
##' requires more than one \dQuote{page}, the \dQuote{Points} axis will be
##' repeated at the top of each page when necessary.
##' @param points.label a character string giving the axis label for the points
##' scale
##' @param total.points.label  a character string giving the axis label for
##' the total points scale
##' @param total.sep.page set to \code{TRUE} to force the total points and
##' later axes to be placed on a separate page
##' @param total.fun  a user-provided function that will be executed before the
##' total points axis is drawn.  Default is not toe xecute a function.
##' This is useful e.g. when \code{total.sep.page = TRUE} and you wish to use
##' \code{locator} to find the coordinates for positioning an abbreviation
##' legend before it's too late and a new page is started (i.e.,
##' \code{total.fun = function() print(locator(1))}).
##' @param verbose set to \code{TRUE} to get printed output detailing how tick
##' marks are chosen and labeled for function axes.  This is useful in seeing
##' how certain linear predictor values cannot be solved for using inverse
##' linear interpolation on the (requested linear predictor values, function
##' values at these lp values).  When this happens you will see \code{NA}s in
##' the verbose output, and the corresponding tick marks will not appear in
##' the nomogram.
##' @param cap.labels logical: should the factor labels have their first
##' letter capitalized?
##' @param total.min the minimum point for the total point axis
##' @param total.max the maxmum point for the total point axis
##' @param survtime specified survival time for the predicted survival
##' probability
##' @param mikeomit a modified version of \code{omit}
##'
##' @return A \code{\link[rms]{nomogram}}
##' @export
##' @note Used internally for \code{\link[QHScrnomo]{nomogram.crr}}. See \code{\link[rms]{nomogram}}.
##' @references
##' Banks J: Nomograms. Encylopedia of Statistical Sciences, Vol 6.
##' Editors: S Kotz and NL Johnson.  New York: Wiley; 1985.
##'
##' Lubsen J, Pool J, van der Does, E: A practical device for the application
##' of a diagnostic or prognostic function.  Meth. Inform. Med. 17:127--129;
##' 1978.
##'
##' Wikipedia: Nomogram, \url{https://en.wikipedia.org/wiki/Nomogram}.
##'
##' @seealso \code{\link[QHScrnomo]{nomogram.crr}} \code{\link{rms}} \code{\link[rms]{nomogram}} \code{\link{plot.summary.rms}} \code{\link{axis}} \code{\link{pretty}} \code{\link{approx}}
##'
##' @keywords graphics
##'
##' @examples
##' data(prostate.dat)
##' dd <- datadist(prostate.dat)
##' options(datadist = "dd")
##' prostate.f <- cph(Surv(TIME_EVENT,EVENT_DOD == 1) ~ TX  + rcs(PSA,3) +
##'            BX_GLSN_CAT +  CLIN_STG + rcs(AGE,3) +
##'            RACE_AA, data = prostate.dat,
##'            x = TRUE, y= TRUE, surv=TRUE,time.inc = 144)
##' ## make a cph nomogram
##' nomogram.mk6(prostate.f, survtime=120, lp=FALSE,
##' funlabel = "Predicted 10-year cumulative incidence")
##'
nomogram.mk6 <-
  function(
      fit,
      ...,
      adj.to,
      lp = TRUE,
      lp.at,
      lplabel = "Linear Predictor",
      fun,
      fun.at,
      fun.lp.at,
      funlabel = "Predicted Value",
      fun.side,
      interact = NULL,
      intercept = 1,
      conf.int = FALSE,
      col.conf = c(1, 12),
      conf.space = c(0.08, 0.2),
      conf.lp = c("representative", "all", "none"),
      est.all = TRUE,
      abbrev = FALSE,
      minlength = 4,
      maxscale = 100,
      nint = 10,
      label.every = 1,
      force.label = FALSE,
      xfrac = 0.35,
      cex.axis = 0.85,
      cex.var = 1,
      col.grid = NULL,
      vnames = c("labels", "names"),
      varname.label = TRUE,
      varname.label.sep = "=",
      ia.space = 0.7,
      tck = NA,
      tcl = -0.25,
      lmgp = 0.4,
      omit = NULL,
      naxes,
      points.label = "Points",
      total.points.label = "Total Points",
      total.sep.page = FALSE,
      total.fun,
      verbose = FALSE,
      cap.labels = FALSE,
      total.min,
      total.max,
      survtime,
      mikeomit = NULL
  ) {

    if (!missing(survtime)) {
      # lp <- FALSE
      surv <- Survival(fit)
      fun <- function(x) surv(survtime, x)
      assign("surv", surv)
      assign("survtime", survtime)
    }
    if (any(stats::na.omit(match(attr(fit, "class"), "lrm"))) &
        missing(fun)) {
      # lp <- FALSE
      fun <- function(x) 1 / (1 + exp(-x))
    }
    ols <- any(stats::na.omit(match(attr(fit, "class"), "ols")))
    # if (!missing(total.min) && !missing(total.max) && lp && !ols)
    # stop("Total points control combined with lp is not setup yet.",
    #       "Pick one.")

    conf.lp <- match.arg(conf.lp)
    vnames <- match.arg(vnames)
    abb <- (is.logical(abbrev) && abbrev) || is.character(abbrev)
    if (is.logical(conf.int) && conf.int) {
      conf.int <- c(0.7, 0.9)
    }
    if (!is.logical(conf.int) && (length(conf.int) != length(col.conf))) {
      stop("conf.int and col.conf must have same length")
    }
    oldpar <- Hmisc::oPar()
    mgp <- oldpar$mgp
    mar <- oldpar$mar
    graphics::par(mgp = c(mgp[1], lmgp, mgp[3]), mar = c(
      mar[1], 1.1, mar[3],
      mar[4]
    ))
    on.exit(Hmisc::setParNro(oldpar))
    tck2 <- tck / 2
    tcl2 <- tcl / 2
    tck3 <- tck / 3
    tcl3 <- tcl / 3
    se <- FALSE
    if (any(conf.int > 0)) {
      se <- TRUE
      zcrit <- stats::qnorm((conf.int + 1) / 2)
      bar <- function(x, y, zcrit, se, col.conf, nlev = 4) {
        y <- rep(seq(y[1], y[2], length = nlev),length.out = length(x))
        for (j in seq(1,length(x))) {
          xj <- x[j]
          yj <- y[j]
          W <- c(0, zcrit) * se[j]
          for (i in seq(1,length(zcrit))) {
            graphics::segments(
              xj - W[i + 1], yj, xj - W[i], yj,
              col = col.conf[i], lwd = 1
            )
            graphics::segments(
              xj + W[i + 1], yj, xj + W[i], yj,
              col = col.conf[i], lwd = 1
            )
          }
        }
      }
    }
    nfun <- if (missing(fun)) {
      0
    } else if (is.list(fun)) {
      length(fun)
    } else {
      1
    }
    if (nfun > 1 && length(funlabel) == 1) {
      funlabel <- rep(funlabel, nfun)
    }
    if (nfun > 0 && is.list(fun) && length(names(fun))) {
      funlabel <- names(fun)
    }
    if (!missing(fun.at)) {
      if (!is.list(fun.at)) {
        fun.at <- rep(list(fun.at), nfun)
      }
    }
    if (!missing(fun.lp.at)) {
      if (!is.list(fun.lp.at)) {
        fun.lp.at <- rep(list(fun.lp.at), nfun)
      }
    }
    if (!missing(fun.side)) {
      if (!is.list(fun.side)) {
        fun.side <- rep(list(fun.side), nfun)
      }
      if (any(!(unlist(fun.side) %in% c(1, 3)))) {
        stop("fun.side must contain only the numbers 1 and 3")
      }
    }
    at <- fit$Design
    if (!length(at)) {
      at <- getOldDesign(fit)
    }
    assume <- at$assume.code
    if (any(assume == 10)) {
      stop("does not currently work with matrix factors in model")
    }
    name <- at$name
    names(assume) <- name
    parms <- at$parms
    label <- if (vnames == "labels") {
      at$label
    } else {
      name
    }
    if (any(d <- duplicated(name))) {
      stop("duplicated variable names:", name[d])
    }
    label <- name
    if (vnames == "labels") {
      label <- at$label
      if (any(d <- duplicated(label))) {
        stop("duplicated variable labels:", label[d])
      }
    }
    ia <- at$interactions
    factors <- list(...)
    nf <- length(factors)
    which <- if (est.all) {
      seq(1,length(assume))[assume != 8]
    } else {
      seq(1,length(assume))[assume != 8 & assume != 9]
    }
    if (nf > 0) {
      jw <- charmatch(names(factors), name, 0)
      if (any(jw == 0)) {
        stop(
          "factor name(s) not in the design:",
          names(factors)[jw == 0])
      }
      if (!est.all) {
        which <- jw
      }
    }
    Limval <- Getlim(at, allow.null = TRUE, need.all = FALSE)
    values <- Limval$values
    lims <- Limval$limits[c(6, 2, 7), , drop = FALSE]
    lims <- unclass(lims)
    for (i in seq(1,length(lims))) {
      if (is.factor(lims[[i]])) {
        lims[[i]] <- as.character(lims[[i]])
      }
    }
    attr(lims, "class") <- "data.frame"
    ucat <- rep(FALSE, length(assume))
    names(ucat) <- name
    for (i in seq(1,length(assume))[assume != 5 & assume < 8]) {
      ucat[i] <- !is.null(V <- values[[name[i]]])
      if (ucat[i]) {
        parms[[name[i]]] <- V
      }
    }
    discrete <- assume == 5 | assume == 8 | ucat
    names(discrete) <- name
    nrp <- Hmisc::num.intercepts(fit)
    Intercept <- if (nrp > 0) {
      fit$coefficients[intercept]
    } else if (!is.null(fit$center)) {
      -fit$center
    } else {
      0
    }
    intercept.offset <- if (nrp < 2) {
      0
    } else {
      fit$coefficients[intercept] - fit$coefficients[1]
    }
    settings <- list()
    for (i in which[assume[which] < 9]) {
      ni <- name[i]
      z <- factors[[ni]]
      lz <- length(z)
      if (lz < 2) {
        settings[[ni]] <- value.chk(
          at, i, NA, -nint, Limval,
          type.range = "full"
        )
      } else if (lz > 0 && any(is.na(z))) {
        stop("may not specify NA as a variable value")
      }
      if (lz == 1) {
        lims[2, i] <- z
      } else if (lz > 1) {
        settings[[ni]] <- z
        if (is.null(lims[[ni]]) || is.na(lims[2, ni])) {
          lims[[ni]] <- c(NA, z[1], NA)
          stop(
            "adjustment values for ", ni,
            " not defined in datadist;",
            "taken to be first value specified (",
            z[1], ")",
          )
        }
      }
    }
    adj <- lims[2, , drop = FALSE]
    if (!missing(adj.to)) {
      for (nn in names(adj.to)) adj[[nn]] <- adj.to[[nn]]
    }
    isna <- vapply(adj, is.na, FALSE)
    if (any(isna)) {
      stop(
        "adjustment values not defined here or with datadist for",
        name[assume != 9][isna]
      )
    }
    num.lines <- 0
    space.used <- 0
    entities <- 0
    set <- list()
    nset <- character(0)
    iset <- 0
    start <- len <- NULL
    end <- 0
    main.effects <- which[assume[which] < 8]
    if (any(assume == 9)) {
      main.effects <- main.effects[
        order(10 * discrete[main.effects] + (
          name[main.effects] %in% names(interact)))]
    }
    rel <- related.predictors(at)
    already.done <- structure(rep(FALSE, length(name)), names = name)
    for (i in main.effects) {
      nam <- name[i]
      ## changhong add
      if (already.done[nam] || (nam %in% omit) || (nam %in% mikeomit)) {
        next
      }
      r <- if (length(rel[[nam]])) {
        sort(rel[[nam]])
      } else {
        NULL
      }
      if (length(r) == 0) {
        num.lines <- num.lines + 1
        space.used <- space.used + 1
        entities <- entities + 1
        x <- list()
        x[[nam]] <- settings[[nam]]
        iset <- iset + 1
        attr(x, "info") <- list(
          predictor = nam, effect.name = nam,
          type = "main"
        )
        set[[iset]] <- x
        nset <- c(nset, label[i])
        start <- c(start, end + 1)
        n <- length(settings[[nam]])
        len <- c(len, n)
        end <- end + n
        NULL
      } else {
        namo <- name[r]
        s <- !(name[r] %in% names(interact))
        if (any(s)) {
          if (is.null(interact)) {
            interact <- list()
          }
          for (j in r[s]) {
            nj <- name[j]
            if (discrete[j]) {
              interact[[nj]] <- parms[[nj]]
            }
            NULL
          }
          s <- !(name[r] %in% names(interact))
        }
        if (any(s)) {
          stop(
            "factors not defined in interact=list(...):",
            name[r[s]]
          )
        }
        combo <- expand.grid(interact[namo])
        oldClass(combo) <- NULL
        acombo <- combo
        if (abb) {
          for (n in if (is.character(abbrev)) {
            abbrev
          } else {
            names(acombo)
          }) {
            if (discrete[n]) {
              acombo[[n]] <- abbreviate(
                parms[[n]],
                minlength = if (minlength ==1) {
                  4
                } else {
                  minlength
                })[combo[[n]]]
            }
          }
        }
        for (n in names(combo)) {
          if (is.factor(combo[[n]])) {
            combo[[n]] <- as.character(combo[[n]])
            acombo[[n]] <- as.character(acombo[[n]])
            NULL
          }
        }
        entities <- entities + 1
        already.done[namo] <- TRUE
        for (k in seq(1,length(combo[[1]]))) {
          num.lines <- num.lines + 1
          space.used <- space.used + if (k == 1) {
            1
          } else {
            ia.space
          }
          x <- list()
          x[[nam]] <- settings[[nam]]
          for (nm in namo) x[[nm]] <- combo[[nm]][k]
          iset <- iset + 1
          set.name <- paste(nam, " (", sep = "")
          for (j in seq(1,length(acombo))) {
            set.name <- paste(set.name, if (varname.label) {
              paste(namo[j], varname.label.sep, sep = "")
            } else {
              ""
            }, format(acombo[[j]][k]), sep = "")
            if (j < length(acombo)) {
              set.name <- paste(set.name, " ", sep = "")
            }
          }
          set.name <- paste(set.name, ")", sep = "")
          ia.names <- NULL
          for (j in r) {
            ia.names <- c(ia.names, name[interactions.containing(
              at,
              j
            )])
          }
          ia.names <- unique(ia.names)
          attr(x, "info") <- list(predictor = nam, effect.name = c(
            nam,
            namo[assume[namo] != 8], ia.names
          ),
          type = if (k == 1) {
            "first"
          } else {
            "continuation"
          })
          set[[iset]] <- x
          nset <- c(nset, set.name)
          start <- c(start, end + 1)
          n <- length(settings[[nam]])
          len <- c(len, n)
          end <- end + n
          NULL
        }
        NULL
      }
    }
    xadj <- unclass(Design.levels(adj, at))
    for (k in seq(1,length(xadj))) xadj[[k]] <- rep(xadj[[k]], sum(len))
    j <- 0
    for (S in set) {
      j <- j + 1
      ns <- names(S)
      nam <- names(S)
      for (k in seq(1,length(nam))) {
        xadj[[nam[k]]][start[j]:(start[j] + len[j] - 1)] <- S[[k]]
        NULL
      }
    }
    xadj <- structure(
      xadj, class = "data.frame",
      row.names = as.character(seq(1,sum(len))))
    xx <- predictDesign(
      fit,
      newdata = xadj, type = "terms",
      center.terms = FALSE, se.fit = FALSE, kint = intercept
    )
    if (any(is.infinite(xx))) {
      stop(
        "variable limits and transformations are such that an",
        "infinite axis value has resulted.\nRe-run specifying",
        "your own limits to variables.")
    }
    if (se) {
      xse <- predictDesign(
        fit,
        newdata = xadj, se.fit = TRUE,
        kint = intercept
      )
    }
    R <- matrix(NA, nrow = 2, ncol = length(main.effects), dimnames = list(
      NULL,
      name[main.effects]
    ))
    R[1, ] <- 1e+30
    R[2, ] <- -1e+30
    for (i in seq(1,num.lines)) {
      is <- start[i]
      ie <- is + len[i] - 1
      s <- set[[i]]
      setinfo <- attr(s, "info")
      nam <- setinfo$effect.name
      xt <- xx[is:ie, nam]
      if (length(nam) > 1) {
        xt <- apply(xt, 1, sum)
      }
      set[[i]]$Xbeta <- xt
      r <- range(xt)
      pname <- setinfo$predictor
      R[1, pname] <- min(R[1, pname], r[1])
      R[2, pname] <- max(R[2, pname], r[2])
      if (se) {
        set[[i]]$Xbeta.whole <- xse$linear.predictors[is:ie]
        set[[i]]$se.fit <- xse$se.fit[is:ie]
        NULL
      }
      NULL
    }
    R <- R[, R[1, ] < 1e+30, drop = FALSE]
    sc <- maxscale / max(R[2, ] - R[1, ])
    xl <- -xfrac * maxscale
    Intercept <- Intercept + sum(R[1, ])
    if (missing(naxes)) {
      naxes <- if (total.sep.page) {
        max(space.used + 1, nfun + lp + 1)
      } else {
        space.used + 1 + nfun + lp + 1
      }
    }
    Format <- function(x) {
      f <- character(l <- length(x))
      for (i in seq(1,l)) f[i] <- format(x[i])
      f
    }
    newpage <- function(
    naxes, xl, maxscale, cex.var, nint, space.used,
    col.grid, cex.axis, tck, tck2, tcl, tcl2,
    label.every, force.label, points = TRUE,
    points.label = "Points", usr) {
      y <- naxes - 1
      plot(
        0, 0,
        xlim = c(xl, maxscale), ylim = c(0, y), type = "n",
        axes = FALSE, xlab = "", ylab = ""
      )
      if (!missing(usr)) {
        graphics::par(usr = usr)
      }
      if (!points) {
        return(y + 1)
      }
      ax <- c(0, maxscale)
      graphics::text(xl, y, points.label, adj = 0, cex = cex.var)
      x <- pretty(ax, n = nint)
      dif <- x[2] - x[1]
      x2 <- seq((x[1] + x[2]) / 2, max(x), by = dif)
      x2 <- sort(c(x2 - dif / 4, x2, x2 + dif / 4))
      if (length(col.grid)) {
        graphics::segments(
          x, y, x, y - space.used,
          col = col.grid[1],
          lwd = 1
        )
        graphics::segments(
          x2, y, x2, y - space.used,
          col = col.grid[-1],
          lwd = 1
        )
      }
      axisf(
        3,
        at = x, pos = y, cex = cex.axis, tck = tck,
        tcl = tcl, label.every = label.every,
        force.label = force.label, padj = 0
      )
      axisf(
        3,
        at = x2, labels = FALSE, pos = y, tck = tck2,
        tcl = tcl2, cex = cex.axis
      )
      y
    }
    y <- newpage(
      naxes, xl, maxscale, cex.var, nint, space.used,
      col.grid, cex.axis, tck, tck2, tcl, tcl2,
      label.every = label.every,
      force.label = force.label, points.label = points.label
    )
    i <- 0
    Abbrev <- list()
    for (S in set) {
      i <- i + 1
      setinfo <- attr(S, "info")
      type <- setinfo$type
      y <- y - (if (type == "continuation") {
        ia.space
      } else {
        1
      })
      if (y < -0.05) {
        y <- newpage(
          naxes, xl, maxscale, cex.var, nint,
          space.used, col.grid, cex.axis, tck, tck2, tcl,
          tcl2,
          label.every = label.every, force.label = force.label,
          points.label = points.label
        ) - (if (type == "continuation") {
          ia.space
        } else {
          1
        })
      }
      graphics::text(
        xl, y, paste(
          Hmisc::strgraphwrap(
            nset[[i]],
            abs(xl), cex = cex.var),
          collapse = "\n"
        ), adj = 0, cex = cex.var)
      x <- S[[1]]
      nam <- names(S)[1]
      fx <- if (is.character(x)) {
        x
      } else {
        Hmisc::sedit(Format(x), " ", "")
      }
      if (abb && discrete[nam] &&
          (is.logical(abbrev) || nam %in% abbrev)) {
        old.text <- fx
        fx <- if (abb && minlength == 1) {
          letters[seq(1,length(fx))]
        } else {
          abbreviate(fx, minlength = minlength)
        }
        Abbrev[[nam]] <- list(abbrev = fx, full = old.text)
      }
      j <- match(nam, name, 0)
      #if (any(j == 0)) stop("program logic error 1")
      is <- start[i]
      ie <- is + len[i] - 1
      xt <- (S$Xbeta - R[1, nam]) * sc
      set[[i]]$points <- xt
      r <- rle(xt)
      if (any(r$length > 1)) {
        is <- 1
        for (j in r$length) {
          ie <- is + j - 1
          if (j > 1) {
            fx[ie] <- if (discrete[nam] || ie < length(xt)) {
              paste(fx[is], "-", fx[ie], sep = "")
            } else {
              paste(fx[is], "+", sep = "")
            }
            fx[is:(ie - 1)] <- ""
            xt[is:(ie - 1)] <- NA
          }
          is <- ie + 1
        }
        fx <- fx[!is.na(xt)]
        xt <- xt[!is.na(xt)]
      }
      side <- c(1, 3)
      padj <- c(1, 0)
      new.mgp <- vector(mode = "list", 2)
      new.mgp[[2]] <- c(0, lmgp, 0)
      new.mgp[[1]] <- new.mgp[[2]] - c(0, 0.6, 0)
      ch <- if (length(xt) > 2) {
        c(FALSE, FALSE, diff(diff(xt) > 0) != 0)
      } else {
        rep(FALSE, length(xt))
      }
      if (discrete[nam] && length(xt) > 1) {
        j <- order(xt)
        graphics::lines(range(xt), rep(y, 2))
        for (k in c(1,2)) {
          is <- j[seq(k, length(j), by = 2)]
          new.labs <- if (cap.labels) {
            Hmisc::capitalize(fx[is])
          } else {
            fx[is]
          }
          axisf(
            side[k],
            at = xt[is], labels = new.labs,
            pos = y, cex = cex.axis, tck = tck, tcl = tcl,
            force.label = force.label ||
              (abb && minlength == 1 && (
                is.logical(abbrev) || nam %in% abbrev)),
            disc = TRUE, mgp = new.mgp[[k]], padj = padj[k]
          )
          if (se) {
            bar(
              xt[is], if (k == 1) {
                y - conf.space - 0.32
              } else {
                y + conf.space + 0.32
              }, zcrit, sc * S$se.fit[is],
              col.conf
            )
          }
        }
      } else if (!any(ch)) {
        axisf(
          1,
          at = xt, labels = fx, pos = y, cex = cex.axis,
          tck = tck, tcl = tcl, mgp = new.mgp[[1]],
          label.every = label.every, force.label = force.label,
          disc = discrete[nam], padj = padj[1]
        )
        if (se) {
          bar(
            xt, y + conf.space, zcrit, sc * S$se.fit,
            col.conf
          )
        }
      } else {
        graphics::lines(range(xt), rep(y, 2))
        j <- seq(1,length(ch))[ch]
        if (max(j) < length(ch)) {
          j <- c(j, length(ch) + 1)
        }
        flag <- 1
        is <- 1
        for (k in j) {
          ie <- k - 1
          axisf(
            side[flag],
            at = xt[is:ie], labels = fx[is:ie],
            pos = y, cex = cex.axis, tck = tck, tcl = tcl,
            label.every = label.every, force.label = force.label,
            mgp = new.mgp[[flag]], disc = discrete[nam],
            padj = padj[flag]
          )
          if (se) {
            bar(
              xt[is:ie], if (side[flag] == 1) {
                y - conf.space - 0.32
              } else {
                y + conf.space + 0.32
              }, zcrit, sc * S$se.fit[is:ie],
              col.conf
            )
          }
          flag <- if (flag == 2) {
            1
          } else {
            2
          }
          is <- ie + 1
        }
      }
    }
    # browser()
    if (missing(lp.at)) {
      xb <- fit$linear.predictors
      if (!length(xb)) {
        xb <- fit$fitted.values
      }
      if (!length(xb)) {
        xb <- fit$fitted
      }
      if (!length(xb)) {
        stop(
          "lp.at not given and fit did not store",
          "linear.predictors or fitted.values")
      }
      if (nrp > 1) {
        xb <- xb + intercept.offset
      }
      lp.at <- pretty(range(xb), n = nint)
    }
    sum.max <- if (entities == 1) {
      maxscale
    } else {
      max(maxscale, sc * max(lp.at - Intercept))
    }
    x <- pretty(c(0, sum.max), n = nint)
    new.max <- max(x)
    xl.old <- xl
    xl <- -xfrac * new.max
    u <- graphics::par()$usr
    if (!missing(total.fun)) {
      total.fun()
    }
    usr <- c(xl * u[1] / xl.old, new.max * u[2] / maxscale, u[3:4])
    graphics::par(usr = usr)
    x.double <- seq(x[1], new.max, by = (x[2] - x[1]) / 5)
    y <- y - 1
    if (y < -0.05 || total.sep.page) {
      y <- newpage(
        naxes, xl, maxscale, cex.var, nint, space.used,
        col.grid, cex.axis, tck, tck2, tcl, tcl2,
        label.every = label.every,
        force.label = force.label, points = FALSE, usr = usr
      ) -
        1
    }
    graphics::text(xl, y, total.points.label, adj = 0, cex = cex.var)
    # changhong edit here
    if (missing(total.max) || missing(total.min)) {
      axisf(
        1,
        at = x, pos = y, cex = cex.axis, tck = tck, tcl = tcl,
        label.every = label.every, force.label = force.label,
        mgp = c(0, lmgp - 0.6, 0), padj = 1
      )
      axisf(
        1,
        at = x.double, labels = FALSE, pos = y, tck = tck2,
        tcl = tcl2, cex = cex.axis
      )
    } else {
      axisf(
        1,
        at = x, pos = y,
        labels = round(seq(
          total.min,total.max,
          by = (total.max - total.min) / (length(x) - 1)
        )),
        cex = cex.axis, tck = tck, tcl = tcl,
        label.every = label.every, force.label = force.label,
        mgp = c(0, lmgp - 0.6, 0), padj = 1
      )
      axisf(
        1,
        at = x.double, labels = FALSE, pos = y, tck = tck2,
        tcl = tcl2, cex = cex.axis
      )
    }
    iset <- iset + 1
    nset <- c(nset, "total.points")
    set[[iset]] <- list(x = x, y = y)
    # browser()
    if (lp) {
      x2 <- seq(lp.at[1], max(lp.at), by = (lp.at[2] - lp.at[1]) / 2)
      scaled.x <- (lp.at - Intercept) * sc
      scaled.x2 <- (x2 - Intercept) * sc
      y <- y - 1
      if (y < -0.05) {
        y <- newpage(
          naxes, xl, maxscale, cex.var, nint,
          space.used, col.grid, cex.axis, tck, tck2, tcl,
          tcl2,
          label.every = label.every, force.label = force.label,
          points = FALSE, usr = usr
        ) - 1
      }
      graphics::text(xl, y, lplabel, adj = 0, cex = cex.var)
      # changhong edit here on 05/12/2010
      if (missing(total.max) || missing(total.min)) {
        axisf(
          1,
          at = scaled.x, labels = Format(lp.at), pos = y,
          cex = cex.axis, tck = tck, tcl = tcl,
          label.every = label.every,
          force.label = force.label, mgp = c(
            0, lmgp - 0.6,
            0
          ), padj = 1
        )
      } else {
        axisf(
          1,
          at = ((scaled.x - total.min) * (
            max(x) - min(x))) / (total.max - total.min),
          labels = Format(lp.at), pos = y,
          cex = cex.axis, tck = tck, tcl = tcl,
          label.every = label.every,
          force.label = force.label, mgp = c(
            0, lmgp - 0.6,
            0
          ), padj = 1
        )
      }
      axisf(
        1,
        at = scaled.x2, labels = FALSE, tck = tck2,
        tcl = tcl2, pos = y, cex = cex.axis
      )
      iset <- iset + 1
      nset <- c(nset, "lp")
      set[[iset]] <- list(x = scaled.x, y = y, x.real = lp.at)
      if (se && conf.lp != "none") {
        xxb <- NULL
        xse <- NULL
        for (S in set) {
          xxb <- c(xxb, S$Xbeta.whole)
          xse <- c(xse, S$se.fit)
        }
        i <- order(xxb)
        if (length(xxb) < 16 | conf.lp == "representative") {
          nlev <- 4
          w <- 1
        } else {
          nlev <- 8
          w <- 2
        }
        if (conf.lp == "representative") {
          deciles <- Hmisc::cut2(xxb[i], g = 10)
          mean.xxb <- tapply(xxb[i], deciles, mean)
          median.se <- tapply(xse[i], deciles, stats::median)
          bar((mean.xxb - Intercept) * sc, y + c(
            conf.space[1],
            conf.space[1] + w * diff(conf.space)
          ), zcrit,
          sc * median.se, col.conf,
          nlev = nlev
          )
        } else {
          bar((xxb[i] - Intercept) * sc, y + c(
            conf.space[1],
            conf.space[1] + w * diff(conf.space)
          ), zcrit,
          sc * xse[i], col.conf,
          nlev = nlev
          )
        }
      }
    }
    # browser()
    if (nfun > 0) {
      if (!is.list(fun)) {
        fun <- list(fun)
      }
      i <- 0
      for (func in fun) {
        i <- i + 1
        if (!missing(fun.lp.at)) {
          xseq <- fun.lp.at[[i]]
          fat <- func(xseq)
          w <- xseq
        } else {
          if (missing(fun.at)) {
            fat <- pretty(func(range(lp.at)), n = nint)
          } else {
            fat <- fun.at[[i]]
          }
          if (verbose) {
            cat(
              "Function", i,
              "values at which to place tick marks:\n")
            print(fat)
          }
          xseq <- seq(min(lp.at), max(lp.at), length = 1000)
          fu <- func(xseq)
          s <- !is.na(fu)
          w <- stats::approx(fu[s], xseq[s], fat, ties = mean)$y
          if (verbose) {
            cat("Estimated inverse function values (lp):\n")
            print(w)
          }
        }
        s <- !(is.na(w) | is.na(fat))
        w <- w[s]
        fat <- fat[s]
        fat.orig <- fat
        fat <- if (is.category(fat)) {
          as.character(fat)
        } else {
          Format(fat)
        }
        scaled <- (w - Intercept) * sc
        y <- y - 1
        if (y < -0.05) {
          y <- newpage(
            naxes, xl, maxscale, cex.var, nint,
            space.used, col.grid, cex.axis, tck, tck2,
            tcl, tcl2,
            label.every = label.every, force.label = force.label,
            points = FALSE, usr = usr
          ) - 1
        }
        graphics::text(xl, y, funlabel[i], adj = 0, cex = cex.var)
        sides <- if (missing(fun.side)) {
          rep(1, length(fat))
        } else {
          (fun.side[[i]])[s]
        }
        if (length(sides) != length(fat)) {
          stop(
            "fun.side vector not same length as",
            "fun.at or fun.lp.at")
        }

        # changhong edit here
        for (jj in seq(1,length(fat))) {
          if (missing(total.max) || missing(total.min)) {
            graphics::axis(
              sides[jj],
              at = scaled[jj], label = fat[jj],
              pos = y, cex.axis = cex.axis, tck = tck, tcl = tcl,
              mgp = if (sides[jj] == 1) {
                c(0, lmgp - 0.6, 0)
              } else {
                c(0, lmgp, 0)
              }, padj = if (sides[jj] == 1) {
                1
              } else {
                0
              }
            )
            graphics::lines(range(scaled), rep(y, 2))
          } else {
            graphics::axis(
              sides[jj],
              at = ((
                scaled[jj] - total.min) * (
                  max(x) - min(x))) /
                (total.max - total.min),
              label = fat[jj],
              pos = y, cex.axis = cex.axis,
              tck = tck, tcl = tcl,
              mgp = if (sides[jj] == 1) {
                c(0, lmgp - 0.6, 0)
              } else {
                c(0, lmgp, 0)
              }, padj = if (sides[jj] == 1) {
                1
              } else {
                0
              }
            )
            graphics::lines(((
              range(scaled) - total.min) *
                (max(x) - min(x))) /
                (total.max - total.min), rep(y, 2))
          }
        }

        iset <- iset + 1
        nset <- c(nset, funlabel[i])
        set[[iset]] <- list(x = scaled, y = y, x.real = fat.orig)
      }
    }
    names(set) <- nset
    set$abbrev <- Abbrev
    oldClass(set) <- "nomogram"
    invisible(set)

  }

nomo2.crr <-
  function(x, f.crr, time) {
    assign("f.crr", f.crr)
    assign("time", time)
    vapply(x, function(x) pred2.crr(f.crr, x, time), 0.5)
  }

is.category <- function (x)
  length(attr(x, "levels")) > 0 && mode(x) == "numeric"

getOldDesign <- function(fit) {
  at <- attr(fit$terms,'Design')
  if(is.null(at))
    stop('fit was not created by a Design library fitting function')
  at
}

Design.levels <- function(df, at) {
  ac <- at$assume.code
  for (nn in names(df)) {
    j <- match(nn, at$name, 0)
    if (j > 0) {
      if ((ac[j] == 5 | ac[j] == 8) & length(lev <- at$parms[[nn]])) {
        df[[nn]] <- factor(df[[nn]], lev)
      }
    }
  }
  df
}

value.chk <- rms:::value.chk
axisf <- rms:::axisf

Try the QHScrnomo package in your browser

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

QHScrnomo documentation built on May 29, 2024, 9:21 a.m.