Skip to contents

Introducation

dsHelper contains a number of client-side functions designed to simplify data manipulation and analysis using DataSHIELD. Functions are grouped in the following families:

  • Descriptive functions
  • Data manipulation functions
  • Utility functions
  • Functions for trajectory models

This vignette demonstrates the first three families of function. Functions to assist with trajectory/mixed effect analysis are described in a separate tutorial.

#install.packages("remotes")
#library(remotes)
#install_github("timcadman/ds-helper")
#install_github("datashield/DSI")
#install_github("datashield/dsBaseClient")
#install.packages("DSMolgenisArmadillo")
#install.packages("tidyverse")
library(dsHelper)
library(DSI)
library(dsBaseClient)
library(DSMolgenisArmadillo)
library(tidyverse)

Data: simulated data based on LifeCycle variables

To demonstrate these functions we will use remote simulated data for three cohorts: ALSPAC, BCG & BiB.

If you are unsure how to log in, please consult the DataSHIELD vignette: [link]

url <- "https://armadillo-demo.molgenis.net"
token <- armadillo.get_token(url)

builder <- DSI::newDSLoginBuilder()

builder$append(
server = "alspac",
url = url,
table = "trajectories/data/alspac",
token = token,
driver = "ArmadilloDriver", 
profile = "xenon")

builder$append(
server = "bcg",
url = url,
table = "trajectories/data/bcg",
token = token,
driver = "ArmadilloDriver", 
profile = "xenon")

builder$append(
server = "bib",
url = url,
table = "trajectories/data/bib",
token = token,
driver = "ArmadilloDriver", 
profile = "xenon")

logindata <- builder$build()

conns <- DSI::datashield.login(logins = logindata, assign = TRUE, symbol = "data")
ds.colnames("data")
## $alspac
## [1] "id"                 "age"                "sex"                "ethnicity"          "father_socialclass"
## [6] "mother_education"   "weight"            
## 
## $bcg
## [1] "id"                 "age"                "sex"                "ethnicity"          "father_socialclass"
## [6] "mother_education"   "weight"            
## 
## $bib
## [1] "id"                 "age"                "sex"                "ethnicity"          "father_socialclass"
## [6] "mother_education"   "weight"

Data manipulation functions

Rename variables with dh.renameVars()

dh.renameVars allows you to change the names of one or more variables within a dataframe. For example, we can rename the variables “id”, and “ethnicity” with simpler names:

dh.renameVars(
  df = "data",
  current_names = c("ethnicity", "father_socialclass"), 
  new_names = c("race", "paternal_class"))
ds.colnames("data")
## $alspac
## [1] "id"               "age"              "sex"              "mother_education" "weight"           "race"            
## [7] "paternal_class"  
## 
## $bcg
## [1] "id"               "age"              "sex"              "mother_education" "weight"           "race"            
## [7] "paternal_class"  
## 
## $bib
## [1] "id"               "age"              "sex"              "mother_education" "weight"           "race"            
## [7] "paternal_class"

Removing columns with dh.dropCols()

dh.dropCols allows you to remove columns from a dataframe. The argument “type” allows you to specify whether to remove or to keep the provided variables. For example, we can remove the column “mat_ed”:

dh.dropCols(
  df = "data", 
  vars = "mother_education", 
  type = "remove")
ds.colnames("data")
## $alspac
## [1] "id"             "age"            "sex"            "weight"         "race"           "paternal_class"
## 
## $bcg
## [1] "id"             "age"            "sex"            "weight"         "race"           "paternal_class"
## 
## $bib
## [1] "id"             "age"            "sex"            "weight"         "race"           "paternal_class"

Alternatively, we can just to keep certain columns and remove everything else. In this example we keep the columns “sex”, “height_m” and “ivf”:

dh.dropCols(
  df = "data", 
  vars = c("id", "age", "sex", "weight"), 
  type = "keep")
