Skip to content

Commit e6aeb46

Browse files
Merge pull request #240 from StuartWheater/v6.1-dev_glmpredict
Added 'ds.glmPredict' method
2 parents 0781572 + a76bdc0 commit e6aeb46

4 files changed

Lines changed: 412 additions & 1 deletion

File tree

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: dsBaseClient
22
Title: DataSHIELD Client Functions
3-
Version: 6.1.0-3
3+
Version: 6.1.0-5
44
Author: DataSHIELD Developers <datashield@newcastle.ac.uk>
55
Maintainer: DataSHIELD Developers <datashield@newcastle.ac.uk>
66
Description: DataSHIELD client functions for the client side.

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export(ds.dim)
2929
export(ds.exists)
3030
export(ds.exp)
3131
export(ds.glm)
32+
export(ds.glmPredict)
3233
export(ds.glmSLMA)
3334
export(ds.glmSummary)
3435
export(ds.glmerSLMA)

R/ds.glmPredict.R

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
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

Comments
 (0)