Getting started with replicate-based imputation

This vignette provides a practical introduction to replicate-based imputation using repli. It introduces the concepts behind missing value imputation, demonstrates the basic workflow, and shows how repli can be integrated into a complete proteomics workflow using SummarizedExperiment objects with DEP.

Introduction

Missing values are common in mass spectrometry-based proteomics data and can complicate downstream statistical analysis. Imputation can be used to retain proteins with incomplete measurements, providing valuable biological insights that might otherwise be missed.

In proteomics, missing values are classified as Missing At Random (MAR), when a protein is not detected in some replicates of a condition, or Missing Not At Randomn (MNAR), where a protein is not detected in all replicates of a condition. For example, in the dataset below, Protein B would be classified as MAR and Proteins C would be classified as MNAR.

              Replicate 1   Replicate 2   Replicate 3

Protein A     20            21            19
Protein B     15            NA            16
Protein C     NA            NA            NA

repli implements replicate-based imputation for MAR data. Instead of estimating a missing value using similar proteins or a global dataset metric, the method uses available measurements from other replicates of the same protein. For each protein-condition combination with missing values, the mean of the available values is calculated to use for the imputation distribution. A global standard deviation is derived from the standard deviations of all protein-condition pairs in the dataset and used for the imputation distribution.

In the example below, replicate 2 of Protein B would be imputed by sampling from a normal distribution with the mean set at 15.5 and a standard deviation set at 0.8535. For more details on how the global standard deviation is calculated, see the section below.

              Replicate 1   Replicate 2   Replicate 3   mean    sd

Protein A     20            21            19            NA      1
Protein B     15            NA            16            15.5    0.707
Protein C     NA            NA            NA            NA      NA

Input data

repli expects samples to follow a condition-replicate naming convention, with an underscore or period as separator.

For example:

control_1
control_2
control_3
treatment_1
treatment_2
treatment_3

The data should contain proteins in rows and samples in columns, with log2-transformed protein abundances in the sample columns and missing values represented as NA.

Data-frame workflow

First, prepare the data using prepare_imputation_data(). Here, we use a modified dataset from Zijlmans et al. 2025 as example. The resulting object contains the prepared data as well as the information required for imputation.

data(example_dataset)

head(example_dataset)
#>        E_T_T5C1_B_1 E_T_T5C1_B_2 E_T_T5C1_B_3   R1_B_1   R1_B_2   R1_B_3
#> Magohb     27.57022     27.06528     27.74464 26.67169 26.68886 26.45081
#> Srcap      25.73255     25.81372     25.60094 24.65223 24.47352 24.56506
#> Eloc       27.53096     27.64924     27.58864 25.48867 25.65196 25.77816
#> Agfg1      25.37885     25.56075     24.79397       NA       NA       NA
#> Ktn1       26.24842     26.46579     26.22523 26.86774 27.00721 27.08111
#> Resf1      25.67020     25.29432     25.23334 24.76172 24.85377 25.03183

prepared <- prepare_imputation_data(example_dataset, p = 0.05)

#The output is a list consisting of the data is the appropriate format,
# as well as a vector specifying the replicate columns and the global sd.
names(prepared)
#> [1] "data"           "replicate_cols" "global_sd"

head(prepared$data)
#> # A tibble: 6 × 8
#>   name   condition    `1`   `2`   `3`     sd missingness row_mean
#>   <chr>  <chr>      <dbl> <dbl> <dbl>  <dbl> <chr>          <dbl>
#> 1 Magohb E_T_T5C1_B  27.6  27.1  27.7 0.353  Valid             NA
#> 2 Magohb R1_B        26.7  26.7  26.5 0.133  Valid             NA
#> 3 Srcap  E_T_T5C1_B  25.7  25.8  25.6 0.107  Valid             NA
#> 4 Srcap  R1_B        24.7  24.5  24.6 0.0894 Valid             NA
#> 5 Eloc   E_T_T5C1_B  27.5  27.6  27.6 0.0591 Valid             NA
#> 6 Eloc   R1_B        25.5  25.7  25.8 0.145  Valid             NA

Protein-condition combinations are classified as:

  • Valid: no missing values;
  • MAR: some missing values;
  • MNAR: all missing values.

The prepared data can be passed to impute_replicate():

set.seed(123)

imputed <- impute_replicate(prepared)

The random seed can be set to make the stochastic imputation reproducible.

Calculating the global standard deviation

The p argument controls the proportion of observations with the highest standard deviations that are used to estimate the global standard deviation.

For example:

set.seed(123)

data(example_se)

imputed_se <- impute_replicate_se(example_se, p = 0.05)

uses the top 5% of standard deviations across the dataset to calculate the median global standard deviation.

SummarizedExperiment workflow