ds.colnames("data")
## $alspac
## [1] "id"     "age"    "sex"    "weight"
## 
## $bcg
## [1] "id"     "age"    "sex"    "weight"
## 
## $bib
## [1] "id"     "age"    "sex"    "weight"

Creating strata of a variable with dh.makeStrata()

A common scenario is when you have repeat measurements of a variable, but want to create individual versions of that variable within different age bands.

For example, you may have a dataset with repeat measurments of height between ages 2-18. However, you want to create a new variable which is height between ages 4-6. This is quite a long process in datashield, as it involves creating a number of subsets. It is also necessary to deal with situations where a subject has >1 observation within the specified period.

In the example below, we create two variables for child height measured between (i) ages 4-6 and (ii) ages 7-10. Where children have more than one observation in either window, we opt to take the earliest measurement. This behaviour is controlled by the argument “mult_action”. You can also choose to take (i) the latest variable, or (ii) the variable closest to a particular time point. Finally, within each band we choose to take values >= to the lowest band, and < the highest band. This behaviour is controlled by the argument “band_action”. See ?dh.makeStrata for more information on the available arguments.

In this example it runs quite quickly, however if you are using many cohorts with large datasets it can take a considerable amount of time (30 - 60 min).

dh.makeStrata(
  df = "data",
  var_to_subset = "weight",
  age_var = "age", 
  bands = c(4, 6, 7, 10), 
  mult_action = "earliest",
  band_action = "ge_l", 
  id_var = "id", 
  new_obj = "height_data")
## Error in `left_join()`:
## ! Join columns in `x` must be present in the data.
## ✖ Problem with `cohort` and `suffix`.
ds.colnames("height_data")
## $alspac
## [1] "id"      "tmp_obj"
## 
## $bcg
## [1] "id"      "tmp_obj"
## 
## $bib
## [1] "id"      "tmp_obj"

The created dataset contains 5 variables: the id variable, two variables giving the height value within the specified bands (height_.6, height_.10) and two variables giving the age of measurement for these height variables (age.6, age.10).

We can then use ds.merge to join this dataframe back with our main data:

ds.merge(
  x.name = "data", 
  y.name = "height_data", 
  by.x = "id", 
  by.y = "id", 
  all.x = TRUE, 
  newobj = "data")
ds.colnames("data")
## $alspac
## [1] "id"      "age"     "sex"     "weight"  "tmp_obj"
## 
## $bcg
## [1] "id"      "age"     "sex"     "weight"  "tmp_obj"
## 
## $bib
## [1] "id"      "age"     "sex"     "weight"  "tmp_obj"

Transforming continuous variable to interquartile range using dh.makeIQR()

In some scenarios (e.g. when using an environmental exposures) we want to standardise a continuous variable by transforming it by its interquartile range. The formula is:

value(subject) / 75th percentile(sample) - 25th percentile(sample)

When using data from multiple cohorts there are two options for the denominator: (i) create IQR in each cohort using the interquartile range in that cohort, (ii) create IQR in each cohort by using the combined interquartile range across all cohorts. This is controlled by the argument type.

In this example we transform child weight. The created variable will have the suffix “iqr_c” if type is “separate” and “iqr_p” if type = “pooled”.

dh.makeIQR(
  df = "data",
  vars = "weight",
  type = "split",
  new_obj = "data")
