DataSHIELD v5 new functionality
Bit.ly quick link
Overview
This tutorial demonstrates some of the new functionality and features included in the v5 version of DataSHIELD. We will focus on data manipulation (including dataset sorting and subsetting, data conversion from long to wide and from wide to long formats, merging of datasets, etc), data visualisations (e.g. scatter plots and heatmap plots) and generalised linear model regressions through DataSHIELD.
Before we continue with our session, log onto the DataSHIELD Training Environment
Start the Opal Servers
Your trainer will have started your Opal training servers in the cloud for you.
Logging into the DataSHIELD Client Portal
Your trainer will give you the IP address of the DataSHIELD client portal ending :8787
They will also provide you with a username and password to login with.
Start R/RStudio and load the required packages
# load libraries
library(opal)
Loading required package: RCurl
Loading required package: bitops
Loading required package: rjson
Loading required package: mime
library(dsBaseClient)Part A: Data Manipulation and Data Visualisations
Build your login dataframe and start the Opal Servers
In part A of this demonstration we use the SURVIVAL.EXPAND_WITH_MISSING datasets that include synthetic longitundinal data. Each dataset is in a long format, which means that each row is one time point per subject. So each subject (individual) will have data in multiple rows.
Build your login dataframe
server <- c("study1", "study2", "study3") # The VM names
url <- c("http://XXX.XXX.XXX.XXX:8080","http://XXX.XXX.XXX.XXX:8080","http://XXX.XXX.XXX.XXX:8080") # The fixed IP addresses of the training VMs
user <- "administrator"
password <- "datashield_test&"
table <- c("SURVIVAL.EXPAND_WITH_MISSING1","SURVIVAL.EXPAND_WITH_MISSING2","SURVIVAL.EXPAND_WITH_MISSING3") # The data tables used in this tutorial
my_logindata <- data.frame(server,url,user,password,table)The output below indicates that each of the three training datasets
study1, study2 and study3contain the same variables listed underVariables assigned:
Build your login dataframe
> opals <- datashield.login(logins=my_logindata, assign=TRUE)
Logging into the collaborating servers
No variables have been specified.
All the variables in the opal table
(the whole dataset) will be assigned to R!
Assigning data:
study1...
study2...
study3...
Variables assigned:
study1--id, study.id, time.id, starttime, endtime, survtime, cens, age.60, female, noise.56, pm10.16, bmi.26
study2--id, study.id, time.id, starttime, endtime, survtime, cens, age.60, female, noise.56, pm10.16, bmi.26
study3--id, study.id, time.id, starttime, endtime, survtime, cens, age.60, female, noise.56, pm10.16, bmi.26Basic statistics and data manipulations
We can use functions that we have learned in the "Introduction to DataSHIELD" tutorial to get an overview of some characteristics of the three datasets. For example, we can use the ds.dim function to see the dimensions of the datasets and the ds.colnames function to get the names of the variables from each dataset.
Build your login dataframe
> ds.dim(x = 'D')
$`dimensions of D in study1`
[1] 2060 12
$`dimensions of D in study2`
[1] 1640 12
$`dimensions of D in study3`
[1] 2688 12
$`dimensions of D in combined studies`
[1] 6388 12
> ds.colnames(x='D', datasources = opals)
$study1
[1] "id" "study.id" "time.id" "starttime" "endtime" "survtime" "cens" "age.60" "female"
[10] "noise.56" "pm10.16" "bmi.26"
$study2
[1] "id" "study.id" "time.id" "starttime" "endtime" "survtime" "cens" "age.60" "female"
[10] "noise.56" "pm10.16" "bmi.26"
$study3
[1] "id" "study.id" "time.id" "starttime" "endtime" "survtime" "cens" "age.60" "female"
[10] "noise.56" "pm10.16" "bmi.26"
Reshape from long to wide format
We can use the ds.reShape function to convert a dataset from long to wide format. In the wide format, a subject’s repeated responses will be in a single row, and each response is in a separate column. The argument timevar.name specifies the column in the long format data that differentiates multiple records from the same subject, and the argument id.var specifies the column that identifies multiple records from the same subject. For more details you can see the function's help file.
Build your login dataframe
> ds.reShape(data.name='D', timevar.name='time.id', idvar.name='id', direction='wide', newobj="Dwide1", datasources=opals)
$is.object.created
[1] "A data object <Dwide1> has been created in all specified data sources"
$validity.check
[1] "<Dwide1> appears valid in all sources"
> ds.ls()
$study1
[1] "D" "Dwide1"
$study2
[1] "D" "Dwide1"
$study3
[1] "D" "Dwide1"
> ds.dim('Dwide1')
$`dimensions of Dwide1 in study1`
[1] 886 61
$`dimensions of Dwide1 in study2`
[1] 659 61
$`dimensions of Dwide1 in study3`
[1] 1167 61
$`dimensions of Dwide1 in combined studies`
[1] 2712 61
> ds.colnames('Dwide1')
$study1
[1] "id" "study.id.1" "starttime.1" "endtime.1" "survtime.1" "cens.1" "age.60.1"
[8] "female.1" "noise.56.1" "pm10.16.1" "bmi.26.1" "study.id.4" "starttime.4" "endtime.4"
[15] "survtime.4" "cens.4" "age.60.4" "female.4" "noise.56.4" "pm10.16.4" "bmi.26.4"
[22] "study.id.6" "starttime.6" "endtime.6" "survtime.6" "cens.6" "age.60.6" "female.6"
[29] "noise.56.6" "pm10.16.6" "bmi.26.6" "study.id.3" "starttime.3" "endtime.3" "survtime.3"
[36] "cens.3" "age.60.3" "female.3" "noise.56.3" "pm10.16.3" "bmi.26.3" "study.id.2"
[43] "starttime.2" "endtime.2" "survtime.2" "cens.2" "age.60.2" "female.2" "noise.56.2"
[50] "pm10.16.2" "bmi.26.2" "study.id.5" "starttime.5" "endtime.5" "survtime.5" "cens.5"
[57] "age.60.5" "female.5" "noise.56.5" "pm10.16.5" "bmi.26.5"
$study2
[1] "id" "study.id.1" "starttime.1" "endtime.1" "survtime.1" "cens.1" "age.60.1"
[8] "female.1" "noise.56.1" "pm10.16.1" "bmi.26.1" "study.id.3" "starttime.3" "endtime.3"
[15] "survtime.3" "cens.3" "age.60.3" "female.3" "noise.56.3" "pm10.16.3" "bmi.26.3"
[22] "study.id.2" "starttime.2" "endtime.2" "survtime.2" "cens.2" "age.60.2" "female.2"
[29] "noise.56.2" "pm10.16.2" "bmi.26.2" "study.id.4" "starttime.4" "endtime.4" "survtime.4"
[36] "cens.4" "age.60.4" "female.4" "noise.56.4" "pm10.16.4" "bmi.26.4" "study.id.5"
[43] "starttime.5" "endtime.5" "survtime.5" "cens.5" "age.60.5" "female.5" "noise.56.5"
[50] "pm10.16.5" "bmi.26.5" "study.id.6" "starttime.6" "endtime.6" "survtime.6" "cens.6"
[57] "age.60.6" "female.6" "noise.56.6" "pm10.16.6" "bmi.26.6" $study3
[1] "id" "study.id.1" "starttime.1" "endtime.1" "survtime.1" "cens.1" "age.60.1"
[8] "female.1" "noise.56.1" "pm10.16.1" "bmi.26.1" "study.id.4" "starttime.4" "endtime.4"
[15] "survtime.4" "cens.4" "age.60.4" "female.4" "noise.56.4" "pm10.16.4" "bmi.26.4"
[22] "study.id.2" "starttime.2" "endtime.2" "survtime.2" "cens.2" "age.60.2" "female.2"
[29] "noise.56.2" "pm10.16.2" "bmi.26.2" "study.id.3" "starttime.3" "endtime.3" "survtime.3"
[36] "cens.3" "age.60.3" "female.3" "noise.56.3" "pm10.16.3" "bmi.26.3" "study.id.5"
[43] "starttime.5" "endtime.5" "survtime.5" "cens.5" "age.60.5" "female.5" "noise.56.5"
[50] "pm10.16.5" "bmi.26.5" "study.id.6" "starttime.6" "endtime.6" "survtime.6" "cens.6"
[57] "age.60.6" "female.6" "noise.56.6" "pm10.16.6" "bmi.26.6"We can also choose for which variables to create multiple columns depending on the time.var by using the argument v.names and also we can use the argument drop if we want to drop some of the variables in the wide format.
Build your login dataframe
> ds.reShape(data.name='D', v.names='bmi.26', timevar.name='time.id', idvar.name='id', direction='wide', newobj="Dwide2", datasources=opals, sep='_')
$is.object.created
[1] "A data object <Dwide2> has been created in all specified data sources"
$validity.check
[1] "<Dwide2> appears valid in all sources"
> ds.reShape(data.name='D', v.names='pm10.16', timevar.name='time.id', idvar.name='id', direction='wide', newobj="Dwide3", datasources=opals, sep='_', drop=c('starttime','endtime','survtime'))
$is.object.created
[1] "A data object <Dwide3> has been created in all specified data sources"
$validity.check
[1] "<Dwide3> appears valid in all sources"
> ds.ls()
$study1
[1] "D" "Dwide1" "Dwide2" "Dwide3"
$study2
[1] "D" "Dwide1" "Dwide2" "Dwide3"
$study3
[1] "D" "Dwide1" "Dwide2" "Dwide3"
> ds.colnames('Dwide2')
$study1
[1] "id" "study.id" "starttime" "endtime" "survtime" "cens" "age.60" "female" "noise.56" "pm10.16"
[11] "bmi.26_1" "bmi.26_4" "bmi.26_6" "bmi.26_3" "bmi.26_2" "bmi.26_5"
$study2
[1] "id" "study.id" "starttime" "endtime" "survtime" "cens" "age.60" "female" "noise.56" "pm10.16"
[11] "bmi.26_1" "bmi.26_3" "bmi.26_2" "bmi.26_4" "bmi.26_5" "bmi.26_6"
$study3
[1] "id" "study.id" "starttime" "endtime" "survtime" "cens" "age.60" "female" "noise.56" "pm10.16"
[11] "bmi.26_1" "bmi.26_4" "bmi.26_2" "bmi.26_3" "bmi.26_5" "bmi.26_6"
> ds.colnames('Dwide3')
$study1
[1] "id" "study.id" "cens" "age.60" "female" "noise.56" "bmi.26" "pm10.16_1" "pm10.16_4" "pm10.16_6"
[11] "pm10.16_3" "pm10.16_2" "pm10.16_5"
$study2
[1] "id" "study.id" "cens" "age.60" "female" "noise.56" "bmi.26" "pm10.16_1" "pm10.16_3" "pm10.16_2"
[11] "pm10.16_4" "pm10.16_5" "pm10.16_6"
$study3
[1] "id" "study.id" "cens" "age.60" "female" "noise.56" "bmi.26" "pm10.16_1" "pm10.16_4" "pm10.16_2"
[11] "pm10.16_3" "pm10.16_5" "pm10.16_6"
Reshape from wide to long format
Using the same function we can convert a dataset from wide to long format if we set the argument direction to "long". In that case, we have to use the argument varying to specify which variables in the wide format, correspond to single variables in the long format.
Build your login dataframe
> ds.reShape(data.name='Dwide2', varying=list("bmi.26_1", "bmi.26_3", "bmi.26_2", "bmi.26_4", "bmi.26_5", "bmi.26_6"), direction='long', newobj="Dlong2", datasources=opals)
$is.object.created
[1] "A data object <Dlong2> has been created in all specified data sources"
$validity.check
[1] "<Dlong2> appears valid in all sources"
> ds.ls()
$study1
[1] "D" "Dlong2" "Dwide1" "Dwide2" "Dwide3"
$study2
[1] "D" "Dlong2" "Dwide1" "Dwide2" "Dwide3"
$study3
[1] "D" "Dlong2" "Dwide1" "Dwide2" "Dwide3"
> ds.dim('Dlong2')
$`dimensions of Dlong2 in study1`
[1] 5316 12
$`dimensions of Dlong2 in study2`
[1] 3954 12
$`dimensions of Dlong2 in study3`
[1] 7002 12
$`dimensions of Dlong2 in combined studies`
[1] 16272 12
> ds.colnames('Dlong2')
$study1
[1] "id" "study.id" "starttime" "endtime" "survtime" "cens" "age.60" "female" "noise.56" "pm10.16"
[11] "time" "bmi"
$study2
[1] "id" "study.id" "starttime" "endtime" "survtime" "cens" "age.60" "female" "noise.56" "pm10.16"
[11] "time" "bmi"
$study3
[1] "id" "study.id" "starttime" "endtime" "survtime" "cens" "age.60" "female" "noise.56" "pm10.16"
[11] "time" "bmi"Merge data frames
We can use the function ds.merge to merge (link) two data frames together based on common values in vectors defined by the arguments by.x.names and by.y.names.
Build your login dataframe
> ds.merge(x.name="Dwide2", y.name="Dwide3", by.x.names="id", by.y.names="id", sort=TRUE, newobj="Dwide_merged", datasources=opals)
$is.object.created
[1] "A data object <Dwide_merged> has been created in all specified data sources"
$validity.check
[1] "<Dwide_merged> appears valid in all sources"
> ds.ls()
$study1
[1] "D" "Dlong2" "Dwide1" "Dwide2" "Dwide3" "Dwide_merged"
$study2
[1] "D" "Dlong2" "Dwide1" "Dwide2" "Dwide3" "Dwide_merged"
$study3
[1] "D" "Dlong2" "Dwide1" "Dwide2" "Dwide3" "Dwide_merged"
> ds.colnames("Dwide_merged")
$study1
[1] "id" "study.id.x" "starttime" "endtime" "survtime" "cens.x" "age.60.x" "female.x" "noise.56.x"
[10] "pm10.16" "bmi.26_1" "bmi.26_4" "bmi.26_6" "bmi.26_3" "bmi.26_2" "bmi.26_5" "study.id.y" "cens.y"
[19] "age.60.y" "female.y" "noise.56.y" "bmi.26" "pm10.16_1" "pm10.16_4" "pm10.16_6" "pm10.16_3" "pm10.16_2"
[28] "pm10.16_5"
$study2
[1] "id" "study.id.x" "starttime" "endtime" "survtime" "cens.x" "age.60.x" "female.x" "noise.56.x"
[10] "pm10.16" "bmi.26_1" "bmi.26_3" "bmi.26_2" "bmi.26_4" "bmi.26_5" "bmi.26_6" "study.id.y" "cens.y"
[19] "age.60.y" "female.y" "noise.56.y" "bmi.26" "pm10.16_1" "pm10.16_3" "pm10.16_2" "pm10.16_4" "pm10.16_5"
[28] "pm10.16_6"
$study3
[1] "id" "study.id.x" "starttime" "endtime" "survtime" "cens.x" "age.60.x" "female.x" "noise.56.x"
[10] "pm10.16" "bmi.26_1" "bmi.26_4" "bmi.26_2" "bmi.26_3" "bmi.26_5" "bmi.26_6" "study.id.y" "cens.y"
[19] "age.60.y" "female.y" "noise.56.y" "bmi.26" "pm10.16_1" "pm10.16_4" "pm10.16_2" "pm10.16_3" "pm10.16_5"
[28] "pm10.16_6"
Dataframe manipulations
Another set of functions that allow data manipulation in DataSHIELD includes the functions ds.dataFrame, ds.dataFrameSort and ds.dataFrameSubset. The ds.dataFrame function creates a data frame from elemental components that can be pre-existing data frames, single variables and/or matrices. The ds.dataFrameSort function sorts a data frame using a specified sort key and the ds.dataFrameSubset function subsets a data frame by row or by column. See the following examples.
We can create a dataframe with only complete cases (i.e. the records in each row of a dataframe do not have any missing values).
Build your login dataframe
> ds.dim('D')
$`dimensions of D in study1`
[1] 2060 12
$`dimensions of D in study2`
[1] 1640 12
$`dimensions of D in study3`
[1] 2688 12
$`dimensions of D in combined studies`
[1] 6388 12
> ds.dim('Dlong2')
$`dimensions of Dlong2 in study1`
[1] 5316 12
$`dimensions of Dlong2 in study2`
[1] 3954 12
$`dimensions of Dlong2 in study3`
[1] 7002 12
$`dimensions of Dlong2 in combined studies`
[1] 16272 12
> ds.dataFrame(x='D', completeCases=TRUE, newobj='Db')
$is.object.created
[1] "A data object <Db> has been created in all specified data sources"
$validity.check
[1] "<Db> appears valid in all sources"
> ds.dataFrame(x='Dlong2', completeCases=TRUE, newobj='Dlong2b')
$is.object.created
[1] "A data object <Dlong2b> has been created in all specified data sources"
$validity.check
[1] "<Dlong2b> appears valid in all sources"> ds.dim('Db')
$`dimensions of Db in study1`
[1] 1974 12
$`dimensions of Db in study2`
[1] 1528 12
$`dimensions of Db in study3`
[1] 2529 12
$`dimensions of Db in combined studies`
[1] 6031 12
> ds.dim('Dlong2b')
$`dimensions of Dlong2b in study1`
[1] 1974 12
$`dimensions of Dlong2b in study2`
[1] 1528 12
$`dimensions of Dlong2b in study3`
[1] 2529 12
$`dimensions of Dlong2b in combined studies`
[1] 6031 12We can create a subset of a dataframe based on a binary variable.