A SummarizedExperiment is a data structure for storing quantitative biological data together with information about the samples and measured features. You can think of it as a container holding the protein abundance matrix, information about the samples and experimental conditions and replicate information. Many Bioconductor packages use this format, which means that using a SummarizedExperiment allows different packages to work together.

For SummarizedExperiment objects, impute_replicate_se() combines preparation, imputation, and reconstruction into a single function. It requires a SummarizedExperiment as input and will return a SummarizedExperiment with the imputed assay as output.

data(example_se)

class(example_se)
#> [1] "SummarizedExperiment"
#> attr(,"package")
#> [1] "SummarizedExperiment"

set.seed(123)

imputed_se <- impute_replicate_se(example_se, p = 0.05)

class(imputed_se)
#> [1] "SummarizedExperiment"
#> attr(,"package")
#> [1] "SummarizedExperiment"

#The imputed data can be accessed using `assay()`:
head(SummarizedExperiment::assay(imputed_se))
#>        E_T_T5C1_B_1 E_T_T5C1_B_2 E_T_T5C1_B_3   R1_B_1   R1_B_2   R1_B_3
#> Magohb     27.57022     27.06528     27.74464 26.67169 26.68886 26.45081
#> Srcap      25.73255     25.81372     25.60094 24.65223 24.47352 24.56506
#> Eloc       27.53096     27.64924     27.58864 25.48867 25.65196 25.77816
#> Agfg1      25.37885     25.56075     24.79397       NA       NA       NA
#> Ktn1       26.24842     26.46579     26.22523 26.86774 27.00721 27.08111
#> Resf1      25.67020     25.29432     25.23334 24.76172 24.85377 25.03183

This workflow is intended to integrate directly with downstream proteomics workflows based on DEP.

Mixed MAR/MNAR imputation

Replicate-based imputation is designed for MAR values, as it requires at east some existing values for a condition. When a dataset contains both MAR and MNAR values, impute_replicate_mixed_se() can combine replicate-based imputation with a MNAR imputation method.

For example, MinProb can be used for MNAR values:

set.seed(123)

imputed_se <- impute_replicate_mixed_se(example_se,
                                        p = 0.05,
                                        MNAR_method = "MinProb",
                                        MNAR_args = list(q = 0.01))
#> Imputing along margin 2 (samples/columns).
#> [1] 0.4216317

The function performs replicate-based MAR imputation and the selected MNAR imputation method independently on the original dataset. The resulting imputed values are then combined, with replicate-based imputations used for partially missing protein-condition pairs and MNAR imputations used for completely missing protein-condition pairs. Originally observed values are retained.

If no MNAR imputation is desired, set MNAR method to "none".

Using repli with DEP

repli can be incorporated into proteomics workflows based on the DEP package.

Here, repli replaces the imputation step while the other analysis steps can be performed using existing DEP functions. Since proteomics data almost always contain both MAR and MNAR missing values, using impute_replicate_mixed_se() is advised. Below is an example DEP workflow.

#Load and clean data
data(UbiLength, package = "DEP")
data <- UbiLength
data <- dplyr::filter(data, Reverse != "+", Potential.contaminant != "+")

#Prepare SummarizedExperiment
data_unique <- DEP::make_unique(data, "Gene.names", "Protein.IDs", delim = ";")
LFQ_columns <- grep("LFQ.", colnames(data_unique))
data(UbiLength_ExpDesign, package = "DEP")
experimental_design <- UbiLength_ExpDesign
data_se <- DEP::make_se(data_unique, LFQ_columns, experimental_design)

#Data filtering and normalization
data_filt <- DEP::filter_missval(data_se, thr = 0)
data_norm <- DEP::normalize_vsn(data_filt)

#Imputation
data_imp <- impute_replicate_mixed_se(data_norm,              # <-- use here
                                      p = 0.05,
                                      MNAR_method = "MinProb",
                                      MNAR_args = list(q = 0.01))
#> Imputing along margin 2 (samples/columns).
#> [1] 0.2608395

#Differential enrichment analysis
data_diff <- DEP::test_diff(data_imp, type = "control", control = "Ctrl")
#> Tested contrasts: Ubi4_vs_Ctrl, Ubi6_vs_Ctrl, Ubi1_vs_Ctrl
dep <- DEP::add_rejections(data_diff, alpha = 0.05, lfc = log2(1.5))

Session information