ds.colnames("data")
## $alspac
## [1] "id"           "age"          "sex"          "weight"       "tmp_obj"      "weight_iqr_s"
## 
## $bcg
## [1] "id"           "age"          "sex"          "weight"       "tmp_obj"      "weight_iqr_s"
## 
## $bib
## [1] "id"           "age"          "sex"          "weight"       "tmp_obj"      "weight_iqr_s"
ds.summary("data$weight_iqr_s")
## $alspac
## $alspac$class
## [1] "numeric"
## 
## $alspac$length
## [1] 129681
## 
## $alspac$`quantiles & mean`
##        5%       10%       25%       50%       75%       90%       95%      Mean 
## 0.4091894 0.4749404 0.8753985 1.9270502 4.0167522 5.8588743 6.8073844 2.6408378 
## 
## 
## $bcg
## $bcg$class
## [1] "numeric"
## 
## $bcg$length
## [1] 12724
## 
## $bcg$`quantiles & mean`
##        5%       10%       25%       50%       75%       90%       95%      Mean 
## 0.4297604 0.4883779 0.6827643 1.2218283 1.6454341 1.9312762 2.0859876 1.2049523 
## 
## 
## $bib
## $bib$class
## [1] "numeric"
## 
## $bib$length
## [1] 68864
## 
## $bib$`quantiles & mean`
##        5%       10%       25%       50%       75%       90%       95%      Mean 
## 0.3423285 0.3857788 0.4784658 0.7840827 1.4784658 1.9096193 2.1163471 0.9941187

Removing objects from workspace using dh.tidyEnv()

The DataSHIELD function ds.rm() allows you to remove single objects from your workspace. However, we may want to remove a number of objects at once. Use the argument type to choose whether to keep or remove the variables listed in vars. Here we chose to keep “data” and remove everything else.

dh.tidyEnv(
  obj = "data",
  type = "keep")
## $alspac
## $alspac$environment.searched
## [1] "R_GlobalEnv"
## 
## $alspac$objects.found
## [1] "data"
## 
## 
## $bcg
## $bcg$environment.searched
## [1] "R_GlobalEnv"
## 
## $bcg$objects.found
## [1] "data"
## 
## 
## $bib
## $bib$environment.searched
## [1] "R_GlobalEnv"
## 
## $bib$objects.found
## [1] "data"

Identifying column indices using dh.findVarsIndex

There are some occasions when we need to do know what column a set of variables is. For example, the function ds.dataFrameSubset takes an input for the argument keep.cols a vector column positions.

Subsetting data frames by column number is highly susceptable to breaking (e.g if you change the order of steps in a script). This function allows you to identify the column numbers of a vector of column names, like so:

indices <- dh.findVarsIndex(
  df = "data",
  vars = c("sex", "age", "weight"))
indices
## $alspac
## [1] 2 3 4
## 
## $bcg
## [1] 2 3 4
## 
## $bib
## [1] 2 3 4

Describing data

Summarising data using dh.getStats()

The DataSHIELD functions ds.summary and ds.table produce basic descriptive statistics. However, they have a number of limitations: the output is not in a very useable format, you can only specify one variable at a time, and they don’t provide some information that we will likely need in manuscripts (e.g. about missingness).

Here we describe multiple variables using dh.getStats():

stats <- dh.getStats(
  df = "data",
  vars = c("age", "sex", "weight")
)
stats
## $categorical
## # A tibble: 0 × 10
## # ℹ 10 variables: variable <chr>, cohort <chr>, category <chr>, value <dbl>, cohort_n <int>, valid_n <dbl>,
## #   missing_n <dbl>, perc_valid <dbl>, perc_missing <dbl>, perc_total <dbl>
## 
## $continuous
## # A tibble: 12 × 15
##    variable cohort    mean std.dev perc_5 perc_10 perc_25 perc_50 perc_75 perc_90 perc_95 valid_n cohort_n missing_n
##    <chr>    <chr>    <dbl>   <dbl>  <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>    <dbl>     <dbl>
##  1 age      alspac    6.2     5.53   0       0.1     0.83    4.52   10.8    13.9    15.7   129681   129681         0
##  2 age      bcg       2.01    1.67   0       0.12    0.5     1.52    3.51    4.53    4.99   12724    12724         0
##  3 age      bib       1.48    1.74   0       0       0.08    0.71    2.11    4.72    4.98   68864    68864         0
##  4 sex      alspac    0.49    0.5    0       0       0       0       1       1       1     129681   129681         0
##  5 sex      bcg       0.48    0.5    0       0       0       0       1       1       1      12724    12724         0
##  6 sex      bib       0.49    0.5    0       0       0       0       1       1       1      68864    68864         0
##  7 weight   alspac   25.3    20.2    3.93    4.56    8.4    18.5    38.5    56.2    65.3   129681   129681         0
##  8 weight   bcg      11.6     5.29   4.12    4.69    6.55   11.7    15.8    18.5    20.0    12724    12724         0
##  9 weight   bib       9.54    5.81   3.28    3.7     4.59    7.52   14.2    18.3    20.3    68864    68864         0
## 10 age      combined  4.41    5.01   0       0.07    0.57    3.1     7.55   10.4    11.6   211269   211269         0
## 11 sex      combined  0.49    0.5    0       0       0       0       1       1       1     211269   211269         0
## 12 weight   combined 19.4    17.9    3.73    4.29    7.05   14.5    29.2    41.6    47.9   211269   211269         0
## # ℹ 1 more variable: missing_perc <dbl>

