|
| 1 | +#ds.glmPredict |
| 2 | +#' @title applies predict.glm() to a serverside glm object |
| 3 | +#' @description apply native R's predict.glm() function to a serverside |
| 4 | +#' glm object previously created using ds.glmSLMA. |
| 5 | +#' @details Clientside function calling a single assign function (glmPredictDS.as) |
| 6 | +#' and a single aggregate function (glmPredictDS.ag). ds.glmPredict applies |
| 7 | +#' the native R predict.glm function to a |
| 8 | +#' glm object that has already been created on the serverside by fitting ds.glmSLMA. |
| 9 | +#' This is precisely the same as the glm object created in native R by fitting a glm |
| 10 | +#' using the glm function. Crucially, if ds.glmSLMA was originally applied to |
| 11 | +#' multiple studies the glm object created on each study is based solely |
| 12 | +#' on data from that study. ds.glmPredict has two distinct actions. First, the |
| 13 | +#' call to the assign function applies the standard predict.glm function of |
| 14 | +#' native R to the glm object on the serverside and writes all the output |
| 15 | +#' that would normally be generated by predict.glm to a newobj on the serverside. |
| 16 | +#' Because no critical information is passed to the clientside, there are no |
| 17 | +#' disclosure issues associated with this action. Any standard DataSHIELD functions |
| 18 | +#' can then be applied to the newobj to interpret the output. For example, it could |
| 19 | +#' be used as the basis for regression diagnostic plots. Second, the call |
| 20 | +#' to the aggregate function creates a non-disclosive summary of all the |
| 21 | +#' information held in the newobj created by the assign function |
| 22 | +#' and returns this summary to the clientside. For example, the full list of |
| 23 | +#' predicted/fitted values generated by the model could be disclosive. |
| 24 | +#' So although the newobj holds the full vector of fitted values, only the |
| 25 | +#' total number of values, the total number of valid (non-missing) values, |
| 26 | +#' the number of missing values, the mean and standard deviation of all valid |
| 27 | +#' values and the 5%, 10%, 25%, 50%, 75%, 90%, and 95% quantiles of these values |
| 28 | +#' are returned to the clientside by the aggregate function. The non-DataSHIELD |
| 29 | +#' arguments of ds.glmPredict are precisely the equivalent to those of predict.glm |
| 30 | +#' in native R and so all detailed information can be found using help(predict.glm) |
| 31 | +#' in native R. |
| 32 | +#' @param glmname is a character string identifying the glm object on serverside to |
| 33 | +#' which predict.glm is to be applied. Equivalent to <object> argument in native R's |
| 34 | +#' predict.glm which is described as: a fitted object of class inheriting from 'glm'. |
| 35 | +#' @param newdataname is a character string identifying an (optional) dataframe on |
| 36 | +#' the serverside in which to look for new covariate values with which to predict. |
| 37 | +#' If omitted, |
| 38 | +#' the original fitted linear predictors from the original glm fit are used as the |
| 39 | +#' basis of prediction. Precisely equivalent to the <newdata> argument in the |
| 40 | +#' predict.glm function in native R. |
| 41 | +#' @param output.type a character string taking the values 'response', 'link' |
| 42 | +#' or 'terms'. The value 'response' generates predictions on the scale of the |
| 43 | +#' original outcome, e.g. as proportions in a logistic regression. These |
| 44 | +#' are often called 'fitted values'. The value |
| 45 | +#' 'link' generates predictions on the scale of the linear predictor, e.g. |
| 46 | +#' log-odds in logistic regression, log-rate or log-count in Poisson regression. |
| 47 | +#' The predictions using 'response' and 'link' are identical for a standard |
| 48 | +#' Gaussian model with an identity link. The value 'terms' returns either |
| 49 | +#' fitted values or predicted values on the link scale based not on |
| 50 | +#' the whole linear predictor but on separate 'terms'. So, if age |
| 51 | +#' is modelled as a five level factor, one of the output components |
| 52 | +#' will relate to predictions (fitted values or link scale predictions) based on |
| 53 | +#' all five levels of age simultaneously. Any simple covariate (e.g. not a composite |
| 54 | +#' factor) will be treated as a term in its own right. ds.glmPredict's <output.type> |
| 55 | +#' argument is precisely equivalent to the <type> argument in |
| 56 | +#' native R's predict.glm function. |
| 57 | +#' @param se.fit logical if standard errors for the fitted predictions are required. |
| 58 | +#' Defaults to FALSE when the output contains only a vector (or vectors) of predicted |
| 59 | +#' values. If TRUE, the output also contains corresponding vectors for the standard |
| 60 | +#' errors of the predicted values, and a single value reporting the scale parameter |
| 61 | +#' of the model. ds.glmPredict's <se.fit> argument is precisely equivalent to |
| 62 | +#' the corresponding argument in predict.glm in native R. |
| 63 | +#' argument is equivalent to the <type> argument in native R's predict.glm function. |
| 64 | +#' @param dispersion numeric value specifying the dispersion of the GLM fit to be assumed |
| 65 | +#' in computing the standard errors. If omitted, that returned by |
| 66 | +#' summary applied to the glm object is used. e.g. if <dispersion> is unspecified |
| 67 | +#' the dispersion assumed for a logistic regression or Poisson model is 1. But if |
| 68 | +#' dispersion is set to 4, the standard errors of the predictions will all be |
| 69 | +#' multiplied by 2 (i.e. sqrt(4)). This is useful in making predictions from |
| 70 | +#' models subject to overdispersion. ds.glmPredict's <dispersion> argument |
| 71 | +#' is precisely equivalent to the corresponding argument in predict.glm in native R. |
| 72 | +#' @param terms a character vector specifying a subset of terms to return in the |
| 73 | +#' prediction. Only applies if output.type='terms'. ds.glmPredict's <terms> |
| 74 | +#' argument is precisely equivalent to the corresponding argument in predict.glm |
| 75 | +#' in native R. |
| 76 | +#' @param na.action character string determining what should be done with missing |
| 77 | +#' values in the data.frame identified by <newdataname>. Default is na.pass which |
| 78 | +#' predicts from the specified new data.frame with all NAs left in place. na.omit |
| 79 | +#' removes all rows containing NAs. na.fail stops the function if there are any |
| 80 | +#' NAs anywhere in the data.frame. For further details see help in native R. |
| 81 | +#' @param newobj a character string specifying the name of the serverside object |
| 82 | +#' to which the output object from the call to ds.glmPredict is to be written |
| 83 | +#' in each study. If no <newobj> argument is specified, the output |
| 84 | +#' object on the serverside defaults to the name "predict_glm". |
| 85 | +#' @param datasources specifies the particular 'connection object(s)' to use. |
| 86 | +#' e.g. if you have several data sets in the sources you are working with |
| 87 | +#' called opals.a, opals.w2, and connection.xyz, you can choose which of |
| 88 | +#' these to work with. The call 'datashield.connections_find()' lists all of |
| 89 | +#' the different datasets available and if one of these is called 'default.connections' |
| 90 | +#' that will be the dataset used by default if no other dataset is specified. If you |
| 91 | +#' wish to change the connections you wish to use by default the call |
| 92 | +#' datashield.connections_default('opals.a') will set 'default.connections' |
| 93 | +#' to be 'opals.a' and so in the absence of specific instructions to the contrary |
| 94 | +#' (e.g. by specifiying a particular dataset to be used via the <datasources> |
| 95 | +#' argument) all subsequent function calls will be to the datasets held in opals.a. |
| 96 | +#' If the <datasources> argument is specified, it should be set without |
| 97 | +#' inverted commas: e.g. datasources=opals.a or datasources=default.connections. |
| 98 | +#' The <datasources> argument also allows you to apply a function solely to a subset |
| 99 | +#' of the studies/sources you are working with. For example, the second source |
| 100 | +#' in a set of three, can be specified using a call such as datasources=connection.xyz[2]. |
| 101 | +#' On the other hand, if you wish to specify solely the first and third sources, the |
| 102 | +#' appropriate call will be datasources=connections.xyz[c(1,3)] |
| 103 | +#' @return ds.glmPredict calls the serverside assign function glmPredictDS.as |
| 104 | +#' which writes a new object to the serverside containing output precisely equivalent to |
| 105 | +#' predict.glm in native R. The name for this serverside object is given by |
| 106 | +#' the newobj argument or if that argument is missing or null it is called "predict_glm". |
| 107 | +#' In addition, ds.glmPredict calls the serverside aggregate function glmPredictDS.ag |
| 108 | +#' which returns an object containing non-disclosive summary statistics relating |
| 109 | +#' either to a single prediction vector called fit or, if se.fit=TRUE, of two vectors |
| 110 | +#' 'fit' and 'se.fit' - the latter containing the standard errors of the predictions |
| 111 | +#' in 'fit'. The non-disclosive summary statistics for the vector(s) include: |
| 112 | +#' length, the total number of valid (non-missing) values, |
| 113 | +#' the number of missing values, the mean and standard deviation of the valid |
| 114 | +#' values and the 5%, 10%, 25%, 50%, 75%, 90%, and 95% quantiles of these values. In addition, |
| 115 | +#' the output always includes: the name of the serverside glm object being predicted from, |
| 116 | +#' the name - if one was specified - of the dataframe being used as the basis for predictions, |
| 117 | +#' the output.type specified ('link', 'response' or 'terms'), the value of the |
| 118 | +#' dispersion parameter if one had been specified and the residual scale parameter (which is |
| 119 | +#' multipled by sqrt(dispersion parameter) if one has been set). If output.type = 'terms', |
| 120 | +#' the summary statistics for the fit and se.fit vectors are replaced by equivalent |
| 121 | +#' summary statistics for each column in fit and se.fit matrices which each have k columns |
| 122 | +#' if k terms are being summarised. |
| 123 | +#' @author Paul Burton, for DataSHIELD Development Team 13/08/20 |
| 124 | +#' @export |
| 125 | + |
| 126 | +ds.glmPredict <- function(glmname=NULL,newdataname=NULL,output.type="response", |
| 127 | + se.fit = FALSE, dispersion = NULL, terms = NULL, |
| 128 | + na.action = "na.pass", newobj=NULL, datasources=NULL){ |
| 129 | + |
| 130 | + # look for DS connections |
| 131 | + if(is.null(datasources)){ |
| 132 | + datasources <- datashield.connections_find() |
| 133 | + } |
| 134 | + |
| 135 | + # check that <glmname> is set |
| 136 | + if(is.null(glmname)){ |
| 137 | + error.message<-"<glmname> is not set, please specify it as a character string containing the name of a valid glm class object on the serverside" |
| 138 | + return(error.message) |
| 139 | + } |
| 140 | + |
| 141 | + # check that <output.type> is correctly set |
| 142 | + if((output.type!="link")&&(output.type!="response")&&(output.type!="terms")){ |
| 143 | + error.message<-"<output.type> is not correctly set, please specify it as one of three character strings: 'link', 'response', or 'terms'" |
| 144 | + return(error.message) |
| 145 | + } |
| 146 | + |
| 147 | + # check that <na.action> is correctly set |
| 148 | + if((na.action!="na.fail")&&(na.action!="na.omit")&&(na.action!="na.exclude")&&(na.action!="na.pass")){ |
| 149 | + error.message<-"<na.action> is not correctly set, please specify it as one of four character strings: 'na.fail', 'na.omit', 'na.exclude' or 'na.pass'" |
| 150 | + return(error.message) |
| 151 | + } |
| 152 | + |
| 153 | + #provide value for newobj if none specified |
| 154 | + if(is.null(newobj)) |
| 155 | + { |
| 156 | + newobj<-"predict_glm" |
| 157 | + } |
| 158 | + |
| 159 | + calltext<-call("glmPredictDS.as", glmname, newdataname, output.type, |
| 160 | + se.fit, dispersion, terms, na.action) |
| 161 | + |
| 162 | + DSI::datashield.assign(datasources, newobj, calltext) |
| 163 | + |
| 164 | +############################################################################################################# |
| 165 | +#DataSHIELD CLIENTSIDE MODULE: CHECK KEY DATA OBJECTS SUCCESSFULLY CREATED # |
| 166 | + # |
| 167 | +#SET APPROPRIATE PARAMETERS FOR THIS PARTICULAR FUNCTION # |
| 168 | +test.obj.name<-newobj # |
| 169 | + # |
| 170 | +#TRACER # |
| 171 | +#return(test.obj.name) # |
| 172 | +#} # |
| 173 | + # |
| 174 | + # |
| 175 | +# CALL SEVERSIDE FUNCTION # |
| 176 | +calltext <- call("testObjExistsDS", test.obj.name) # |
| 177 | + # |
| 178 | +object.info<-DSI::datashield.aggregate(datasources, calltext) # |
| 179 | + # |
| 180 | +# CHECK IN EACH SOURCE WHETHER OBJECT NAME EXISTS # |
| 181 | +# AND WHETHER OBJECT PHYSICALLY EXISTS WITH A NON-NULL CLASS # |
| 182 | +num.datasources<-length(object.info) # |
| 183 | + # |
| 184 | + # |
| 185 | +obj.name.exists.in.all.sources<-TRUE # |
| 186 | +obj.non.null.in.all.sources<-TRUE # |
| 187 | + # |
| 188 | +for(j in 1:num.datasources){ # |
| 189 | + if(!object.info[[j]]$test.obj.exists){ # |
| 190 | + obj.name.exists.in.all.sources<-FALSE # |
| 191 | + } # |
| 192 | + if(object.info[[j]]$test.obj.class=="ABSENT"){ # |
| 193 | + obj.non.null.in.all.sources<-FALSE # |
| 194 | + } # |
| 195 | + } # |
| 196 | + # |
| 197 | +if(obj.name.exists.in.all.sources && obj.non.null.in.all.sources){ # |
| 198 | + # |
| 199 | + return.message<- # |
| 200 | + paste0("A data object <", test.obj.name, "> has been created in all specified data sources") # |
| 201 | + # |
| 202 | + # |
| 203 | + }else{ # |
| 204 | + # |
| 205 | + return.message.1<- # |
| 206 | + paste0("Error: A valid data object <", test.obj.name, "> does NOT exist in ALL specified data sources") # |
| 207 | + # |
| 208 | + return.message.2<- # |
| 209 | + paste0("It is either ABSENT and/or has no valid content/class,see return.info above") # |
| 210 | + # |
| 211 | + return.message.3<- # |
| 212 | + paste0("Please use ds.ls() to identify where missing") # |
| 213 | + # |
| 214 | + # |
| 215 | + return.message<-list(return.message.1,return.message.2,return.message.3) # |
| 216 | + # |
| 217 | + } # |
| 218 | + # |
| 219 | + calltext <- call("messageDS", test.obj.name) # |
| 220 | + studyside.message<-DSI::datashield.aggregate(datasources, calltext) # |
| 221 | + # |
| 222 | + no.errors<-TRUE # |
| 223 | + for(nd in 1:num.datasources){ # |
| 224 | + if(studyside.message[[nd]]!="ALL OK: there are no studysideMessage(s) on this datasource"){ # |
| 225 | + no.errors<-FALSE # |
| 226 | + } # |
| 227 | + } # |
| 228 | + # |
| 229 | + # |
| 230 | + if(no.errors){ # |
| 231 | + validity.check<-paste0("<",test.obj.name, "> appears valid in all sources") # |
| 232 | + print(list(is.object.created=return.message,validity.check=validity.check)) # |
| 233 | + } # |
| 234 | + # |
| 235 | +if(!no.errors){ # |
| 236 | + validity.check<-paste0("<",test.obj.name,"> invalid in at least one source. See studyside.messages:") # |
| 237 | + print(list(is.object.created=return.message,validity.check=validity.check, # |
| 238 | + studyside.messages=studyside.message)) # |
| 239 | + } # |
| 240 | + # |
| 241 | +#END OF CHECK OBJECT CREATED CORECTLY MODULE # |
| 242 | +############################################################################################################# |
| 243 | + |
| 244 | + calltext<-call("glmPredictDS.ag", glmname, newdataname, output.type, |
| 245 | + se.fit, dispersion, terms, na.action) |
| 246 | + |
| 247 | + output<-DSI::datashield.aggregate(datasources, calltext) |
| 248 | + |
| 249 | + return(output) |
| 250 | + |
| 251 | +} |
| 252 | + |
| 253 | +#ds.glmPredict |
0 commit comments