sessionInfo()
#> R version 4.6.2 (2026-10-29)
#> 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] repli_0.99.0     BiocStyle_2.41.0
#> 
#> loaded via a namespace (and not attached):
#>   [1] RColorBrewer_1.1-3          sys_3.4.3                  
#>   [3] jsonlite_2.0.0              shape_1.4.6.1              
#>   [5] MultiAssayExperiment_1.39.1 magrittr_2.0.5             
#>   [7] farver_2.1.2                MALDIquant_1.22.3          
#>   [9] rmarkdown_2.32              GlobalOptions_0.1.4        
#>  [11] fs_2.1.0                    vctrs_0.7.3                
#>  [13] htmltools_0.5.9             S4Arrays_1.13.2            
#>  [15] SparseArray_1.13.4          mzID_1.51.0                
#>  [17] sass_0.4.10                 bslib_0.12.0               
#>  [19] htmlwidgets_1.6.4           plyr_1.8.9                 
#>  [21] sandwich_3.1-3              impute_1.87.0              
#>  [23] zoo_1.9-1                   cachem_1.1.0               
#>  [25] buildtools_1.0.0            igraph_2.3.4               
#>  [27] mime_0.13                   lifecycle_1.0.5            
#>  [29] iterators_1.0.14            pkgconfig_2.0.3            
#>  [31] Matrix_1.7-6                R6_2.6.1                   
#>  [33] fastmap_1.2.0               MatrixGenerics_1.25.0      
#>  [35] shiny_1.14.0                clue_0.3-68                
#>  [37] fdrtool_1.2.18              digest_0.6.39              
#>  [39] pcaMethods_2.5.0            colorspace_2.1-4           
#>  [41] S4Vectors_0.51.10           GenomicRanges_1.65.4       
#>  [43] Spectra_1.23.7              abind_1.4-8                
#>  [45] compiler_4.6.2              withr_3.0.3                
#>  [47] doParallel_1.0.17           S7_0.2.2                   
#>  [49] PTMods_1.1.1                BiocParallel_1.47.0        
#>  [51] MASS_7.3-66                 DelayedArray_0.39.8        
#>  [53] rjson_0.2.23                mzR_2.47.2                 
#>  [55] tools_4.6.2                 PSMatch_1.17.3             
#>  [57] otel_0.3.0                  httpuv_1.6.17              
#>  [59] glue_1.8.1                  QFeatures_1.23.2           
#>  [61] promises_1.5.0              grid_4.6.2                 
#>  [63] cluster_2.1.8.3             reshape2_1.4.5             
#>  [65] generics_0.1.4              gtable_0.3.6               
#>  [67] tzdb_0.5.0                  preprocessCore_1.75.3      
#>  [69] tidyr_1.3.2                 data.table_1.18.6.1        
#>  [71] hms_1.1.4                   MetaboCoreUtils_1.21.2     
#>  [73] utf8_1.2.6                  XVector_0.53.0             
#>  [75] BiocGenerics_0.59.13        foreach_1.5.2              
#>  [77] pillar_1.11.1               stringr_1.6.0              
#>  [79] limma_3.99.4                later_1.4.8                
#>  [81] circlize_0.4.18             gmm_1.9-1                  
#>  [83] dplyr_1.2.1                 lattice_0.23-1             
#>  [85] tidyselect_1.2.1            ComplexHeatmap_2.29.0      
#>  [87] maketools_1.3.2             knitr_1.52                 
#>  [89] IRanges_2.47.5              Seqinfo_1.3.2              
#>  [91] ProtGenerics_1.45.0         imputeLCMD_2.1             
#>  [93] SummarizedExperiment_1.43.0 stats4_4.6.2               
#>  [95] xfun_0.62                   shinydashboard_0.7.3       
#>  [97] Biobase_2.73.2              statmod_1.5.2              
#>  [99] MSnbase_2.39.6              matrixStats_1.5.0          
#> [101] DT_0.34.0                   DEP_3.99.0                 
#> [103] stringi_1.8.9               lazyeval_0.2.3             
#> [105] yaml_2.3.12                 evaluate_1.0.5             
#> [107] codetools_0.2-20            MsCoreUtils_1.25.4         
#> [109] tibble_3.3.1                BiocManager_1.30.27        
#> [111] cli_3.6.6                   affyio_1.83.0              
#> [113] xtable_1.8-8                jquerylib_0.1.4            
#> [115] Rcpp_1.1.2                  norm_1.0-11.1              
#> [117] png_0.1-9                   XML_3.99-0.25              
#> [119] parallel_4.6.2              ggplot2_4.0.3              
#> [121] readr_2.2.0                 assertthat_0.2.1           
#> [123] AnnotationFilter_1.37.0     mvtnorm_1.4-2              
#> [125] scales_1.4.0                tmvtnorm_1.7               
#> [127] affy_1.91.0                 ncdf4_1.24                 
#> [129] purrr_1.2.2                 crayon_1.5.3               
#> [131] GetoptLong_1.1.1            rlang_1.3.0                
#> [133] vsn_3.81.1