This vignette focuses on how to upload data to magma via its R-client, magmaR.
Magma is the data warehouse of the UCSF Data Library.
For a deeper overview of the structure of magma and the UCSF Data
Library system, please see the download-focused vignette,
vignette("Download", package = "magmaR"), or the Data
Library’s own documentation, here https://mountetna.github.io/magma.html.
This vignette assumes that you have already gone through the
download-focused vignette,
vignette("Download", package = "magmaR"), which covers how
to 1) install magmaR, 2) use a token for authentication, and 3)
switch, if needed, between the production / staging / development magma
environments.
This vignette focuses on use-cases where a user wishes to push data, from their own system, to magma.
Not all Data Library users have write privileges, so not all magmaR users will have need for this vignette.
For those that do, please note: Sending data to magma is an advanced use-case which needs to be treated with due care. The functions involved have the ability to overwrite data, so it is imperative, for data integrity purposes, that inputs to these functions are double-checked in order to make sure that they target only the intended records & attributes.
Also note that a users’ write-privileges are project-specific, so it is unlikely that you will be able to run any code, exactly as it exists in this vignette, without getting an authorization error. (That also means you don’t run the risk of breaking our download vignette by testing out any fun alterations of the code in here… trade-offs =] .)
In general, magmaR functions will:
retrieve,
query, or update functions to send or receive
desired data.Steps 4&5 are very simple for upload functions because the only
return from magma will be curl request attributes that indicate whether
the call to magma/update worked.
So in this vignette, our singular focus will be on how to input your data so that magmaR can send it to magma properly.
The base function:
updateValues() = a few-frills wrapper
of magma’s sole data input function,
/update. It’s quite flexible, but that
flexibility owes largely to a rigid input structure, so we provide a few
wrapper functions which make some common upload needs easier.Convenient wrapper functions:
updateFromDF() allows update of a set
of attributes, of multiple records, via a simple data.frame
structure, or via a csv/tsv file encoding such data.updateMatrix() allows update of a
matrix-type attribute, of multiple records, directly from an R
matrix, data.frame, or a csv/tsv file encoding
such data.Jumping in, the first thing to know is that all magmaR update functions will:
auto.proceed is set to FALSE.magma/update, including any
error messages returned.For example, after running some magmaR update code, you might see a summary like this:
For model "rna_seq", this update() will create 3 NEW records:
ID1
ID2
ID3
WARNING: Check the above carefully. Once created, there is no easy way to remove records from magma.
For model "biospecimen", this update() will update 2 records:
EXAMPLE-HS1-WB1
EXAMPLE-HS2-WB1
For model "rna_seq", this update() will update 1 records:
EXAMPLE-HS1-WB1-RSQ1
Proceed, Y/n?
It is highly recommended that these summaries be checked carefully
for accuracy before proceeding as updates have the power to
overwrite magmaR data! There is currently no history tracking in magma,
so updates cannot be rolled back.
Also, contrary to the “update” portion of the function names, these functions can add totally new data to magma records.
Once created, it is not easy to fully remove records with an incorrect ID.
So, especially if you get a WARNING message like in the example above, we recommend to always double-check the summary output before proceeding!
That said, it is possible to bypass the user-prompt step.
To continue with your upload, simply enter y,
yes, or the like, and hit enter to proceed.
Or, enter anything else to stop.
After a successful update() users will see this message
(unless verbose has been set to FALSE):
/update: successful.
In cases where you are trying to automate uploads with a script,
inputting “yes” to proceed is not possible. In such cases, the
user-prompt step can also be turned off by adding the input
auto.proceed = TRUE.
Of course, extra care should be taken to ensure proper payload generation.
Example:
revs <- list(
"biospecimen" = list(
"EXAMPLE-HS1-WB1" = list(biospecimen_type = "Whole Blood"),
"EXAMPLE-HS2-WB1" = list(biospecimen_type = "Whole Blood")
),
"rna_seq" = list(
"EXAMPLE-HS1-WB1-RSQ1" = list(fraction = "Tcells")
)
)
updateValues(
target = prod,
project = "example",
revisions = revs,
auto.proceed = TRUE) ## <---## For model "biospecimen", this update will update 2 records:
## EXAMPLE-HS1-WB1
## EXAMPLE-HS2-WB1
## For model "rna_seq", this update will update 1 records:
## EXAMPLE-HS1-WB1-RSQ1
## /update: successful.
Examples in this vignette will target the same “example” project used in the download vignette.
To refresh, the model map for that project is below.
The “biospecimen” and “rna_seq” models will be our particular targets, and have attributes…
## [1] "name" "biospecimen_type" "subject" "rna_seq"
## [5] "flow"
## [1] "tube_name" "expression_type" "cell_number" "gene_tpm"
## [5] "gene_counts" "fraction" "biospecimen" "fastq"
As the name suggests, updateMatrix() is meant
specifically for matrix data. It allows a user to point magmaR to either
a file containing matrix data, or to a readily constructed matrix,
without needing to perform the manual conversion of such data to the
complicated revisions-input format of the
updateValues() function.
Internally, the function performs some necessary validations, adjusts
the matrix into the revisions-input format of
updateValues(), then passes its payload along to
updateValues() where all the common update functionality is
housed.
In addition to the normal target and
projectName inputs here, modelName, and
attributeName are also used to direct where to upload your
matrix data.
A separate matrix input takes in the target data. This
can be given a matrix or data.frame directly, or a string representing
the path to a csv or tsv file containing such data. In all cases,
matrix must be formatted to have:
?updateMatrix for one method of finding
out what these ‘options’ are.)When matrix points to a file, the optional
separator input can be used to indicate between csv
(default assumption) versus tsv parsing.
Example:
To update the raw counts of our “rna_seq” model from either a csv, a tsv, or directly from a matrix, we could use the code below:
### From a csv
updateMatrix(
target = prod,
projectName = "example",
modelName = "rna_seq",
attributeName = "gene_counts",
matrix = "path/to/rna_seq_counts.csv")
### From a tsv, set the 'separator' input to "\t"
updateMatrix(
target = prod,
projectName = "example",
modelName = "rna_seq",
attributeName = "gene_counts",
matrix = "path/to/rna_seq_counts.tsv",
# Use separator to adjust parsing for tab-separated values
separator = "\t")
### From an already loaded matrix:
matrix <- retrieveMatrix(target = prod, "example", "rna_seq", "all", "gene_counts")
updateMatrix(
target = prod,
projectName = "example",
modelName = "rna_seq",
attributeName = "gene_counts",
matrix = matrix)Let’s explore the structure of matrix a little bit,
noting that:
## EXAMPLE-HS10-WB1-RSQ1 EXAMPLE-HS11-WB1-RSQ1
## gene1 4 8
## gene2 231 43
## gene3 861 155
## gene4 2077 427
## gene5 3 2
## gene6 0 0
This function allows easy update of a multiple attributes and multiple records, via a rectangular data.frame input structure. It’s the ideal method for uploading to table-type models, but is just as convenient for standard models.
In addition to the normal target and
projectName inputs, here a modelName is also
needed in order to direct where to upload your data.
A separate df input then takes in the target data.
df can be given as a data.frame directly, or as a string
representing the path to a csv or tsv file containing such data. In all
cases, df must be formatted to have:
modelName targets a ‘standard’ model that uses
identifiers, the first column must provide current identifiers. A
separate column can be named as the identifier attribute if there is
need to update record names.modelName targets a ‘table’ model that does not
rely on identifiers, at least one column must be named after the parent
model Except for the first column (ignored as this column’s data are
used as identifiers), all column names must be valid attribute names of
the target modelName.When df points to a file, the optional
separator input can be used to indicate between csv
(default assumption) versus tsv parsing.
Example1, ‘standard’ model:
To update multiple attributes of multiple records of our “rna_seq” model from either a csv, a tsv, or directly from a data.frame, we could use the code below:
### From a csv
updateFromDF(
target = prod,
projectName = "example",
modelName = "rna_seq",
df = "path/to/rna_seq_attributes.csv")
### From a tsv, set the 'separator' input to "\t"
updateFromDF(
target = prod,
projectName = "example",
modelName = "rna_seq",
df = "path/to/rna_seq_attributes.tsv",
# Use separator to adjust parsing for tab-separated values
separator = "\t")
### From an already loaded data.frame:
df <- retrieve(target = prod, "example", "rna_seq", "all",
c("tube_name", "cell_number", "fraction"))
updateFromDF(
target = prod,
projectName = "example",
modelName = "rna_seq",
df = df)Let’s explore the structure of df a little bit, noting
that:
## tube_name cell_number fraction
## 1 EXAMPLE-HS10-WB1-RSQ1 50000 Tcells
## 2 EXAMPLE-HS11-WB1-RSQ1 50000 Tcells
## 3 EXAMPLE-HS12-WB1-RSQ1 50000 Tcells
## 4 EXAMPLE-HS1-WB1-RSQ1 50000 Tcells
## 5 EXAMPLE-HS2-WB1-RSQ1 50000 Tcells
## 6 EXAMPLE-HS3-WB1-RSQ1 50000 Tcells
One final note: The identifier column’s name, though accurately the name of the identifier attribute here, does not actually matter. ‘tube_name’ is the name of the identifier attribute of this “rna_seq” model, but we could have named the column anything. This is an intended feature as it allows users to adjust records’ identifiers with this function by providing a separate data column, of new identifiers, with the identifier attribute name as its name!
Example2, ‘table’-type model:
To update multiple entries of a table-type “demographics” model from a data.frame…
df <- data.frame(
"subject" = paste0("EXAMPLE-HS", c(1:4,1:4)),
"name" = rep(c("Sex at Birth", "Race"), each = 4),
"value" = c("Male", "Male", "Female", "Female",
"White", "Black or African American",
"Asian", "Decline to answer")
)
df## subject name value
## 1 EXAMPLE-HS1 Sex at Birth Male
## 2 EXAMPLE-HS2 Sex at Birth Male
## 3 EXAMPLE-HS3 Sex at Birth Female
## 4 EXAMPLE-HS4 Sex at Birth Female
## 5 EXAMPLE-HS1 Race White
## 6 EXAMPLE-HS2 Race Black or African American
## 7 EXAMPLE-HS3 Race Asian
## 8 EXAMPLE-HS4 Race Decline to answer
there is one extra decision to be made:
Is your goal extending the data that already exists for the targeted
subjects? Or else is the current upload comprehensive for these subjects
to where all previous data, in this table, attached to targeted parent
records, should be cleared? The answer to this question determines what
options to set for the table.method parameter. It must
either be set to “append”, or “replace”.
In the code below, we exemplify an “append” case:
updateFromDF(
target = prod,
projectName = "example",
modelName = "demographic",
table.method = "append",
df = df)Note: When using table.method = "append", running the
same code multiple times WILL create multiple instances of the same data
being put into the model. This is allowed by design. In the example
here, it’s likely that you would never want to have multiple entries for
a single individual’s “Sex at Birth”, but this same model-type can also
be used for treatment information. Having multiple table entries with
same name=“drug_name” and and different value=“drug1” or “drug2”
attached to the same parent record does make sense.
updateValues() is the main workhorse function of
magmaR’s data upload capabilities. It largely mimics
magma/update, where each target model, record, and
attribute are indicated individually, thus giving very flexible control.
The function relies on a rigid nested list structure for providing such
data. This revisions input structure can feel a bit clunky,
but is ultimately very powerful.
Utility: Useful for smaller updates, and for any updates that cannot
be handled with the updateFromDF() and
updateMatrix() wrappers.
In addition to the normal target and
projectName inputs, revisions is the primary
input of updateValues():
revisions includes information about which model(s),
which record(s), and which attribute(s) to update, and with what
value(s). Each of these levels are encoded as a nested list where the
format looks something like:revisions = list(
modelName = list(
recordName = list(
attributeName = value(s)
)
)
)
To make more than one update within a single call, you can simply add an additional index at any of these levels.
For example, the below revisions would encode some very
different updates
# 2 attributes for the same record
revisions = list(
modelName = list(
recordName = list(
attributeName1 = value(s),
attributeName2 = value(s)
)
)
)
# The same attribute for 2 different records
revisions = list(
modelName = list(
recordName1 = list(
attributeName1 = value(s)
),
recordName2 = list(
attributeName1 = value(s)
)
)
)
# Some attribute for 2 different records of two different models
revisions = list(
modelName1 = list(
recordName1 = list(
attributeName1 = value(s)
)
),
modelName2 = list(
recordName2 = list(
attributeName2 = value(s)
)
)
)
Say we wanted to update the “biospecimen_type” attribute of 2 records from the “biospecimen” model, and the “fraction” attribute for 1 record from the “rna_seq” model. The code for this could be:
# Create 'revisions'
revs <- list(
"biospecimen" = list(
"EXAMPLE-HS1-WB1" = list(biospecimen_type = "Whole Blood"),
"EXAMPLE-HS2-WB1" = list(biospecimen_type = "Whole Blood")
),
"rna_seq" = list(
"EXAMPLE-HS1-WB1-RSQ1" = list(fraction = "Tcells")
)
)
# Run update()
updateValues(
target = prod,
project = "example",
revisions = revs)A user would then see a summary of models/records to be updated, followed by a prompt to proceed or not:
For model "biospecimen", this update() will update 2 records:
EXAMPLE-HS1-WB1
EXAMPLE-HS2-WB1
For model "rna_seq", this update() will update 1 records:
EXAMPLE-HS1-WB1-RSQ1
Proceed, Y/n?
As mentioned previously, it is highly recommended that summary outputs be checked carefully for accuracy before proceeding.
However, for running update*() code in non-interactive
modes, like scripts or .Rmd knits, this user-prompt step can also be
turned off by adding the input auto.proceed = TRUE.
Example:
## For model "biospecimen", this update will update 2 records:
## EXAMPLE-HS1-WB1
## EXAMPLE-HS2-WB1
## For model "rna_seq", this update will update 1 records:
## EXAMPLE-HS1-WB1-RSQ1
## /update: successful.
After a successful update() a user should see this
message (unless verbose has been set to
FALSE):
/update: successful.
## R version 4.6.1 (2026-06-24)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 26.04.1 LTS
##
## Matrix products: default
## BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
## LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so; LAPACK version 3.12.0
##
## locale:
## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
## [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
## [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
## [9] LC_ADDRESS=C LC_TELEPHONE=C
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
##
## time zone: Etc/UTC
## tzcode source: system (glibc)
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] dittoSeq_1.25.0 ggplot2_4.0.3 magmaR_1.0.4 vcr_2.1.0
## [5] BiocStyle_2.41.0
##
## loaded via a namespace (and not attached):
## [1] SummarizedExperiment_1.43.0 gtable_0.3.6
## [3] xfun_0.61 bslib_0.12.0
## [5] httr2_1.3.0 ggrepel_0.9.8
## [7] Biobase_2.73.2 lattice_0.23-1
## [9] vctrs_0.7.3 tools_4.6.1
## [11] generics_0.1.4 stats4_4.6.1
## [13] curl_8.0.0 pheatmap_1.0.13
## [15] Matrix_1.7-6 RColorBrewer_1.1-3
## [17] S7_0.2.2 S4Vectors_0.51.10
## [19] ggridges_0.5.7 lifecycle_1.0.5
## [21] compiler_4.6.1 farver_2.1.2
## [23] Seqinfo_1.3.2 htmltools_0.5.9
## [25] sys_3.4.3 buildtools_1.0.0
## [27] sass_0.4.10 yaml_2.3.12
## [29] jquerylib_0.1.4 SingleCellExperiment_1.35.2
## [31] cachem_1.1.0 DelayedArray_0.39.8
## [33] abind_1.4-8 digest_0.6.39
## [35] labeling_0.4.3 maketools_1.3.2
## [37] cowplot_1.2.0 fastmap_1.2.0
## [39] grid_4.6.1 colorspace_2.1-4
## [41] cli_3.6.6 SparseArray_1.13.4
## [43] magrittr_2.0.5 S4Arrays_1.13.2
## [45] triebeard_0.4.1 crul_1.6.0
## [47] withr_3.0.3 waldo_0.6.2
## [49] scales_1.4.0 rmarkdown_2.32
## [51] XVector_0.53.0 matrixStats_1.5.0
## [53] otel_0.2.0 gridExtra_2.3.1
## [55] evaluate_1.0.5 knitr_1.52
## [57] GenomicRanges_1.65.4 IRanges_2.47.5
## [59] rlang_1.3.0 urltools_1.7.3.1
## [61] Rcpp_1.1.2 glue_1.8.1
## [63] httpcode_0.3.0 BiocManager_1.30.27
## [65] BiocGenerics_0.59.12 jsonlite_2.0.0
## [67] R6_2.6.1 MatrixGenerics_1.25.0