The output is a list of two tibbles, corresponding to categorical and continuous variables. See the help file for a list of calculated figures and stats.

Extracting model coefficients using dh.lmTab

When we run simple linear regression models in DataSHIELD, the coefficients are returned within a nested list. It can be a bit tricky to manually extract these (e.g. to create a table or a plot). This function extracts the coefficients from linear models and outputs them in a tibble. It can also handle mixed-effect models and will also return summaries of random effects.

output <- ds.glm(formula = "weight~age", data = "data", family = "gaussian")

dh.lmTab(
  model = output, 
  type = "glm_ipd", 
  direction = "wide", 
  ci_format  = "separate")
## Error: Assertion on 'model' failed. function (msg) 
## msgs <<- c(msgs, msg).

## Error: Assertion on 'model' failed. function () 
## msgs.

## Error: Assertion on 'model' failed. function () 
## length(msgs) == 0.

For manuscripts we may want to include confidence intervals in brackets after the point estimate. We can specify this with the argument “ci_format”:

dh.lmTab(
  model = output, 
  type = "glm_ipd", 
  direction = "wide", 
  ci_format  = "paste")
## Error: Assertion on 'model' failed. function (msg) 
## msgs <<- c(msgs, msg).

## Error: Assertion on 'model' failed. function () 
## msgs.

## Error: Assertion on 'model' failed. function () 
## length(msgs) == 0.

Checking class of data using dh.classDiscrepancy()

In an ideal world all variables would have the same class in every study. However due to mistakes in harmonisation (or your scripts!) this may not be the case. The can cause problems because many DataSHIELD functions require the input variable(s) to have the same class in all studies.

Using dh.classDiscrepancy we can produce a tibble summarising the class of variable(s) across multiple studies. If we leave the argument vars empty we check the class of all variables. The column “discrepancy” summarises whether there are differences across studies.

disc <- dh.classDiscrepancy(df = "data")
disc
## # A tibble: 6 × 5
##   variable     discrepancy alspac  bcg     bib    
##   <chr>        <chr>       <chr>   <chr>   <chr>  
## 1 id           no          numeric numeric numeric
## 2 age          no          numeric numeric numeric
## 3 sex          no          numeric numeric numeric
## 4 weight       no          numeric numeric numeric
## 5 tmp_obj      no          numeric numeric numeric
## 6 weight_iqr_s no          numeric numeric numeric

Checking non-missing data using dh.anyData()

When we perform analyses with multiple cohorts, we may want to use the same set of covariates in all analyses. We can use dh.anyData() to check which cohortsh have at least some data on given variables.

available <- dh.anyData(
  df = "data",
  vars = c("age", "sex", "weight"))
available
## # A tibble: 3 × 4
##   variable alspac bcg   bib  
##   <chr>    <lgl>  <lgl> <lgl>
## 1 age      TRUE   TRUE  TRUE 
## 2 sex      TRUE   TRUE  TRUE 
## 3 weight   TRUE   TRUE  TRUE

Creating a dataset for analysis using dh.defineCases()

Before starting analysis we often want to restrict the dataset to subjects meeting certain criteria. In the first example, we create a vector indicating whether each subject has complete data on the set of variables age, sex and weight:

dh.defineCases(
  df = "data", 
  vars = c("age", "sex", "weight"),
  type = "all", 
  new_obj = "data_on_all_vars")
cases_all <- ds.table("data_on_all_vars")$output.list$TABLE_rvar.by.study_counts
cases_all
##                 study
## data_on_all_vars alspac   bcg   bib
##               1  129681 12724 68864
##               NA      0     0     0

The data we have here is not the best to illustrate this function, as there is no missing data.

We can also check whether subjects have available data on any of a set of variables:

dh.defineCases(
  df = "data", 
  vars = c("age", "sex", "weight"),
  type = "any", 
  new_obj = "data_on_any_vars")
cases_any <- ds.table("data_on_any_vars")$output.list$TABLE_rvar.by.study_counts
 cases_any
##                 study
## data_on_any_vars alspac   bcg   bib
##               1  129681 12724 68864
##               NA      0     0     0

Again, we can’t see the differences because all subjects have data on all variables. However, lets assume that we want to create an subset of our data restricted to participants with data on at least one of these variables. We can then use ds.dataFrameSubset in conjuncture with the vector we created:

ds.dataFrameSubset(
  df.name = "data", 
  V1.name = "data_on_all_vars", 
  V2.name = "1", 
  Boolean.operator = "==", 
  newobj = "complete_df")
ds.dim("data")
ds.dim("complete_df")
## $`dimensions of data in alspac`
## [1] 129681      6
## 
## $`dimensions of data in bcg`
## [1] 12724     6
## 
## $`dimensions of data in bib`
## [1] 68864     6
## 
## $`dimensions of data in combined studies`
## [1] 211269      6
## 
## $`dimensions of complete_df in alspac`
## [1] 129681      6
## 
## $`dimensions of complete_df in bcg`
## [1] 12724     6
## 
## $`dimensions of complete_df in bib`
## [1] 68864     6
## 
## $`dimensions of complete_df in combined studies`
## [1] 211269      6

Miscellaneous functions

Enabling auto-complete functionality within R Studio.

When using R studio with native R, auto-complete suggestions will appear for variables within a data frame. However, by default these won’t appear for variables within a serverside data frame.

We can get round this by creating a local object with the same structure as the remote object. Once you have run this function, autocomplete options for the data frame “data” should now be available.

dh.localProxy(df = "data", conns = conns)

Manually pooling estimates by meta-analysis

Most of the time the meta-analysis functions in DataSHIELD (e.g. ds.glmSLMA) will be sufficient. However, you may sometimes need additional flexibility with meta-analysis. For example, currently ds.glmSLMA doesn’t return heterogeneity statistics.

This functions takes vectors of coefficients and standard errors from multiple cohorts and meta-analyses them.

First we fit a model in different cohorts and extract the coefficients:

output <- ds.glmSLMA(
  formula = "weight~age", 
  dataName = "data", 
  family = "gaussian")

Now we can use this object and to the meta-analysis manually:

meta_analysed <- dh.metaManual(
  model = output,
  method = "ML")

The output consists of a list with two elements. The first element (“model”) is a list with the output from metafor. The length will be the number of variables meta-analysed:

str(meta_analysed$model)
## List of 2
##  $ (Intercept):List of 74
##   ..$ b           : num [1, 1] 4.91
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : chr "intrcpt"
##   .. .. ..$ : NULL
##   ..$ beta        : num [1, 1] 4.91
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : chr "intrcpt"
##   .. .. ..$ : NULL
##   ..$ se          : num 0.415
##   ..$ zval        : num 11.8
##   ..$ pval        : num 3.03e-32
##   ..$ ci.lb       : num 4.09
##   ..$ ci.ub       : num 5.72
##   ..$ vb          : num [1, 1] 0.172
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : chr "intrcpt"
##   .. .. ..$ : chr "intrcpt"
##   ..$ tau2        : num 0.516
##   ..$ se.tau2     : num 0.422
##   ..$ tau2.fix    : logi FALSE
##   ..$ tau2.f      : num 0.516
##   ..$ I2          : num 99.9
##   ..$ H2          : num 994
##   ..$ R2          : NULL
##   ..$ vt          : num 0.000519
##   ..$ QE          : num 1753
##   ..$ QEp         : num 0
##   ..$ QM          : num 140
##   ..$ QMdf        : int [1:2] 1 NA
##   ..$ QMp         : num 3.03e-32
##   ..$ k           : int 3
##   ..$ k.f         : int 3
##   ..$ k.eff       : int 3
##   ..$ k.all       : int 3
##   ..$ p           : int 1
##   ..$ p.eff       : int 1
##   ..$ parms       : num 2
##   ..$ int.only    : logi TRUE
##   ..$ int.incl    : logi TRUE
##   ..$ intercept   : logi TRUE
##   ..$ allvipos    : logi TRUE
##   ..$ coef.na     : Named logi FALSE
##   .. ..- attr(*, "names")= chr "X"
##   ..$ yi          : Named num [1:3] 4.02 5.78 4.92
##   .. ..- attr(*, "names")= chr [1:3] "betas study 1" "betas study 2" "betas study 3"
##   .. ..- attr(*, "measure")= chr "GEN"
##   .. ..- attr(*, "slab")= int [1:3] 1 2 3
##   ..$ vi          : Named num [1:3] 0.000819 0.000964 0.000108
##   .. ..- attr(*, "names")= chr [1:3] "ses study 1" "ses study 2" "ses study 3"
##   ..$ X           : num [1:3, 1] 1 1 1
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : NULL
##   .. .. ..$ : chr "intrcpt"
##   ..$ weights     : NULL
##   ..$ yi.f        : Named num [1:3] 4.02 5.78 4.92
##   .. ..- attr(*, "names")= chr [1:3] "betas study 1" "betas study 2" "betas study 3"
##   .. ..- attr(*, "measure")= chr "GEN"
##   .. ..- attr(*, "slab")= int [1:3] 1 2 3
##   ..$ vi.f        : Named num [1:3] 0.000819 0.000964 0.000108
##   .. ..- attr(*, "names")= chr [1:3] "ses study 1" "ses study 2" "ses study 3"
##   ..$ X.f         : num [1:3, 1] 1 1 1
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : NULL
##   .. .. ..$ : chr "intrcpt"
##   ..$ weights.f   : NULL
##   ..$ M           : num [1:3, 1:3] 0.517 0 0 0 0.517 ...
##   ..$ outdat.f    :List of 8
##   .. ..$ ai : num NA
##   .. ..$ bi : num NA
##   .. ..$ ci : num NA
##   .. ..$ di : num NA
##   .. ..$ x1i: num NA
##   .. ..$ x2i: num NA
##   .. ..$ t1i: num NA
##   .. ..$ t2i: num NA
##   ..$ ni          : NULL
##   ..$ ni.f        : NULL
##   ..$ ids         : int [1:3] 1 2 3
##   ..$ not.na      : Named logi [1:3] TRUE TRUE TRUE
##   .. ..- attr(*, "names")= chr [1:3] "betas study 1" "betas study 2" "betas study 3"
##   ..$ subset      : NULL
##   ..$ slab        : int [1:3] 1 2 3
##   ..$ slab.null   : logi TRUE
##   ..$ measure     : chr "GEN"
##   ..$ method      : chr "ML"
##   ..$ model       : chr "rma.uni"
##   ..$ weighted    : logi TRUE
##   ..$ test        : chr "z"
##   ..$ dfs         : int NA
##   ..$ ddf         : int NA
##   ..$ s2w         : num 1
##   ..$ btt         : int 1
##   ..$ m           : int 1
##   ..$ digits      : Named num [1:9] 4 4 4 4 4 4 4 4 4
##   .. ..- attr(*, "names")= chr [1:9] "est" "se" "test" "pval" ...
##   ..$ level       : num 0.05
##   ..$ control     : list()
##   ..$ verbose     : logi FALSE
##   ..$ add         : num 0.5
##   ..$ to          : chr "only0"
##   ..$ drop00      : logi FALSE
##   ..$ fit.stats   :'data.frame': 5 obs. of  2 variables:
##   .. ..$ ML  : num [1:5] -3.27 24.21 10.53 8.73 22.53
##   .. ..$ REML: num [1:5] -2.68 5.36 9.36 6.74 21.36
##   ..$ data        :<environment: 0x139378300> 
##   ..$ formula.yi  : NULL
##   ..$ formula.mods: NULL
##   ..$ version     :Classes 'package_version', 'numeric_version'  hidden list of 1
##   .. ..$ : int [1:3] 4 2 0
##   ..$ call        : language rma(yi = model$betamatrix.valid[x, ], sei = model$sematrix.valid[x, ], method = "ML")
##   ..$ time        : num 0.003
##   ..- attr(*, "class")= chr [1:2] "rma.uni" "rma"
##  $ age        :List of 74
##   ..$ b           : num [1, 1] 3.14
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : chr "intrcpt"
##   .. .. ..$ : NULL
##   ..$ beta        : num [1, 1] 3.14
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : chr "intrcpt"
##   .. .. ..$ : NULL
##   ..$ se          : num 0.133
##   ..$ zval        : num 23.6
##   ..$ pval        : num 6.46e-123
##   ..$ ci.lb       : num 2.88
##   ..$ ci.ub       : num 3.41
##   ..$ vb          : num [1, 1] 0.0178
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : chr "intrcpt"
##   .. .. ..$ : chr "intrcpt"
##   ..$ tau2        : num 0.0533
##   ..$ se.tau2     : num 0.0435
##   ..$ tau2.fix    : logi FALSE
##   ..$ tau2.f      : num 0.0533
##   ..$ I2          : num 99.9
##   ..$ H2          : num 1915
##   ..$ R2          : NULL
##   ..$ vt          : num 2.78e-05
##   ..$ QE          : num 4454
##   ..$ QEp         : num 0
##   ..$ QM          : num 556
##   ..$ QMdf        : int [1:2] 1 NA
##   ..$ QMp         : num 6.46e-123
##   ..$ k           : int 3
##   ..$ k.f         : int 3
##   ..$ k.eff       : int 3
##   ..$ k.all       : int 3
##   ..$ p           : int 1
##   ..$ p.eff       : int 1
##   ..$ parms       : num 2
##   ..$ int.only    : logi TRUE
##   ..$ int.incl    : logi TRUE
##   ..$ intercept   : logi TRUE
##   ..$ allvipos    : logi TRUE
##   ..$ coef.na     : Named logi FALSE
##   .. ..- attr(*, "names")= chr "X"
##   ..$ yi          : Named num [1:3] 3.44 2.87 3.12
##   .. ..- attr(*, "names")= chr [1:3] "betas study 1" "betas study 2" "betas study 3"
##   .. ..- attr(*, "measure")= chr "GEN"
##   .. ..- attr(*, "slab")= int [1:3] 1 2 3
##   ..$ vi          : Named num [1:3] 1.19e-05 1.41e-04 2.07e-05
##   .. ..- attr(*, "names")= chr [1:3] "ses study 1" "ses study 2" "ses study 3"
##   ..$ X           : num [1:3, 1] 1 1 1
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : NULL
##   .. .. ..$ : chr "intrcpt"
##   ..$ weights     : NULL
##   ..$ yi.f        : Named num [1:3] 3.44 2.87 3.12
##   .. ..- attr(*, "names")= chr [1:3] "betas study 1" "betas study 2" "betas study 3"
##   .. ..- attr(*, "measure")= chr "GEN"
##   .. ..- attr(*, "slab")= int [1:3] 1 2 3
##   ..$ vi.f        : Named num [1:3] 1.19e-05 1.41e-04 2.07e-05
##   .. ..- attr(*, "names")= chr [1:3] "ses study 1" "ses study 2" "ses study 3"
##   ..$ X.f         : num [1:3, 1] 1 1 1
##   .. ..- attr(*, "dimnames")=List of 2
##   .. .. ..$ : NULL
##   .. .. ..$ : chr "intrcpt"
##   ..$ weights.f   : NULL
##   ..$ M           : num [1:3, 1:3] 0.0533 0 0 0 0.0534 ...
##   ..$ outdat.f    :List of 8
##   .. ..$ ai : num NA
##   .. ..$ bi : num NA
##   .. ..$ ci : num NA
##   .. ..$ di : num NA
##   .. ..$ x1i: num NA
##   .. ..$ x2i: num NA
##   .. ..$ t1i: num NA
##   .. ..$ t2i: num NA
##   ..$ ni          : NULL
##   ..$ ni.f        : NULL
##   ..$ ids         : int [1:3] 1 2 3
##   ..$ not.na      : Named logi [1:3] TRUE TRUE TRUE
##   .. ..- attr(*, "names")= chr [1:3] "betas study 1" "betas study 2" "betas study 3"
##   ..$ subset      : NULL
##   ..$ slab        : int [1:3] 1 2 3
##   ..$ slab.null   : logi TRUE
##   ..$ measure     : chr "GEN"
##   ..$ method      : chr "ML"
##   ..$ model       : chr "rma.uni"
##   ..$ weighted    : logi TRUE
##   ..$ test        : chr "z"
##   ..$ dfs         : int NA
##   ..$ ddf         : int NA
##   ..$ s2w         : num 1
##   ..$ btt         : int 1
##   ..$ m           : int 1
##   ..$ digits      : Named num [1:9] 4 4 4 4 4 4 4 4 4
##   .. ..- attr(*, "names")= chr [1:9] "est" "se" "test" "pval" ...
##   ..$ level       : num 0.05
##   ..$ control     : list()
##   ..$ verbose     : logi FALSE
##   ..$ add         : num 0.5
##   ..$ to          : chr "only0"
##   ..$ drop00      : logi FALSE
##   ..$ fit.stats   :'data.frame': 5 obs. of  2 variables:
##   .. ..$ ML  : num [1:5] 0.14 25.2 3.72 1.92 15.72
##   .. ..$ REML: num [1:5] -0.407 0.814 4.814 2.201 16.814
##   ..$ data        :<environment: 0x12971ce00> 
##   ..$ formula.yi  : NULL
##   ..$ formula.mods: NULL
##   ..$ version     :Classes 'package_version', 'numeric_version'  hidden list of 1
##   .. ..$ : int [1:3] 4 2 0
##   ..$ call        : language rma(yi = model$betamatrix.valid[x, ], sei = model$sematrix.valid[x, ], method = "ML")
##   ..$ time        : num 0.003
##   ..- attr(*, "class")= chr [1:2] "rma.uni" "rma"

The second element (“coefficients”) is a summary table:

meta_analysed$coefs
## # A tibble: 2 × 3
##   term         coef    se
##   <chr>       <dbl> <dbl>
## 1 (Intercept)  4.91 0.415
## 2 age          3.14 0.133

Finish by logging out of the demo server