Here is a report showing how to create injection sequence plot and dilution plot using plotly for each Multiple Reaction Monitoring (MRM) transition in Mass Spectrometry.
These multiple interactive plots will be displayed as a trellis using trelliscopejs.
The source code used to generate this report can be found in this GitHub page.
Background
Here is some background knowledge about the use of the quality control (QC) plots, for example, the injection sequence plot and the dilution plot, in targeted lipidomics analysis.
About Targeted Lipidomics
The peak area data used to generate these quality control plots comes from a targeted lipidomics experiment using Reversed-Phase Ultra High-Performance Liquid Chromatography/ Mass Spectrometry (RP-UHPLC/MS).
For those new to mass spectrometry or lipidomics, the aim of targeted lipidomics is to measure and even quantify the amount of specific lipids found in a given sample. To tell the mass spectrometer what lipids to measure, a list of multiple reaction monitoring (MRM) transitions (with their corresponding precursor and product ions) is provided. More details on what multiple reaction monitoring (MRM) is can be found in this webpage by Proteomics International Laboratories LTD.
Reason for creating quality control (QC) plots
However, variation in a transition’s peak area measured between samples can come from many unwanted sources, besides that sample itself. Such unwanted sources includes, contaminant ions or a different pipette used during lipid extraction. To check the severity of these unwanted variation, quality control (QC) samples are used.
In most practices, we want to keep transitions that give peak areas with low variation in the quality control samples. While variations can be measured using summary statistics such as the coefficient of variation, creating quality control (QC) plots can help us identify the cause of the unwanted variation. In this report, two such plots are introduced.
The injection sequence plot is a scatter plot with the peak area (or concentration) on the y-axis and the injection sequence order (or data acquisition time) on the x axis. This is to better understand the variation of peak area in the quality control (QC) sample types over time. A raincloud plot(1) is placed on the right of the scatter plot to show the distribution of each (QC) sample type as well as to highlight any potential outliers.
The dilution plot, on the other hand, checks for possible signal enhancement or suppression as a result of detector saturation or matrix effect.
R Packages Used
Code
# For session info and package reportinglibrary("sessioninfo")# R Session Information Posit RPSM v1.2.2library("quarto")# R Interface to 'Quarto' Markdown Publishing System Posit RPSM v1.4.4library("report")# Automated Reporting of Results and Statistical Models Posit RPSM v0.5.9library("knitr")# A General-Purpose Package for Dynamic Report Generation in R CRAN v1.48library("fontawesome")# Easily Work with 'Font Awesome' Icons Posit RPSM v0.5.2library("verbaliseR")# Make your Text Mighty Fine CRAN v0.1# For reading fileslibrary("readr")# Read Rectangular Text Data CRAN v2.1.5# For interactive tableslibrary("reactable")# Interactive Data Tables for R Posit RPSM v0.4.4library("htmltools")# Tools for HTML Posit RPSM v0.5.8.1# For data wranglinglibrary("dplyr")# A Grammar of Data Manipulation Posit RPSM v1.1.4library("tidyr")# Tidy Messy Data Posit RPSM v1.3.1library("tibble")# Simple Data Frames Posit RPSM v3.2.1library("purrr")# Functional Programming Tools Posit RPSM v1.0.2library("broom")# Convert Statistical Objects into Tidy Tibbles Posit RPSM v1.0.7library("glue")# Interpreted String Literals Posit RPSM v1.7.0# For palatte colourslibrary("scales")# Scale Functions for Visualization CRAN v1.3.0# For lipid annotation# Install from BioConductorlibrary("rgoslin")# Lipid Shorthand Name Parsing and Normalization [github::lifs-tools/rgoslin] v2.2.0# For plottinglibrary("plotly")# Create Interactive Web Graphics via 'plotly.js' Posit RPSM v4.10.4# For cognosticslibrary("assertable")# Verbose Assertions for Tabular Data (Data.frames and Data.tables) Posit RPSM v0.2.8# For combining plotly plotslibrary("manipulateWidget")# Add Even More Interactivity to Interactive Charts Posit RPSM v0.11.1# For trellis plotlibrary("trelliscopejs")# Create Interactive Trelliscope Displays Posit RPSM v0.2.6
This Quarto document uses the Quarto Extension schochastics/quarto-social-share. Prior to rendering this document via command quarto render index.qmd, remember to install the extension by running quarto install extension schochastics/quarto-social-share in the command line terminal. This will install the extension under the _extensions subdirectory. If you’re using version control, you will want to check in this directory.
We read the transition’s peak area measurement using Reversed-Phase Ultra High-Performance Liquid Chromatography/ Mass Spectrometry (RP-UHPLC/MS). The measurement is done by lab 3 during Phase II of the project.
The first column is the name of the sample. Subsequent columns are the transition’s integrated peak areas.
The order in which the samples are injected to the mass spectrometer for data acquisition.
Acqusition_Time_Stamp
The time the sample is being measured. It is expressed yyyy-mm-dd hh:mm:ss timezone.
Sample_Name
The name of the sample.
QC_Sample_Type
Quality control category of the sample. More information can be found in Broadhurst et. al. 2018(6).
SPL stands for experimental/unknown samples.
BQC stands for batch/pooled quality control.
TQC stands for technical (or pooled lipid extracts) quality control.
RQC stands for response (or pooled QC dilution) quality control.
PBLK stands for processed blanks.
LTR stands for long term reference.
NIST stands for SRM1950 NIST plasma sample from Simón-Manso et. al. 2013(7) . It is a standard reference material provides a method to allow quality assessment across different laboratories.
We first merge the peak area data with the dilution and sample annotation. Next, we created a nested dilution_data for each transition name.
dilution_data will be used later to do dilution curve related analysis for each transition.
Code
sample_annot_column_names<-sample_annot|>colnames()dilution_annot_column_names<-dilution_annot|>colnames()nested_dilution_data<-dilution_annot|>dplyr::inner_join(y =sample_annot, by ="Sample_Name")|>dplyr::inner_join(y =area_data, by ="Sample_Name")|>tidyr::pivot_longer(cols =-dplyr::any_of(c(sample_annot_column_names,dilution_annot_column_names)), names_to ="Transition_Name", values_to ="Area")|>dplyr::group_by(.data[["Transition_Name"]])|>tidyr::nest()|>dplyr::rename(dilution_data =dplyr::all_of("data"))|>dplyr::ungroup()
Here is a preview of the first five rows of the nested_dilution_data. For each dilution_data of a given transition name, we display the first five rows.
The R package rgoslin is useful in getting additional information of lipid names such as the lipid classes. A column called For_Rgoslin was created to convert each transition name that can be parsed by rgoslin. Unfortunately only Coenzyme Q10 is unable to be parsed by rgoslin. We thus add the relevant information manually.
Below is an code example (not part of the pipeline) on how rgoslin can be used
We now proceed to calculate some statistical summary for each transition using the nested dilution_data and sample_data.
Calculate RQC/Dilution Statistics
The following statistics are calculated from dilution_data.
Column Name
Column Description
r_corr
Pearson Correlation \(R\) value.
r2_linear
Linear Regression \(R^2\) Value.
r2_linear_<=0.8
Set to TRUE if r2_linear is less than 0.8.
The relevant functions to do these calculations are as follows
Code
#' @title Create Linear Model#' @description A wrapper to create a linear model from dilution data#' @param dilution_data A data frame or tibble containing dilution data#' @param conc_var Column name in `dilution_data` to indicate concentration#' @param signal_var Column name in `dilution_data` to indicate signal#' @return A linear model object from `stats:lm()` with formula#' `signal_var ~ conc_var` from data `diltuion_data`#' @examples#' dilution_percent <- c(10, 20, 40, 60, 80, 100)#' area <- c(22561, 31178, 39981, 48390, 52171, 53410)#' dilution_data <- data.frame(Dilution_Percent = dilution_percent, Area = area)#' linear_model <- create_linear_model(dilution_data,#' "Dilution_Percent",#' "Area")#' linear_model#' @rdname create_linear_model#' @exportcreate_linear_model<-function(dilution_data, conc_var, signal_var){conc_var<-paste0("`",conc_var,"`")signal_var<-paste0("`",signal_var,"`")# Create the formulalinear_formula<-stats::as.formula(paste(signal_var, "~",paste(conc_var, collapse =" + ")))# Create the linear model on dilution datalinear_model<-stats::lm(linear_formula, data =dilution_data)return(linear_model)}#' @title Create Quadratic Model#' @description A wrapper to create a quadratic model for dilution data#' @param dilution_data A data frame or tibble containing dilution data#' @param conc_var Column name in `dilution_data` to indicate concentration#' @param signal_var Column name in `dilution_data` to indicate signal#' @return A linear model object from `stats:lm()` with formula#' `signal_var ~ conc_var + I(conc_var * conc_var)`#' from data `diltuion_data`#' @examples#' dilution_percent <- c(10, 20, 40, 60, 80, 100)#' area <- c(22561, 31178, 39981, 48390, 52171, 53410)#' dilution_data <- data.frame(Dilution_Percent = dilution_percent, Area = area)#' quad_model <- create_quad_model(dilution_data,#' "Dilution_Percent",#' "Area")#' quad_model#' @rdname create_quad_model#' @exportcreate_quad_model<-function(dilution_data, conc_var, signal_var){conc_var<-paste0("`",conc_var,"`")signal_var<-paste0("`",signal_var,"`")# Create the formulaquad_formula<-stats::as.formula(paste(signal_var, "~",paste(conc_var, "+",paste0("I(", conc_var, " * ",conc_var, ")"))))# Create the quadratic model on dilution dataquad_model<-stats::lm(quad_formula, data =dilution_data)return(quad_model)}#' @title Calculate Linear Model's Goodness Of Fit#' @description Calculate the Goodness of Fit of the Dilution Linear Model#' @param dilution_data A data frame or tibble containing dilution data#' @param conc_var Column name in `dilution_data` to indicate concentration#' @param signal_var Column name in `dilution_data` to indicate signal#' @return A tibble containing the Goodness of Fit measures of the linear model#' The Goodness of Fit measures are the Pearson correlation coefficient (R) and#' the R^2#' @details The function will return a tibble with NA values#' if the number of dilution points is less than or equal to three#' @examples#' dilution_percent <- c(10, 20, 40, 60, 80, 100)#' area <- c(22561, 31178, 39981, 48390, 52171, 53410)#' dilution_data <- data.frame(Dilution_Percent = dilution_percent, Area = area)#' dil_linear_gof <- calculate_gof_linear(dilution_data,#' "Dilution_Percent", "Area")#' dil_linear_gof#' @rdname calculate_gof_linear#' @exportcalculate_gof_linear<-function(dilution_data, conc_var, signal_var){dil_linear_gof<-tibble::tibble(r_corr =NA, r2_linear =NA)if(is.null(nrow(dilution_data))){return(dil_linear_gof)}# Drop rows whose value of signal_var is NAdilution_data<-dilution_data|>tidyr::drop_na(.data[[signal_var]])# Return NA for too little points# Horizontal, Vertical line or single pointif(nrow(dilution_data)<=2){return(dil_linear_gof)}if(stats::sd(dilution_data[[conc_var]])==0){return(dil_linear_gof)}if(stats::sd(dilution_data[[signal_var]])==0){return(dil_linear_gof)}# Get the correlation resultscor_result<-broom::tidy(stats::cor.test(dilution_data[[signal_var]],dilution_data[[conc_var]], method ="pearson"))r_corr<-round(cor_result$estimate, digits =6)# Create the linear model on dilution datalinear_model<-create_linear_model(dilution_data, conc_var, signal_var)# Get GOF for each modellinear_gof<-broom::glance(linear_model)# Get R2r2_linear<-round(linear_gof$r.squared, digits =6)dil_linear_gof<-tibble::tibble(r_corr =r_corr, r2_linear =r2_linear)return(dil_linear_gof)}#' @title Summarise Dilution Curve Data#' @description Get the summary statistics of the dilution data#' for one group or batch#' @param dilution_data A data frame or tibble containing dilution data#' @param conc_var Column name in `dilution_data` to indicate concentration (x-axis)#' @param signal_var Column name in `dilution_data` to indicate signal (y-axis)#' @return A tibble containing the Goodness of Fit measures of the linear model#' The Goodness of Fit measures are the Pearson correlation coefficient (R) and R^2get_dilution_summary<-function(dilution_data, conc_var, signal_var){dil_linear_gof<-calculate_gof_linear(dilution_data,conc_var, signal_var)dilution_summary<-dil_linear_gofreturn(dilution_summary)}
Ratio of median signal from processed blank sample to median signal from techincal QC (or pooled lipid extracts) samples.
bqc_cv_>20%
Set to TRUE if bqc_cv is greater than 20%.
tqc_cv_>20%
Set to TRUE if tqc_cv is greater than 20%.
pblk_to_tqc_ratio_>10%
Set to TRUE if pblk_to_tqc_ratio_% is greater than 10%.
The relevant functions to do these calculations are as follows.
Code
#' Function used to calculate statistics summary (RSD,Signal to Blank Ratio) of a given input numeric vector#'#' @param lipid_data_vector Input lipid data numeric vector#' @param prepend_string Input characters to prepend to the result table column name#'#' @return Output statistical summary table (RSD,Signal to Blank Ratio)#' @exportget_sample_description<-function(lipid_data_vector, prepend_string){description_table<-tibble::tibble( mean =lipid_data_vector|>mean(na.rm =TRUE), median =lipid_data_vector|>stats::median(na.rm =TRUE), sd =lipid_data_vector|>stats::sd(na.rm =TRUE), mad =lipid_data_vector|>stats::mad(na.rm =TRUE,constant =1),)|>dplyr::mutate( `cv_%` =.data$sd/.data$mean*100, `cv_robust_%` =(1.4826*.data$mad)/.data$median*100)description_table<-description_table|>dplyr::rename_all(~paste0(prepend_string,"_",.))return(description_table)}#' Function used to calculate QC Sample Types statistics summary (RSD,Signal to Blank Ratio) of a given column name from the input data set#'#' @param lipid_Data Input lipid data that must contain the columns from `qc_sample_type_var` and `signal_var`#' @param qc_sample_type_var Input column name indicating QC sample_type#' @param signal_var Input column name to extract from the input lipid data and calculate qc sample type statistics#' #' @return Output QC Sample Types statistical summary table (RSD,Signal to Blank Ratio)#' @exportget_qc_sample_summary<-function(lipid_data, qc_sample_type_var, signal_var){spl_summary<-lipid_data|>dplyr::filter(.data[[qc_sample_type_var]]=="SPL")|>dplyr::pull(.data[[signal_var]])|>get_sample_description(prepend_string ="spl")bqc_summary<-lipid_data|>dplyr::filter(.data[[qc_sample_type_var]]=="BQC")|>dplyr::pull(.data[[signal_var]])|>get_sample_description(prepend_string ="bqc")tqc_summary<-lipid_data|>dplyr::filter(.data[[qc_sample_type_var]]=="TQC")|>dplyr::pull(.data[[signal_var]])|>get_sample_description(prepend_string ="tqc")tqc_median<-lipid_data|>dplyr::filter(.data[[qc_sample_type_var]]%in%c("TQC"))|>dplyr::pull(!!signal_var)|>stats::median(na.rm =TRUE)pblk_median<-lipid_data|>dplyr::filter(.data[[qc_sample_type_var]]=="PBLK")|>dplyr::pull(!!signal_var)|>stats::median(na.rm =TRUE)`pblk_to_tqc_ratio_%`<-pblk_median/tqc_median*100pblk_summary<-tibble::tibble( pblk_median =pblk_median, `pblk_to_tqc_ratio_%` =`pblk_to_tqc_ratio_%`)qc_stat_table<-dplyr::bind_cols(bqc_summary, tqc_summary,spl_summary,pblk_summary)return(qc_stat_table)}
In this section, we create the palette for our dilution plots as well as our injection sequence plots. The Okato-Ito palette(9) is used.
The relevant utility functions to do this are as follows.
Code
#' @title Create Cyclic Character Sequence#' @description Create cyclic character sequence#' @param group_name A character vector as input#' @param output_length The length of the output sequence#' @return A cyclic character sequence with length `output_length`#' @details Taken from#' https://community.rstudio.com/t/fill-in-a-sequence-of-letters-based-on-a-given-order/88823/3#' @examples#' group_name <- c("red", "green", "blue")#' create_char_seq(group_name, output_length = 2)#' create_char_seq(group_name, output_length = 5)#'#' @rdname create_char_seq#' @exportcreate_char_seq<-function(group_name, output_length){# whole integer divisioni<-output_length%/%length(group_name)# remainderr<-output_length%%length(group_name)# Set the number of cycles neededif(r>0){t<-i+1}else{t<-i}# Create the set and cut by the output_lengthset<-rep(group_name, t)output<-set[1:output_length]return(output)}
We create a colour for each dilution batch name. The palette for the dilution plot are created as such.
Code
# Get the dilution batch name from dilution_annotdilution_batch_name<-dilution_annot|>dplyr::pull(.data[["Dilution_Batch_Name"]])|>unique()|>as.character()# Create palette for each dilution batch for plottingdilution_pal<-c("#377eb8")|>create_char_seq(output_length =length(dilution_batch_name))|>stats::setNames(dilution_batch_name)scales::show_col(dilution_pal)
We create a colour for each QC sample type. The palette for the injection sequence plot are created as such.
Code
qc_sample_type<-c("SPL","TQC", "BQC","NIST", "LTR", "PBLK","RQC")# Create palette for each qc sample type for plottingqc_sample_type_pal<-c("#F0E442", "#0072B2", "#D55E00","#009E73","#56B4E9", "#CC79A7", "#E69F00")|>create_char_seq(output_length =length(qc_sample_type))|>stats::setNames(qc_sample_type)scales::show_col(qc_sample_type_pal)
Plot Dilution Curves
With the palette created, we will create a dilution plot for each transition.
The relevant functions to do this plot are as follows.
Code
#' @title Plot Dilution Curve Using `plotly`#' @description Plot Dilution Data using `plotly`#' @param dilution_data A data frame or tibble containing dilution data#' @param dilution_title Title to use for each dilution plot#' @param dilution_pal Input palette for each dilution batch group in `dil_batch_var`.#' It is a named char vector where each value is a colour and#' name is a dilution batch group given in `dil_batch_var`#' @param sample_name_var Column name in `dilution_data`#' to indicate the sample name#' @param dil_batch_var Column name in `dilution_data`#' to indicate the group name of each dilution batch,#' used to colour the points in the dilution plot#' @param conc_var Column name in `dilution_data` to indicate concentration#' @param conc_var_units Unit of measure for `conc_var` in the dilution plot#' @param conc_var_interval Distance between two tick labels#' @param signal_var Column name in `dilution_data` to indicate signal#' @param x_axis_title The x axis title for the graph. If there is no input,#' column name corresponding to `conc_var` will be used instead.#' @param y_axis_title The y axis title for the graph. If there is no input,#' column name corresponding to `signal_var` will be used instead.#' @param y_axis_rangemode Wrapper of `plotly` `rangemode`. If "normal" the#' y range should be computed in relation to the extrema of the input data.#' If "tozero", the range extends to 0. If "nonnegative",#' the range is non-negative, regardless of the input data.#' Default: "normal"#' @param lin_reg_name The name of the linear regression line used to fit#' the dilution data. The name will be displayed in the legend.#' Default: "lin reg"#' @param quad_reg_name The name of the quadratic regression line used to#' fit the dilution data. The name will be displayed in the legend.#' Default: "quad reg"#' @param show_legend Decide if we want to create legends for the plot.#' If we are creating a sub plot with shared legends, ensure that only#' the first plot has show_legend set to TRUE, the rest must be set to FALSE.#' Default: TRUE#' @param show_y_axis_title Decide if we want to show the y-axis title.#' Default: TRUE#' @param annotation_var Column name in `dilution_data` to show in hover point#' @return Output `plotly` dilution plot data of one dilution batch per transition#' @rdname plot_dil_curve_plotly#' @exportplot_dil_curve_plotly<-function(dilution_data,dilution_title,dilution_pal,sample_name_var="Sample_Name",dil_batch_var="Dilution_Batch_Name",conc_var="Dilution_Percent",conc_var_units="%",conc_var_interval=50,signal_var="Area",x_axis_title="",y_axis_title="",y_axis_rangemode="normal",lin_reg_name="lin reg",quad_reg_name="quad reg",show_legend=TRUE,show_y_axis_title=TRUE,annotation_var=c()){# Drop rows whose value of signal_var is NAdilution_data<-dilution_data|>tidyr::drop_na(.data[[signal_var]])# For the hover texttext_input<-glue::glue("<b>{dilution_data[[sample_name_var]]}</b>\\ <br>{conc_var}: {dilution_data[[conc_var]]}\\ <br>{signal_var}: {format(dilution_data[[signal_var]], big.mark = ",", nsmall = 1)}")for(thingsinannotation_var){text_input<-glue::glue("{text_input}\\ <br>{things}: {dilution_data[[things]]}")}# Convert the column that holds the dilution_batch_var# to factorsdilution_data[[dil_batch_var]]<-dilution_data[[dil_batch_var]]|># To handle the case that batches are named "1", "2", "3"as.character()|># Factor batch based on the order of the palette colours# So that the order will matchfactor(levels =names(dilution_pal))# Create the dots in the dilution plotp<-plotly::plot_ly()|>plotly::add_trace(data =dilution_data, x =dilution_data[[conc_var]], y =dilution_data[[signal_var]], type ="scatter", mode ="markers", marker =list(size =10, opacity =1, line =list(color ="black", width =1.5)), name =dilution_data[[dil_batch_var]], color =dilution_data[[dil_batch_var]],# See https://github.com/plotly/plotly.R/issues/1985 colors =unname(dilution_pal), hoverinfo ="text", text =dilution_data[[sample_name_var]], hovertemplate =text_input, legendgroup ="group1", showlegend =show_legend, inherit =FALSE)if(nrow(dilution_data)>3){# When we need to plot a horizontal lineif(stats::sd(dilution_data[[signal_var]])==0){min_x<-min(dilution_data[[conc_var]], na.rm =TRUE)max_x<-max(dilution_data[[conc_var]], na.rm =TRUE)cont_y<-unique(dilution_data[[signal_var]])p<-p|>plotly::add_segments(x =min_x, xend =max_x, y =cont_y, yend =cont_y, name =lin_reg_name, line =list(color ="black", width =1), legendgroup ="group2", showlegend =show_legend, inherit =FALSE)}elseif(stats::sd(dilution_data[[conc_var]])==0){# When we need to plot a vertical linemin_y<-min(dilution_data[[signal_var]], na.rm =TRUE)max_y<-max(dilution_data[[signal_var]], na.rm =TRUE)cont_x<-unique(dilution_data[[conc_var]])p<-p|>plotly::add_segments(x =cont_x, xend =cont_x, y =min_y, yend =max_y, name =lin_reg_name, line =list(color ="black", width =1), legendgroup ="group2", showlegend =show_legend, inherit =FALSE)}else{# Plot the curves# Model the datalinear_model<-create_linear_model(dilution_data, conc_var, signal_var)quad_model<-create_quad_model(dilution_data, conc_var, signal_var)dilution<-seq(min(dilution_data[[conc_var]]),max(dilution_data[[conc_var]]), length.out =15)# See https://github.com/plotly/plotly.R/issues/1985linear_prediction<-linear_model|>stats::predict(tibble::tibble(!!conc_var:=dilution))|>unname()quad_prediction<-quad_model|>stats::predict(tibble::tibble(!!conc_var:=dilution))|>unname()# Create the linear and quadratic curve in the dilution plotp<-p|>plotly::add_trace(data =dilution_data, x =dilution, y =linear_prediction, type ="scatter", mode ="lines", name =lin_reg_name, line =list(color ="black", width =1), legendgroup ="group2", showlegend =show_legend, inherit =FALSE)|>plotly::add_trace(data =dilution_data, x =dilution, y =quad_prediction, type ="scatter", mode ="lines", name =quad_reg_name, line =list(color ="red", width =1, opacity =0.25), legendgroup ="group3", showlegend =show_legend, inherit =FALSE)}}# Create x axis title# If conc_var_units is empty, do not add bracketsif(x_axis_title!=""){x_title<-x_axis_title}else{x_title<-conc_var}if(conc_var_units!=""){x_title<-paste0(x_title, " (", conc_var_units, ")")}# Create y axis titleif(y_axis_title!=""){y_title<-y_axis_title}else{y_title<-signal_var}# Create the layout to be the same as ggplot2p<-p|>plotly::layout(title =list(text =dilution_title, x =0.1) , xaxis =list(title =x_title, titlefont =list(size =10), gridcolor ="rgb(255,255,255)", showgrid =TRUE, showline =FALSE, showticklabels =TRUE, tickcolor ="rgb(127,127,127)", ticks ="outside", zeroline =FALSE, tickfont =list(size =10), tick0 =0, dtick =conc_var_interval, showspikes =TRUE, spikemode ="toaxis+marker", spikesnap ="data"), yaxis =list(title =NA, autorange =TRUE, fixedrange =FALSE, rangemode =y_axis_rangemode, titlefont =list(size =10), gridcolor ="rgb(255,255,255)", showgrid =TRUE, showline =FALSE, showticklabels =TRUE, tickcolor ="rgb(127,127,127)", ticks ="outside", zeroline =FALSE, tickfont =list(size =10), exponentformat ="e", showspikes =TRUE, spikemode ="toaxis+marker", spikesnap ="data"), hovermode ="x", legend =list(orientation ="v", font =list(size =10)), paper_bgcolor ="rgb(255,255,255)", plot_bgcolor ="rgb(229,229,229)"#showlegend = show_legend)# Add the y axis titleif(isTRUE(show_y_axis_title)){p<-p|>plotly::add_annotations( x =0, y =1, xref ="paper", yref ="paper", xanchor ="left", yanchor ="bottom", text =y_title, showarrow =FALSE)}return(p)}
Here is the code that plots the dilution curve for each transition. A new column dilution_panel is created to contain such a plot. Both nested data dilution_data and sample_data are removed in the process.
The injection sequence plot consist of a scatter plot and a raincloud plot.
Scatter Plot
The relevant function to do the scatter plot is as follows.
Code
#' @title Plot Scatter Plot Using `plotly`#' @description Plot Scatter Plot using `plotly`#' @param scatterplot_data A data frame or tibble containing data#' to be plotted in the scatter plot#' @param scatterplot_pal Input palette for category group#' It is a named char vector where each value is a colour and#' name is a category group.#' @param x_axis_var Column name in `scatterplot_data` to use#' as x axis of scatter plot. Default: 'Injection_Sequence'#' @param y_axis_var Column name in `scatterplot_data` to use#' as y axis of scatter plot. Default: 'Area'#' @param sample_name_var Column name in `scatterplot_data` to use#' in the hover text for each point in the scatter plot.#' Default: 'Sample_Name'#' @param legend_category_var Column name in `scatterplot_data` to use#' for legend and colour, Default: 'QC_Sample_Type'#' @param legend_title Input character to use as legend title. #' If there is no input, input character from `legend_category_var` #' will be used instead. Default: ''#' @param annotation_var Column name in `scatterplot_data` to show in hover point#' Default: c()#' @param lod_val Input numeric to indicate the limit of detection value in #' the scatter plot as a red horizontal line. It is not plotted if no input#' is provided. Default: NA#' @param show_y_axis_title Decide if we want to show the y-axis title.#' Default: FALSE#' @param show_legend Decide if we want to create legends for the plot.#' If we are creating a sub plot with shared legends, ensure that only#' the first plot has show_legend set to TRUE, the rest must be set to FALSE.#' Default: TRUE#' @return Output `plotly` scatter plot#' @rdname plot_scatterplot_plotly#' @exportplot_scatterplot_plotly<-function(scatterplot_data,scatterplot_pal,x_axis_var="Injection_Sequence",y_axis_var="Area",sample_name_var="Sample_Name",legend_category_var="QC_Sample_Type",legend_title="",annotation_var=c(),lod_val=NA,show_y_axis_title=FALSE,show_legend=TRUE){# Drop rows whose value of signal_var is NAscatterplot_data<-scatterplot_data|>tidyr::drop_na(.data[[y_axis_var]])# For the hover texttext_input<-glue::glue("<b>{scatterplot_data[[sample_name_var]]}</b>\\ <br>{x_axis_var}: {scatterplot_data[[x_axis_var]]}\\ <br>{y_axis_var}: {format(scatterplot_data[[y_axis_var]], big.mark = ",", nsmall = 1)}")for(thingsinannotation_var){text_input<-glue::glue("{text_input}\\ <br>{things}: {scatterplot_data[[things]]}")}# Convert the column that holds the legend_category_var# to factorsscatterplot_data[[legend_category_var]]<-scatterplot_data[[legend_category_var]]|># To handle the case that batches are named "1", "2", "3"as.character()|># Factor batch based on the order of the palette colours# So that the order will matchfactor(levels =names(scatterplot_pal))scatter<-plotly::plot_ly()|>plotly::add_trace(data =scatterplot_data, x =scatterplot_data[[x_axis_var]], y =scatterplot_data[[y_axis_var]], type ="scattergl", mode ="markers", visible =TRUE, marker =list( size =8 , opacity =1, line =list(color ="black", width =1.5)), name =scatterplot_data[[legend_category_var]], color =scatterplot_data[[legend_category_var]], colors =unname(scatterplot_pal), legendgroup =scatterplot_data[[legend_category_var]], showlegend =show_legend, hoverinfo ="text", text =text_input, inherit =FALSE)|># Create the layout to be the same as ggplot2plotly::layout(xaxis =list(title =x_axis_var, titlefont =list(size =10), tickfont =list(size =10), tickangle =0, gridcolor ="rgb(255,255,255)", showgrid =TRUE, showticklabels =TRUE, tickcolor ="rgb(127,127,127)", ticks ='outside', zeroline =FALSE, showspikes =TRUE, spikemode ="toaxis+marker", spikesnap ="data", rangeslider =list(thickness =0.05), autorange =TRUE, fixedrange =FALSE), yaxis =list(title ="", titlefont =list(size =10), tickfont =list(size =10), gridcolor ="rgb(255,255,255)", showgrid =TRUE, showline =FALSE, showticklabels =TRUE, tickcolor ="rgb(127,127,127)", ticks ="outside", zeroline =FALSE, showspikes =TRUE, spikemode ="toaxis+marker", spikesnap ="data", autorange =TRUE, fixedrange =FALSE), hovermode ="closest", legend =list(title =list(text =legend_title), orientation ='v', font =list(size =10)), paper_bgcolor ="rgb(255,255,255)", plot_bgcolor ="rgb(229,229,229)")# Add the LOD lineif(!isTRUE(is.na(lod_val))){scatter<-scatter|>plotly::add_trace(data =scatterplot_data, x =scatterplot_data[[x_axis_var]], y =lod_val, yend =lod_val, type ="scattergl" , mode ="lines", name ="LOD Line", line =list(color ="red", width =2, dash ="solid"), inherit =FALSE)}# Add the y axis titleif(isTRUE(show_y_axis_title)){scatter<-scatter|>plotly::add_annotations( x =0, y =1, xref ="paper", yref ="paper", yanchor ="bottom", text =y_axis_var, showarrow =FALSE)}return(scatter)}
The first step is to create a scatter plot (Area vs Injection Sequence) for each transition. A new column scatterplot_panel is created to contain such a plot.
The relevant function to do the raincloud plot is as follows.
Code
#' @title Plot Raincloud Plot Using `plotly`#' @description Plot Raincloud Plot using `plotly`#' @param raincloud_data A data frame or tibble containing data to be plotted#' into a raincloud plot#' @param raincloud_pal Input palette for category group#' It is a named char vector where each value is a colour and#' name is a category group#' @param x_axis_var Column name in `raincloud_data` to use#' as x axis of raincloud plot. Default: 'QC_Sample_Type'#' @param y_axis_var Column name in `raincloud_data` to use#' as y axis of raincloud plot. Default: 'Area'#' @param sample_name_var Column name in `raincloud _data` to use#' in the hover text for each point in the raincloud plot.#' Default: 'Sample_Name'#' @param legend_category_var Column name in `raincloud _data` to use#' for legend and colour, Default: 'QC_Sample_Type'#' @param legend_title Input character to use as legend title. #' If there is no input, input character from `legend_category_var` #' will be used instead. Default: ''#' @param annotation_var Column name in `raincloud_data` to show in hover point#' Default: c()#' @param show_y_axis_title Decide if we want to show the y-axis title.#' Default: FALSE#' @param show_legend Decide if we want to create legends for the plot.#' If we are creating a sub plot with shared legends, ensure that only#' the first plot has show_legend set to TRUE, the rest must be set to FALSE.#' Default: TRUE#' @return Output `plotly` raincloud plot#' @rdname plot_raincloud_plotly#' @exportplot_raincloud_plotly<-function(raincloud_data,raincloud_pal,x_axis_var="QC_Sample_Type",y_axis_var="Area",sample_name_var="Sample_Name",legend_category_var="QC_Sample_Type",legend_title="",annotation_var=c(),show_y_axis_title=FALSE,show_legend=TRUE){# Drop rows whose value of signal_var is NAraincloud_data<-raincloud_data|>tidyr::drop_na(.data[[y_axis_var]])# For the hover texttext_input<-glue::glue("<b>{raincloud_data[[sample_name_var]]}</b>\\ <br>{x_axis_var}: {raincloud_data[[x_axis_var]]}\\ <br>{y_axis_var}: {format(raincloud_data[[y_axis_var]], big.mark = ",", nsmall = 1)}")for(thingsinannotation_var){text_input<-glue::glue("{text_input}\\ <br>{things}: {raincloud_data[[things]]}")}# Convert the column that holds the legend_category_var# to factorsraincloud_data[[legend_category_var]]<-raincloud_data[[legend_category_var]]|># To handle the case that batches are named "1", "2", "3"as.character()|># Factor batch based on the order of the palette colours# So that the order will matchfactor(levels =names(raincloud_pal))violin<-plotly::plot_ly()|>plotly::add_trace(data =raincloud_data, x =raincloud_data[[x_axis_var]], y =raincloud_data[[y_axis_var]], type ="violin", opacity =1,#type = 'box', color =raincloud_data[[legend_category_var]], colors =unname(raincloud_pal), legendgroup =raincloud_data[[legend_category_var]], showlegend =show_legend, text =text_input, orientation ="v", box =list( visible =TRUE, width =1, line =list(color ="black"), fillcolor ='white'), side ="positive",#boxpoints = "all", points ="suspectedoutliers", jitter =0,#boxmean = "sd", meanline =list(visible =TRUE, color ="red"), marker =list( size =7 , opacity =1,#outliercolor = 'rgba(133,231,1,1)', line =list(color ="black", width =1.5)), pointpos =-1, inherit =FALSE)|>plotly::layout(xaxis =list(title =x_axis_var, titlefont =list(size =10), tickfont =list(size =10), tickangle =0, gridcolor ="rgb(255,255,255)", type ='category', showgrid =TRUE, showline =FALSE, showticklabels =TRUE, tickcolor ="rgb(127,127,127)", ticks ='outside', zeroline =FALSE, tickfont =list(size =10), autorange =TRUE, fixedrange =FALSE), yaxis =list(title ="", titlefont =list(size =10), tickfont =list(size =10), gridcolor ="rgb(255,255,255)", showgrid =TRUE, showline =FALSE, showticklabels =TRUE, tickcolor ="rgb(127,127,127)", ticks ="outside", zeroline =FALSE, tickfont =list(size =10), autorange =TRUE, fixedrange =FALSE), hovermode ="closest", legend =list(title =list(text =legend_title), orientation ="v", font =list(size =10)), paper_bgcolor ="rgb(255,255,255)", plot_bgcolor ="rgb(229,229,229)")if(isTRUE(show_y_axis_title)){violin<-violin|>plotly::add_annotations( x =0, y =1, xref ="paper", yref ="paper", yanchor ="bottom", text =y_axis_var, showarrow =FALSE)}return(violin)}
The second step is to create a raincloud plot (QC Sample Type (without RQC) vs Injection Sequence) for each transition. A new column called no_rqc_data is created from sample_data to filter out the RQC samples. The filtered samples are then used to create the plot. A new column raincloud_panel is created to contain such a plot.
The last step is to combine the two plots together. dilution_data, sample_data, no_rqc_data, scatterplot_panel and raincloud_panel are removed in the process.
Here is a preview of the injection sequence plot of first row of the injection_sequence_table.
Code
injection_sequence_table$panel[[1]]
Cognostics
To output these interactive plots as a trellis plot in html, we need to convert our current tibble and dilution_plot_table and injection_sequence_table into a cognostics dataframe.
The cognostics dataframe consist of three components
A group of columns known as conditioning variables. They will form the unique id of the trellis plot. As such, each row of these columns must be unique. In our example, the column used is Transition_Name
One column that holds the images to display each plot in the trellis. This column is known as the panel variable. We just created this column using trelliscopejs::pmap_plot earlier.
The other columns will be grouped as general cognostics columns.
Cognostics Conversion Functions
The relevant functions to convert all columns, except the panel variable, to cognostics objects are as follows.
Code
#' @title Validate Cognostics Data#' @description Validate Cognostics Data#' @param cog_df A data frame or tibble that contains cognostics information#' @param needed_column A vector consisting of needed column names that#' must be found in `cog_df`,#' Default:#' `c("col_name_vec", "desc_vec", "type_vec")`#' @return An error if the things in `needed_column`#' is not found in the Cognostics Data#' @examples#' # Create Cognostics Dataframe#' col_name_vec <- c("Transition_Name", "Dilution_Batch_Name")#'#' desc_vec <- c("Transition_Name", "Dilution_Batch_Name")#'#' type_vec <- c("factor","factor")#'#' cog_df <- data.frame(col_name_vec = col_name_vec,#' desc_vec = desc_vec,#' type_vec = type_vec)#'#' validate_cog_df(cog_df)#'#' @rdname validate_cog_df#' @exportvalidate_cog_df<-function(cog_df,needed_column=c("col_name_vec","desc_vec","type_vec")){# Check if things in needed_column are in cog_dfassertable::assert_colnames(cog_df, needed_column, only_colnames =FALSE, quiet =TRUE)}#' @title Update Cognostics Manually#' @description Update cognostics on `summary_table` based on#' the cognostics parameters given by `cog_df`. We assume#' `cog_df` is created manually#' @param summary_table The summary data frame or tibble generated#' by function [summarise_dilution_data()]#' @param cog_df A data frame or tibble output#' from the function [create_default_cog_df()] or created manually#' @param col_name_vec Column name in `cog_df` to indicate the columns#' in `summary_table` that needs to be converted to a cognostics,#' Default: 'col_name_vec'#' @param desc_vec Column name in `cog_df` to indicate the description#' for each cognostics as define in `trelliscopejs::cog`,#' Default: 'desc_vec'#' @param type_vec Column name in `cog_df` to indicate the type#' of each cognostics as define in `trelliscopejs::cog`,#' Default: 'type_vec'#' @return `summary_table` with some columns converted#' to type cog as defined in `trelliscopejs::cog`#' @details#' More details in `trelliscopejs::cog` can be found in#' <https://rdrr.io/cran/trelliscopejs/man/cog.html>#'#' @rdname update_cog_manual#' @exportupdate_cog_manual<-function(summary_table, cog_df,col_name_vec="col_name_vec",desc_vec="desc_vec",type_vec="type_vec"){if(is.null(cog_df)){return(summary_table)}#Check if cog_df is valid with the relevant columnsvalidate_cog_df(cog_df, needed_column =c(col_name_vec,desc_vec,type_vec))# See this webpage to learn how to mutate specific columns.# https://stackoverflow.com/questions/52998471/dynamically-determine-if-a-dataframe-column-exists-and-mutate-if-it-doesfor(colnameincolnames(summary_table)){row_index<-which(cog_df$col_name_vec==colname)if(length(row_index)==1){summary_table<-summary_table|>dplyr::mutate(dplyr::across( .cols =dplyr::one_of(cog_df[[col_name_vec]][row_index]), .fns = \(x)trelliscopejs::cog( val =x, desc =cog_df[[desc_vec]][row_index], type =cog_df[[type_vec]][row_index])))}}return(summary_table)}#' Update Cognostics Automatically#' @description Update cognostics on the dilution summary based on#' the cognostics data frame given by the function `create_default_cog_df`.#' @param summary_table The summary table generated#' by function [summarise_dilution_data()]#' @return `summary_table` with some columns converted#' to type cog as defined in `trelliscopejs::cog`#' @details#' More details in `trelliscopejs::cog` can be found in#' <https://rdrr.io/cran/trelliscopejs/man/cog.html>#' @rdname update_cog_auto#' @exportupdate_cog_auto<-function(summary_table){# Create the default cognostics tablecog_df<-create_default_cog_df()# Update the dilution summary columns and# convert the relevant to cognosticssummary_table<-summary_table|>update_cog_manual(cog_df =cog_df)return(summary_table)}#' @title Convert To Cognostics#' @description Convert columns in `summary_table` to `trelliscopejs` cognostics#' for the `Trelliscope` display#' @param summary_table Input summary table as a#' data frame or tibble#' @param cog_df A data frame or tibble that contains cognostics information#' If no input is given the cognostics information generated by function#' [create_default_cog_df()] will be used.#' Default: NULL#' @param grouping_variable A character vector of#' column names in `summary_table`to indicate how each dilution curve#' should be grouped by. It is also going to be used as a conditional#' cognostics in the `trelliscopejs` report,#' Default: c("Transition_Name")#' @param panel_variable A column name in `summary_table` to be converted#' into a panel for the `Trelliscope` display#' Default: NULL#' @param col_name_vec Column name in `cog_df` to indicate the columns#' in `summary_table` that needs to be converted to a cognostics,#' Default: 'col_name_vec'#' @param desc_vec Column name in `cog_df` to indicate the description#' for each cognostics as define in `trelliscopejs::cog`,#' Default: 'desc_vec'#' @param type_vec Column name in `cog_df` to indicate the type#' of each cognostics as define in `trelliscopejs::cog`,#' Default: 'type_vec'#' @return The dilution summary table with `grouping variable` columns#' converted to conditional cognostics,#' other columns in `summary_table` converted to cognostics#' to be used in the in the `trelliscopejs` report.#'#' @rdname convert_to_cog#' @exportconvert_to_cog<-function(summary_table, cog_df=NULL,grouping_variable=c("Transition_Name"),panel_variable=NULL,col_name_vec="col_name_vec",desc_vec="desc_vec",type_vec="type_vec"){# Check if things in needed_column are in summary_tableassertable::assert_colnames(summary_table, grouping_variable, only_colnames =FALSE, quiet =TRUE)# Check if panel_variable is also a grouping variableif(isTRUE(panel_variable%in%grouping_variable)){stop(paste("panel_variable", panel_variable,"cannot be a grouping_variable"))}# Convert logical columns to characterssummary_table<-summary_table|>dplyr::mutate_if(is.logical,~as.character(.x))# Separate the panel variables if it is in summary_tablepanel_df<-NULLif(!is.null(panel_variable)){panel_df<-summary_table|>dplyr::select(dplyr::any_of(c(grouping_variable,panel_variable)))summary_table<-summary_table|>dplyr::select(-dplyr::any_of(c(panel_variable)))}# Get cognostics for trellis report# First convert the columns based on default cognostics# from create_default_cog_df()# Next convert the columns based on user's input cognostics# Lastly, convert the rest of the columns based on class# Grouping variables must be the conditional columnssummary_table<-summary_table|>update_cog_auto()|>update_cog_manual(cog_df =cog_df, col_name_vec =col_name_vec, desc_vec =desc_vec, type_vec =type_vec)|>trelliscopejs::as_cognostics(cond_cols =grouping_variable, needs_cond =TRUE, needs_key =FALSE)#Convert panel variables if any to trelliscope_panelsif(!is.null(panel_df)&&ncol(panel_df)!=length(grouping_variable)){# Ensure that the grouping variable is converted to# conditional columns# Ensure that the panel variable is converted to# trelliscope_panelpanel_df<-panel_df|>dplyr::select(dplyr::all_of(c(grouping_variable)))|>trelliscopejs::as_cognostics(cond_cols =grouping_variable, needs_cond =TRUE, needs_key =FALSE)|>dplyr::bind_cols(panel_df|>dplyr::select(dplyr::any_of(c(panel_variable))))|>dplyr::mutate(dplyr::across( .cols =dplyr::all_of(panel_variable), .fns = \(x)structure(x, class =c("trelliscope_panels","list"))))# Panel_df to do a left join with dilution summary# Move panel_variable to the endsummary_table<-panel_df|>dplyr::left_join(summary_table, by =grouping_variable)|>dplyr::relocate(dplyr::any_of(c(panel_variable)), .after =dplyr::last_col())}return(summary_table)}
Cognostics Setup
The function trelliscopejs::cog is used to convert a column into cognostic object. The following meta information must be provided in order for the conversion to be successful.
Given a column name, we need to provide
Description of the column name (meta information)
The type of cognostics to used. For our example only “numeric” and “factor” are used. Other factor types can be found in the documentation of trelliscopejs::cog
Here are the relevant functions to create our meta information tibble.
Code
#' @title Create Transition Name Annotation#' Cognostics Data Frame#' @description Create transition name annotation#' cognostics data frame to be used to#' convert columns to class cognostics#' @return A dataframe of default cognostics information#' @details Internal function to create a dataframe of default cognostics#' information to be used by the `trelliscopejs::cog`.#' @examples#' transition_name_annot_cog_df <- create_transition_name_annot_cog_df()#' transition_name_annot_cog_df#'#' @rdname create_transition_name_annot_cog_df#' @exportcreate_transition_name_annot_cog_df<-function(){# More details in `trelliscopejs::cog` can be found in# <https://rdrr.io/cran/trelliscopejs/man/cog.html>col_name_vec<-c("Transition_Name","Lipid_Name","For_Rgoslin","Precursor_Ion","Product_Ion","Retention_Time_[min]","Delta_Retention_Time_[min]","Collision_Energy","Cell_Accelerator_Voltage","Polarity","isISTD")desc_vec<-c("Name of the transition to be mesured in the mass spectrometer.","Converted transition name to suit lipid nomenclature set by Liebisch et. al. 2020.","Input transition for the R package rgoslin to annotate.","The ion to be fragmented into smaller fragment ions.","Ions created from fragmentation of the precursor ion.","Transition name's expectred retention time.","Time window to acquire the transition. Each transition is acquired in the range Retention Time +/- 0.5(Delta Retention Time).","Rate of acceleration as the precursor ions enter the Q2 for fragmentation.","Rate of acceleration as the product ions leave the Q2.","Settings (Positive or Negative) for the mass spectrometry to detect (positive or negative) ions.","Set to TRUE if the given transition name an internal standard.")type_vec<-c("factor","factor","factor","numeric","numeric","numeric","numeric","numeric","numeric","factor","factor")transition_annot_name_cog_df<-data.frame( col_name_vec =col_name_vec, desc_vec =desc_vec, type_vec =type_vec)return(transition_annot_name_cog_df)}#' @title Create Rgoslin Annotation#' Cognostics Data Frame#' @description Create rgoslin annotation#' cognostics data frame to be used to#' convert columns to class cognostics#' @return A dataframe of default cognostics information#' @details Internal function to create a dataframe of default cognostics#' information to be used by the `trelliscopejs::cog`.#' @examples#' rgoslin_cog_df <- create_rgoslin_cog_df()#' rgoslin_cog_df#'#' @rdname create_rgoslin_cog_df#' @exportcreate_rgoslin_cog_df<-function(){# More details in `trelliscopejs::cog` can be found in# <https://rdrr.io/cran/trelliscopejs/man/cog.html> col_name_vec<-c("Lipid.Maps.Category","Lipid.Maps.Main.Class","Species.Name","Molecular.Species.Name","Functional.Class.Abbr","Functional.Class.Synonyms")desc_vec<-c("Lipid MAPS' Lipid Category which the transition name belongs to.","Lipid MAPS' Lipid Main Class which the transition name belongs to.","Represented by the sum composition, i.e., sum of carbon atoms, double bond equivalent and number of additional oxygen atoms, e.g. FA 18:1;O.","Represented by lipid species with identified fatty acyl/alkyl residues, e.g. TG 16:0_18:1_18:1.","Abbreviation used to describe the lipid's functional class based on the grammar used.","Alternative abbreviations used to describe the lipid's functional class.")type_vec<-c("factor","factor","factor","factor","factor","factor")rgoslin_cog_df<-data.frame(col_name_vec =col_name_vec, desc_vec =desc_vec, type_vec =type_vec)return(rgoslin_cog_df)}#' @title Create Dilution Stats#' Cognostics Data Frame#' @description Create dilution statistics#' cognostics data frame to be used to#' convert columns to class cognostics#' @return A dataframe of default cognostics information#' @details Internal function to create a dataframe of default cognostics#' information to be used by the `trelliscopejs::cog`.#' @examples#' dilution_stats_cog_df <- create_dilution_stats_cog_df()#' dilution_stats_cog_df#'#' @rdname create_dilution_stats_cog_df#' @exportcreate_dilution_stats_cog_df<-function(){col_name_vec<-c("r_corr","r2_linear","r2_linear_<=0.8")desc_vec<-c("Pearson Correlation R value.","Linear Regression R^2 Value.","Set to TRUE if r2_linear is less than 0.8.")type_vec<-c("numeric", "numeric","factor")dilution_stats_cog_df<-data.frame(col_name_vec =col_name_vec, desc_vec =desc_vec, type_vec =type_vec)return(dilution_stats_cog_df)}#' @title Create QC Sample Type Stats#' Cognostics Data Frame#' @description Create QC sample type statistics#' cognostics data frame to be used to#' convert columns to class cognostics#' @return A dataframe of default cognostics information#' @details Internal function to create a dataframe of default cognostics#' information to be used by the `trelliscopejs::cog`.#' @examples#' qc_sample_type_stats_cog_df <- create_qc_sample_type_stats_cog_df()#' qc_sample_type_stats_cog_df#'#' @rdname create_qc_sample_type_stats_cog_df#' @exportcreate_qc_sample_type_stats_cog_df<-function(){col_name_vec<-c("bqc_mean","bqc_median","bqc_sd","bqc_mad","bqc_cv_%","bqc_cv_robust_%","tqc_mean","tqc_median","tqc_sd","tqc_mad","tqc_cv_%","tqc_cv_robust_%","spl_mean","spl_median","spl_sd","spl_mad","spl_cv_%","spl_cv_robust_%","pblk_median","pblk_to_tqc_ratio_%","bqc_cv_>20%","tqc_cv_>20%","pblk_to_tqc_ratio_>10%")desc_vec<-c("Mean signal from batch / pooled QC samples defined by Broadhurst et. al. 2018.","Median signal from batch / pooled QC samples defined by Broadhurst et. al. 2018.","Standard deviation of signal from batch / pooled QC samples defined by Broadhurst et. al. 2018.","Median absolute deviation of signal from batch / pooled QC samples defined by Broadhurst et. al. 2018.","Coefficient of variation of signal in % from batch / pooled QC samples defined by Broadhurst et. al. 2018.","Robust coefficient of variation of signal in % from batch / pooled QC samples defined by Broadhurst et. al. 2018.","Mean signal from techincal QC (or pooled lipid extracts) samples defined by Broadhurst et. al. 2018.","Median signal from techincal QC (or pooled lipid extracts) samples defined by Broadhurst et. al. 2018.","Standard deviation of signal from techincal QC (or pooled lipid extracts) samples defined by Broadhurst et. al. 2018.","Median absolute deviation of signal techincal QC (or pooled lipid extracts) samples defined by Broadhurst et. al. 2018.","Coefficient of variation of signal in % from techincal QC (or pooled lipid extracts) samples defined by Broadhurst et. al. 2018.","Robust coefficient of variation in % of signal from techincal QC (or pooled lipid extracts) samples defined by Broadhurst et. al. 2018.","Mean signal from experimental (unknown) samples defined by Broadhurst et. al. 2018.","Median signal from experimental (unknown) defined by Broadhurst et. al. 2018.","Standard deviation of signal from experimental (unknown) defined by Broadhurst et. al. 2018.","Median absolute deviation of signal from experimental (unknown) defined by Broadhurst et. al. 2018.","Coefficient of variation in % of signal from experimental (unknown) defined by Broadhurst et. al. 2018.","Robust coefficient of variation in % of signal from experimental (unknown) defined by Broadhurst et. al. 2018.","Median signal from processed blank samples defined by Broadhurst et. al. 2018.","Ratio of median signal from processed blank sample to median signal from techincal QC (or pooled lipid extracts) samples defined by Broadhurst et. al. 2018.","Set to TRUE if bqc_cv is greater than 20%.","Set to TRUE if tqc_cv is greater than 20%.","Set to TRUE if pblk_to_tqc_ratio_ % is greater than 10%.")type_vec<-c("numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","factor","factor","factor")qc_sample_type_stats_cog_df<-data.frame(col_name_vec =col_name_vec, desc_vec =desc_vec, type_vec =type_vec)return(qc_sample_type_stats_cog_df)}#' @title Create Default Cognostics Data Frame#' @description Create default cognostics data frame to be used to#' convert columns in `dilution_summary` to class cognostics#' @return A dataframe of default cognostics information#' @details Internal function to create a dataframe of default cognostics#' information to be used by the `trelliscopejs::cog`.#' @examples#' cog_df <- create_default_cog_df()#' cog_df#'#' @rdname create_default_cog_df#' @exportcreate_default_cog_df<-function(){# More details in `trelliscopejs::cog` can be found in# <https://rdrr.io/cran/trelliscopejs/man/cog.html>col_name_vec<-c("Transition_Name")desc_vec<-c("Transition_Name")type_vec<-c("factor")cog_df<-data.frame(col_name_vec =col_name_vec, desc_vec =desc_vec, type_vec =type_vec)transition_name_annot_cog_df<-create_transition_name_annot_cog_df()rgoslin_cog_df<-create_rgoslin_cog_df()dilution_stats_cog_df<-create_dilution_stats_cog_df()qc_sample_type_stats_cog_df<-create_qc_sample_type_stats_cog_df()cog_df<-dplyr::bind_rows(transition_name_annot_cog_df,rgoslin_cog_df,dilution_stats_cog_df,qc_sample_type_stats_cog_df,cog_df)return(cog_df)}
We convert each column in dilution_plot_table to relevant cognostics using the function convert_to_cog. With Transition_Name as a conditioning variable, dilution_panel as a panel variable.
Here are the attributes of some columns in dilution_plot_table after conversion to cognostics. Observe that the attributes of the columns are different.
For the column labelled as conditioning (grouping) variable column labelled we have.
We convert each column in injection_sequence_table to relevant cognostics using the function convert_to_cog. With Transition_Name as a conditioning variable, panel as a panel variable.
Here are the attributes of some columns in injection_sequence_table after conversion to cognostics. Observe that the attributes of the columns are different.
For the column labelled as conditioning (grouping) variable column labelled we have.
> $cog_attrs
> $cog_attrs$desc
> [1] "The ion to be fragmented into smaller fragment ions."
>
> $cog_attrs$type
> [1] "numeric"
>
> $cog_attrs$group
> [1] "common"
>
> $cog_attrs$defLabel
> [1] FALSE
>
> $cog_attrs$defActive
> [1] TRUE
>
> $cog_attrs$filterable
> [1] TRUE
>
> $cog_attrs$log
> [1] NA
>
>
> $class
> [1] "cog" "numeric"
Create Trellis Plot
Dilution Curves
Here is a trellis plot for the dilution curves.
Code
trelliscope_name<-"Dilution Plot"# Conditional cognostics must always be displayed as a labeltrellis_labels<-c("Transition_Name")|>unique()# If we only have one label to display# Set it as NULL as it will automatically# appeared in stateif(length(trellis_labels)==1){trellis_labels<-NULL}dilution_trellis|>trelliscopejs::trelliscope( name =trelliscope_name, panel_col ="dilution_panel", path ="Dilution_Plot_Folder", state =list( labels =trellis_labels, sort =list(trelliscopejs::sort_spec("Transition_Name", dir ="asc"))), nrow =2, ncol =2, height =520, width =1200, self_contained =FALSE, thumb =FALSE, auto_cog =FALSE)
Injection Sequence Plots
Here is a trellis plot for the injection sequence plots.
Code
trelliscope_name<-"Injection Sequence Plot"# Conditional cognostics must always be displayed as a labeltrellis_labels<-c("Transition_Name")|>unique()# If we only have one label to display# Set it as NULL as it will automatically# appeared in stateif(length(trellis_labels)==1){trellis_labels<-NULL}injection_sequence_trellis|>trelliscopejs::trelliscope( name =trelliscope_name, panel_col ="panel", path ="Injection_Sequence_Plot_Folder", state =list( labels =trellis_labels, sort =list(trelliscopejs::sort_spec("Transition_Name", dir ="asc"))), nrow =1, ncol =1, height =520, width =1200, self_contained =FALSE, thumb =FALSE, auto_cog =FALSE)
Combined Plots
It is also possible to combined the two trellis plots together as they have the same conditioning cognostics variable. Simply set the path to the same folder name. In the example below, path = "docs" was used.
Unfortunately, the trellis plot does not seems to function properly in the Quarto document. As such, the code below is called interactively via the console.
Code
trelliscope_name<-"Dilution Plot"# Conditional cognostics must always be displayed as a labeltrellis_labels<-c("Transition_Name")|>unique()# If we only have one label to display# Set it as NULL as it will automatically# appeared in stateif(length(trellis_labels)==1){trellis_labels<-NULL}dilution_trellis|>trelliscopejs::trelliscope( name =trelliscope_name, panel_col ="dilution_panel", path ="docs", state =list( labels =trellis_labels, sort =list(trelliscopejs::sort_spec("Transition_Name", dir ="asc"))), nrow =2, ncol =2, height =520, width =1100, self_contained =FALSE, thumb =FALSE, auto_cog =FALSE)trelliscope_name<-"Injection Sequence Plot"injection_sequence_trellis|>trelliscopejs::trelliscope( name =trelliscope_name, panel_col ="panel", path ="docs", state =list( labels =trellis_labels, sort =list(trelliscopejs::sort_spec("Transition_Name", dir ="asc"))), nrow =1, ncol =1, height =520, width =1100, self_contained =FALSE, thumb =FALSE, auto_cog =FALSE)
A docs folder will be created. It is currently placed in this GitHub repository.
Below is just an embedded webpage of the trellis plot. It can be accessed via this GitHub Page link.
Allaire J, Dervieux C (2024). quarto: R Interface to ‘Quarto’ Markdown Publishing System. R package version 1.4.4, https://quarto-dev.github.io/quarto-r/, https://github.com/quarto-dev/quarto-r.
Cheng J, Sievert C, Schloerke B, Chang W, Xie Y, Allen J (2024). htmltools: Tools for HTML. R package version 0.5.8.1, https://rstudio.github.io/htmltools/, https://github.com/rstudio/htmltools.
Hester J, Bryan J (2024). glue: Interpreted String Literals. R package version 1.7.0, https://github.com/tidyverse/glue, https://glue.tidyverse.org/.
Iannone R (2023). fontawesome: Easily Work with ‘Font Awesome’ Icons. R package version 0.5.2, https://rstudio.github.io/fontawesome/, https://github.com/rstudio/fontawesome.
Lin G (2023). reactable: Interactive Data Tables for R. R package version 0.4.4, https://github.com/glin/reactable, https://glin.github.io/reactable/.
Makowski D, Lüdecke D, Patil I, Thériault R, Ben-Shachar M, Wiernik B (2023). “Automated Results Reporting as a Practical Tool to Improve Reproducibility and Methodological Best Practices Adoption.” CRAN. https://easystats.github.io/report/.
Müller K, Wickham H (2023). tibble: Simple Data Frames. R package version 3.2.1, https://github.com/tidyverse/tibble, https://tibble.tidyverse.org/.
Nguyen G (2021). assertable: Verbose Assertions for Tabular Data (Data.frames and Data.tables). R package version 0.2.8.
R Core Team (2024). R: A Language and Environment for Statistical Computing. R Foundation for Statistical Computing, Vienna, Austria. https://www.R-project.org/.
Robinson D, Hayes A, Couch S (2024). broom: Convert Statistical Objects into Tidy Tibbles. R package version 1.0.7, https://github.com/tidymodels/broom, https://broom.tidymodels.org/.
Sievert C (2020). Interactive Web-Based Data Visualization with R, plotly, and shiny. Chapman and Hall/CRC. ISBN 9781138331457, https://plotly-r.com.
Wickham H (2016). ggplot2: Elegant Graphics for Data Analysis. Springer-Verlag New York. ISBN 978-3-319-24277-4, https://ggplot2.tidyverse.org.
Wickham H, Chang W, Flight R, Müller K, Hester J (2021). sessioninfo: R Session Information. R package version 1.2.2, https://r-lib.github.io/sessioninfo/, https://github.com/r-lib/sessioninfo#readme.
Wickham H, François R, Henry L, Müller K, Vaughan D (2023). dplyr: A Grammar of Data Manipulation. R package version 1.1.4, https://github.com/tidyverse/dplyr, https://dplyr.tidyverse.org.
Wickham H, Henry L (2023). purrr: Functional Programming Tools. R package version 1.0.2, https://github.com/tidyverse/purrr, https://purrr.tidyverse.org/.
Wickham H, Vaughan D, Girlich M (2024). tidyr: Tidy Messy Data. R package version 1.3.1, https://github.com/tidyverse/tidyr, https://tidyr.tidyverse.org.
Xie Y (2024). knitr: A General-Purpose Package for Dynamic Report Generation in R. R package version 1.48, https://yihui.org/knitr/. Xie Y (2015). Dynamic Documents with R and knitr, 2nd edition. Chapman and Hall/CRC, Boca Raton, Florida. ISBN 978-1498716963, https://yihui.org/knitr/. Xie Y (2014). “knitr: A Comprehensive Tool for Reproducible Research in R.” In Stodden V, Leisch F, Peng RD (eds.), Implementing Reproducible Computational Research. Chapman and Hall/CRC. ISBN 978-1466561595.
References
1.
Allen M, Poggiali D, Whitaker K, Marshall T, Langen J van, Kievit R. Raincloud plots: A multi-platform tool for robust data visualization [version 2; peer review: 2 approved]. Wellcome Open Research [Internet]. 2021;4(63). Available from: https://wellcomeopenresearch.org/articles/4-63
2.
Wolrab D, Jirásko R, Cífková E, Höring M, Mei D, Chocholoušková M, et al. Lipidomic profiling of human serum enables detection of pancreatic cancer. Nature Communications [Internet]. 2022 Jan 10;13(1):124. Available from: https://doi.org/10.1038/s41467-021-27765-9
3.
Liebisch F Gerhard, Spener F. Update on LIPID MAPS classification, nomenclature, and shorthand notation for MS-derived lipid structures. Journal of Lipid Research [Internet]. 2020 Dec 1;61(12):1539–55. Available from: https://doi.org/10.1194/jlr.S120001025
4.
Kopczynski D, Hoffmann N, Peng B, Ahrends R. Goslin: A grammar of succinct lipid nomenclature. Analytical Chemistry [Internet]. 2020;92(16):10957–60. Available from: https://doi.org/10.1021/acs.analchem.0c01690
5.
Kopczynski D, Hoffmann N, Peng B, Liebisch G, Spener F, Ahrends R. Goslin 2.0 implements the recent lipid shorthand nomenclature for MS-derived lipid structures. Analytical Chemistry [Internet]. 2022;94(16):6097–101. Available from: https://doi.org/10.1021/acs.analchem.1c05430
6.
Broadhurst D, Goodacre R, Reinke SN, Kuligowski J, Wilson ID, Lewis MR, et al. Guidelines and considerations for the use of system suitability and quality control samples in mass spectrometry assays applied in untargeted clinical metabolomic studies. Metabolomics [Internet]. 2018 May 18;14(6):72. Available from: https://doi.org/10.1007/s11306-018-1367-3
7.
Simón-Manso Y, Lowenthal MS, Kilpatrick LE, Sampson ML, Telu KH, Rudnick PA, et al. Metabolite profiling of a NIST standard reference material for human plasma (SRM 1950): GC-MS, LC-MS, NMR, and clinical laboratory analyses, libraries, and web-based resources. Analytical Chemistry [Internet]. 2013 Dec 17;85(24):11725–31. Available from: https://doi.org/10.1021/ac402503m
8.
Sud M, Fahy E, Cotter D, Brown A, Dennis EA, Glass CK, et al. LMSD: LIPID MAPS structure database. Nucleic Acids Research [Internet]. 2006 Nov;35(suppl_1):D527–32. Available from: https://doi.org/10.1093/nar/gkl838
9.
Okabe M, Ito K. Color universal design (CUD)- how to make figures and presentations that are friendly to colorblind people - [Internet]. 2002 [cited 2022 Aug 1]. Available from: https://jfly.uni-koeln.de/color/index.html
Citation
BibTeX citation:
@online{selva2022,
author = {Selva, Jeremy},
title = {Quarto {Report} {Example} {With} {Plotly} and
{Trelliscopejs}},
date = {2022-08-08},
url = {https://jauntyjjs.github.io/Trelliscopejs_In_Quarto_Example/},
langid = {en},
abstract = {A report showing how to create injection sequence plot and
dilution plot using R package plotly for each Multiple Reaction
Monitoring (MRM) transition and display them as an interactive
trellis plot using R package trelliscopejs.}
}
---title: "Quarto Report Example With Plotly and Trelliscopejs"subtitle: "`r verbaliseR::prettify_date(Sys.Date(), uk_or_us = 'UK')`"bibliography: utils/bibliography.bibcsl: utils/f1000research.cslknitr: opts_chunk: dpi: 600 message: FALSE warning: FALSE echo: TRUE eval: TRUE cache: FALSE comment: ">" tidyverse-quiet: TRUEfilters: - social-shareshare: permalink: "https://jauntyjjs.github.io/Trelliscopejs_In_Quarto_Example/" description: "A report showing how to create injection sequence plot and dilution plot using R package plotly for each Multiple Reaction Monitoring (MRM) transition and display them as an interactive trellis plot using R package trelliscopejs" twitter: true facebook: true reddit: true stumble: true tumblr: true linkedin: true email: truecitation: type: webpage author: Jeremy Selva issued: 8/8/2022 keyword: plotly, trelliscopejs, Data Visualisation abstract: A report showing how to create injection sequence plot and dilution plot using R package plotly for each Multiple Reaction Monitoring (MRM) transition and display them as an interactive trellis plot using R package trelliscopejs. url: https://jauntyjjs.github.io/Trelliscopejs_In_Quarto_Example/format: html: theme: light: cerulean dark: cyborg toc: true toc-depth: 3 toc-location: left number-sections: false code-fold: show code-overflow: scroll code-line-numbers: false code-link: true code-copy: true code-tools: true self-contained: false smooth-scroll: true---Written by Jeremy Selva [`r fontawesome::fa(name = "link")`](https://jeremy-selva.netlify.app/){target="_blank"} [`r fontawesome::fa(name = "linkedin")`](https://www.linkedin.com/in/jeremy-selva-085b9112a/){target="_blank"} [`r fontawesome::fa(name = "github")`](https://github.com/JauntyJJS){target="_blank"} [`r fontawesome::fa(name = "fab fa-x-twitter")`](https://twitter.com/JauntyJJS){target="_blank"}# IntroductionHere is a report showing how to create injection sequence plot and dilution plot using [`plotly`](https://plotly.com/r/){target="_blank"} for each Multiple Reaction Monitoring (MRM) transition in Mass Spectrometry.::: panel-tabset## Injection Sequence Plot![Injection Sequence Plot](images/Injection_Sequence.JPG){fig-alt="An injection sequence plot. It consists of two figures. The figure on the left is a scatter plot with the y axis as peak area while the x axis as the injection sequence. Colours on the scatter plot are based on the quality control sample types. The figure in the right is a raincloud plot with the y axis as peak area while the x axis as quality control sample types. Colours on the raincloud plot are based on the quality control sample types."}## Dilution Plot![Dilution Plot](images/Dilution_Plot.JPG){fig-alt="A dilution plot with the y axis as the peak area and the x axis as relative sample amount in percentages. Both linear and quadratic regression are used to fit the dilution points."}:::These multiple interactive plots will be displayed as a trellis using [`trelliscopejs`](https://hafen.github.io/trelliscopejs/){target="_blank"}.The source code used to generate this report can be found in this [GitHub page](https://github.com/JauntyJJS/Trelliscopejs_In_Quarto_Example){target="_blank"}.# BackgroundHere is some background knowledge about the use of the quality control (QC) plots, for example, the injection sequence plot and the dilution plot, in targeted lipidomics analysis.### About Targeted LipidomicsThe peak area data used to generate these quality control plots comes from a targeted lipidomics experiment using Reversed-Phase Ultra High-Performance Liquid Chromatography/ Mass Spectrometry (RP-UHPLC/MS). For those new to mass spectrometry or lipidomics, the aim of targeted lipidomics is to measure and even quantify the amount of specific lipids found in a given sample. To tell the mass spectrometer what lipids to measure, a list of multiple reaction monitoring (MRM) transitions (with their corresponding precursor and product ions) is provided. More details on what multiple reaction monitoring (MRM) is can be found in this [webpage](https://www.proteomics.com.au/analytical-services/srms-mrm/){target="_blank"} by Proteomics International Laboratories LTD.### Reason for creating quality control (QC) plotsHowever, variation in a transition's peak area measured between samples can come from many unwanted sources, besides that sample itself. Such unwanted sources includes, contaminant ions or a different pipette used during lipid extraction. To check the severity of these unwanted variation, quality control (QC) samples are used. In most practices, we want to keep transitions that give peak areas with low variation in the quality control samples. While variations can be measured using summary statistics such as the coefficient of variation, creating quality control (QC) plots can help us identify the cause of the unwanted variation. In this report, two such plots are introduced.The injection sequence plot is a scatter plot with the peak area (or concentration) on the y-axis and the injection sequence order (or data acquisition time) on the x axis. This is to better understand the variation of peak area in the quality control (QC) sample types over time. A [raincloud plot](https://wellcomeopenresearch.org/articles/4-63){target="_blank"} [@Allen2021] is placed on the right of the scatter plot to show the distribution of each (QC) sample type as well as to highlight any potential outliers.The dilution plot, on the other hand, checks for possible signal enhancement or suppression as a result of [detector saturation](https://www.researchgate.net/figure/llustration-of-the-saturation-of-the-MCP-detector-caffeine-m-z-1950882-as-an_fig2_221846657){target="_blank"} or [matrix effect](https://www.slideshare.net/amitpatel745/matrix-effect-7614914){target="_blank"}.# R Packages Used```{r}#| label: load library#| code-fold: true#| output: asis# For session info and package reportinglibrary("sessioninfo") # R Session Information Posit RPSM v1.2.2library("quarto") # R Interface to 'Quarto' Markdown Publishing System Posit RPSM v1.4.4library("report") # Automated Reporting of Results and Statistical Models Posit RPSM v0.5.9library("knitr") # A General-Purpose Package for Dynamic Report Generation in R CRAN v1.48library("fontawesome") # Easily Work with 'Font Awesome' Icons Posit RPSM v0.5.2library("verbaliseR") # Make your Text Mighty Fine CRAN v0.1# For reading fileslibrary("readr") # Read Rectangular Text Data CRAN v2.1.5# For interactive tableslibrary("reactable") # Interactive Data Tables for R Posit RPSM v0.4.4library("htmltools") # Tools for HTML Posit RPSM v0.5.8.1# For data wranglinglibrary("dplyr") # A Grammar of Data Manipulation Posit RPSM v1.1.4library("tidyr") # Tidy Messy Data Posit RPSM v1.3.1library("tibble")# Simple Data Frames Posit RPSM v3.2.1library("purrr") # Functional Programming Tools Posit RPSM v1.0.2library("broom") # Convert Statistical Objects into Tidy Tibbles Posit RPSM v1.0.7library("glue") # Interpreted String Literals Posit RPSM v1.7.0# For palatte colourslibrary("scales") # Scale Functions for Visualization CRAN v1.3.0# For lipid annotation# Install from BioConductorlibrary("rgoslin") # Lipid Shorthand Name Parsing and Normalization [github::lifs-tools/rgoslin] v2.2.0# For plottinglibrary("plotly") # Create Interactive Web Graphics via 'plotly.js' Posit RPSM v4.10.4# For cognosticslibrary("assertable") # Verbose Assertions for Tabular Data (Data.frames and Data.tables) Posit RPSM v0.2.8# For combining plotly plotslibrary("manipulateWidget") # Add Even More Interactivity to Interactive Charts Posit RPSM v0.11.1# For trellis plotlibrary("trelliscopejs") # Create Interactive Trelliscope Displays Posit RPSM v0.2.6``````{r}#| label: see spackages info#| code-fold: truer_package_table <- sessioninfo::package_info()rownames(r_package_table) <-NULLr_package_table |> dplyr::mutate(version =ifelse(is.na(r_package_table$loadedversion), r_package_table$ondiskversion, r_package_table$loadedversion) ) |> dplyr::filter(.data$attached ==TRUE) |> dplyr::select( dplyr::any_of(c("package", "version", "date", "source") ) ) |> reactable::reactable(columns =list(package = reactable::colDef(# Freeze first columnsticky ="left",style =list(borderRight ="1px solid #eee"),headerStyle =list(borderRight ="1px solid #eee")) ) )```# R Platform Information```{r}#| label: see session info#| code-fold: true# Taken from https://github.com/r-lib/sessioninfo/issues/75get_quarto_version <-function() {if (isNamespaceLoaded("quarto")) { path <- quarto::quarto_path() ver <-system("quarto -V", intern =TRUE)if (is.null(path)) {"NA (via quarto)" } else {paste0(ver, " @ ", path, "/ (via quarto)") } } else { path <-Sys.which("quarto")if (path =="") {"NA" } else { ver <-system("quarto -V", intern =TRUE)paste0(ver, " @ ", path) } }}r_platform_table <- sessioninfo::platform_info()r_platform_table[["quarto"]] <-get_quarto_version()[1]r_platform_table <-data.frame(setting =names(r_platform_table),value =unlist(r_platform_table,use.names =FALSE),stringsAsFactors =FALSE )r_platform_table |> reactable::reactable(defaultPageSize =5 )```# Quarto Extenstion usedThis Quarto document uses the Quarto Extension [`schochastics/quarto-social-share`](https://github.com/schochastics/quarto-social-share){target="_blank"}. Prior to rendering this document via command `quarto render index.qmd`, remember to install the extension by running `quarto install extension schochastics/quarto-social-share` in the command line terminal. This will install the extension under the `_extensions` subdirectory. If you're using version control, you will want to check in this directory.# Input DataThe data we are using are from [Wolrab et. al 2022](https://doi.org/10.1038/s41467-021-27765-9){target="_blank"} [@Wolrab2022].Here are the links to the following csv file:* [RP-UHPLC_MS_Area.csv](https://raw.githubusercontent.com/JauntyJJS/Trelliscopejs_In_Quarto_Example/main/data/RP-UHPLC_MS_Area.csv){target="_blank"}* [RP-UHPLC_MS_Transition_Name_Annot.csv](https://raw.githubusercontent.com/JauntyJJS/Trelliscopejs_In_Quarto_Example/main/data/RP-UHPLC_MS_Transition_Name_Annot.csv){target="_blank"}* [RP-UHPLC_MS_Sample_Annot.csv](https://raw.githubusercontent.com/JauntyJJS/Trelliscopejs_In_Quarto_Example/main/data/RP-UHPLC_MS_Sample_Annot.csv){target="_blank"}* [RP-UHPLC_MS_Dilution_Annot.csv](https://raw.githubusercontent.com/JauntyJJS/Trelliscopejs_In_Quarto_Example/main/data/RP-UHPLC_MS_Dilution_Annot.csv){target="_blank"}::: panel-tabset## Peak Area DataWe read the transition's peak area measurement using Reversed-Phase Ultra High-Performance Liquid Chromatography/ Mass Spectrometry (RP-UHPLC/MS). The measurement is done by lab 3 during Phase II of the project.The first column is the name of the sample. Subsequent columns are the transition's integrated peak areas.```{r}#| label: read area dataarea_data <- readr::read_csv(file ="https://raw.github.com/JauntyJJS/Trelliscopejs_In_Quarto_Example/main/data/RP-UHPLC_MS_Area.csv",show_col_types =FALSE)```Here is how the data (first 30 rows) looks like```{r}#| label: preview area data#| code-fold: truearea_data[1:30,] |> reactable::reactable(defaultPageSize =5,defaultColDef = reactable::colDef(minWidth =350),columns =list(Sample_Name = reactable::colDef(# Freeze first columnsticky ="left",style =list(borderRight ="1px solid #eee"),headerStyle =list(borderRight ="1px solid #eee"),minWidth =150 ) ),bordered =TRUE,highlight =TRUE,paginationType ="jump" )```## Transition Name AnnotationThe transition annotation provides information about the given transitions that has been measured.```{r}#| label: read transition name annotationtransition_name_annot <- readr::read_csv(file ="https://raw.github.com/JauntyJJS/Trelliscopejs_In_Quarto_Example/main/data/RP-UHPLC_MS_Transition_Name_Annot.csv",show_col_types =FALSE)```Here are the column descriptions.| Column Name | Column Description ||-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|| Transition_Name | Name of the transition to be mesured in the mass spectrometer. || Lipid_Name | Converted transition name to suit lipid nomenclature set by [Liebisch et. al. 2020](https://doi.org/10.1021/acs.jafc.0c07175){target="_blank"} [@Liebisch2020]. || For_Rgoslin | Input transition for the R package [rgoslin](https://github.com/lifs-tools/rgoslin){target="_blank"} [@GOSLIN], [@GOSLIN2] to annotate. || Precursor_Ion | The ion to be fragmented into smaller fragment ions. || Product_Ion | Ions created from fragmentation of the precursor ion. || Retention_Time\_\[min\] | Transition name's expectred retention time. || Delta_Retention_Time\_\[min\] | Time window to acquire the transition. Each transition is acquired in the range Retention Time +/- 0.5(Delta Retention Time). || Collision_Energy | Rate of acceleration as the precursor ions enter the Q2 for fragmentation. || Cell_Accelerator_Voltage | Rate of acceleration as the product ions leave the Q2. || Polarity | Settings (Positive or Negative) for the mass spectrometry to detect (positive or negative) ions. || isISTD | Set to TRUE if the given transition name an internal standard. |Here is how the data looks like```{r}#| label: preview transition name annotation#| code-fold: truetransition_name_annot |> reactable::reactable(defaultPageSize =5,defaultColDef = reactable::colDef(minWidth =300),columns =list(Transition_Name = reactable::colDef(# Freeze first columnsticky ="left",style =list(borderRight ="1px solid #eee"),headerStyle =list(borderRight ="1px solid #eee"),minWidth =250 )),bordered =TRUE,highlight =TRUE,searchable =TRUE,paginationType ="jump" )```## Sample AnnotationThe sample annotation provides information about the given samples that has been measured.```{r}#| label: read sample annotationsample_annot <- readr::read_csv(file ="https://raw.github.com/JauntyJJS/Trelliscopejs_In_Quarto_Example/main/data/RP-UHPLC_MS_Sample_Annot.csv",show_col_types =FALSE)```Here are the column descriptions.+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| Column Name | Column Description |+=======================+==========================================================================================================================================================================================================================================================================+| Injection_Sequence | The order in which the samples are injected to the mass spectrometer for data acquisition. |+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| Acqusition_Time_Stamp | The time the sample is being measured. It is expressed yyyy-mm-dd hh:mm:ss timezone. |+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| Sample_Name | The name of the sample. |+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| QC_Sample_Type | Quality control category of the sample. More information can be found in [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || | || | - SPL stands for experimental/unknown samples. || | || | - BQC stands for batch/pooled quality control. || | || | - TQC stands for technical (or pooled lipid extracts) quality control. || | || | - RQC stands for response (or pooled QC dilution) quality control. || | || | - PBLK stands for processed blanks. || | || | - LTR stands for long term reference. || | || | - NIST stands for SRM1950 NIST plasma sample from [Simón-Manso et. al. 2013](https://doi.org/10.1021/ac402503m){target="_blank"} [@Simón-Manso2013] . It is a standard reference material provides a method to allow quality assessment across different laboratories. |+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| Vial_Position | The sample's vial position in the autosampler. |+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+Here is how it looks like```{r}#| label: preview sample annotation#| code-fold: truesample_annot |> reactable::reactable(defaultPageSize =5,defaultColDef = reactable::colDef(minWidth =250),columns =list(Injection_Sequence = reactable::colDef(# Freeze first columnsticky ="left",style =list(borderRight ="1px solid #eee"),headerStyle =list(borderRight ="1px solid #eee"),minWidth =200 )),bordered =TRUE,highlight =TRUE,searchable =TRUE,paginationType ="jump" )```## Dilution AnnotationThe dilution annotation provides information about the given RQC samples that has been measured.```{r}#| label: read dilution annotationdilution_annot <- readr::read_csv(file ="https://raw.github.com/JauntyJJS/Trelliscopejs_In_Quarto_Example/main/data/RP-UHPLC_MS_Dilution_Annot.csv",show_col_types =FALSE)```Here are the column descriptions.| Column Name | Column Description ||-------------------------------|------------------------------------------------------------------------|| Sample_Name | The name of the sample. || Dilution_Batch_Name | Name of the dilution batch. || Relative_Sample_Amount\_\[%\] | Relative sample amount in %. Used as the x-axis of the dilution curve. |Here is how it looks like```{r}#| label: preview dilution annotation#| code-fold: truedilution_annot |> reactable::reactable(defaultPageSize =5,defaultColDef = reactable::colDef(minWidth =270),columns =list(Sample_Name = reactable::colDef(# Freeze first columnsticky ="left",style =list(borderRight ="1px solid #eee"),headerStyle =list(borderRight ="1px solid #eee"),minWidth =150 )),bordered =TRUE,highlight =TRUE,searchable =TRUE,paginationType ="jump" )```:::# Nested Data## Nested Sample DataWe first merge the peak area data with the sample annotation. Next, we created a nested `sample_data` for each transition name.`sample_data` will be used later to do statistical analysis for each QC sample type.```{r}#| label: merge and nest sample annotation datasample_annot_column_names <- sample_annot |>colnames()nested_sample_data <- sample_annot |> dplyr::inner_join(y = area_data, by ="Sample_Name") |> tidyr::pivot_longer(cols =-dplyr::any_of(sample_annot_column_names),names_to ="Transition_Name", values_to ="Area") |> dplyr::group_by(.data[["Transition_Name"]]) |> tidyr::nest() |> dplyr::rename(sample_data = dplyr::all_of("data")) |> dplyr::ungroup()```Here is a preview of the first five rows of the `nested_sample_data`. For each `sample_data` of a given transition name, we display the first ten rows.```{r}#| label: preview nested sample annotation#| code-fold: truenested_sample_data[1:5,] |> reactable::reactable(defaultColDef = reactable::colDef(minWidth =250),columns =list(sample_data = reactable::colDef(details =function(index) { filtered_data <- nested_sample_data[["sample_data"]][index][[1]] htmltools::div(style ="padding: 1rem", reactable::reactable(data = filtered_data[1:10,],defaultPageSize =5,defaultColDef = reactable::colDef(minWidth =250),columns =list(Injection_Sequence = reactable::colDef(minWidth =150),Sample_Name = reactable::colDef(minWidth =150)),outlined =TRUE,highlight =TRUE) ) } ) ))```## Nested Dilution DataWe first merge the peak area data with the dilution and sample annotation. Next, we created a nested `dilution_data` for each transition name.`dilution_data` will be used later to do dilution curve related analysis for each transition.```{r}#| label: merge and nest sample dilution datasample_annot_column_names <- sample_annot |>colnames()dilution_annot_column_names <- dilution_annot |>colnames()nested_dilution_data <- dilution_annot |> dplyr::inner_join(y = sample_annot, by ="Sample_Name") |> dplyr::inner_join(y = area_data, by ="Sample_Name") |> tidyr::pivot_longer(cols =-dplyr::any_of(c(sample_annot_column_names, dilution_annot_column_names)),names_to ="Transition_Name", values_to ="Area") |> dplyr::group_by(.data[["Transition_Name"]]) |> tidyr::nest() |> dplyr::rename(dilution_data = dplyr::all_of("data")) |> dplyr::ungroup()```Here is a preview of the first five rows of the `nested_dilution_data`. For each `dilution_data` of a given transition name, we display the first five rows.```{r}#| label: preview nested dilution annotation#| code-fold: truenested_dilution_data[1:5,] |> reactable::reactable(defaultColDef = reactable::colDef(minWidth =250),columns =list(dilution_data = reactable::colDef(details =function(index) { filtered_data <- nested_dilution_data[["dilution_data"]][index][[1]] htmltools::div(style ="padding: 1rem", reactable::reactable(data = filtered_data[1:5,],defaultPageSize =5,defaultColDef = reactable::colDef(minWidth =250),columns =list(Sample_Name = reactable::colDef(minWidth =150)),outlined =TRUE,highlight =TRUE) ) } ) ))```## Join them togetherWe join the two nested data `sample_data` and `dilution_data` together```{r}#| label: merge nested data togethernested_data <- nested_sample_data |> dplyr::inner_join(y = nested_dilution_data, by ="Transition_Name")```Here is a preview of the first five rows of the `nested_data`.```{r}#| label: preview nested data#| code-fold: truenested_data[1:5,] |> reactable::reactable(defaultColDef = reactable::colDef(minWidth =250),columns =list(sample_data = reactable::colDef(details =function(index) { filtered_data <- nested_data[["sample_data"]][index][[1]] htmltools::div(style ="padding: 1rem", reactable::reactable(data = filtered_data[1:10,],defaultPageSize =5,defaultColDef = reactable::colDef(minWidth =250),columns =list(Injection_Sequence = reactable::colDef(minWidth =150)),outlined =TRUE,highlight =TRUE) )}),dilution_data = reactable::colDef(details =function(index) { filtered_data <- nested_data[["dilution_data"]][index][[1]] htmltools::div(style ="padding: 1rem", reactable::reactable(data = filtered_data[1:5,],defaultPageSize =5,defaultColDef = reactable::colDef(minWidth =250),columns =list(Sample_Name = reactable::colDef(minWidth =150)),outlined =TRUE,highlight =TRUE) )}) ) )```# Annotate Transition NamesWe now add the annotations for each transitions from `transition_name_annot` and [rgoslin](https://github.com/lifs-tools/rgoslin){target="_blank"}.## Add `transition_name_annot` annotationsWe first join the nested_data with our transition annotations.```{r}#| label: add transition_name_annot annotationannotated_data <- nested_data |> dplyr::inner_join(y = transition_name_annot, by ="Transition_Name")```Here is a preview of the first five rows of the `annotated_data`.```{r}#| label: preview annotated data 1#| code-fold: trueannotated_data[1:5,] |> reactable::reactable(defaultColDef = reactable::colDef(minWidth =250),columns =list(sample_data = reactable::colDef(details =function(index) { filtered_data <- annotated_data[["sample_data"]][index][[1]] htmltools::div(style ="padding: 1rem", reactable::reactable(data = filtered_data[1:10,],defaultPageSize =5,defaultColDef = reactable::colDef(minWidth =250),columns =list(Injection_Sequence = reactable::colDef(minWidth =150)),outlined =TRUE,highlight =TRUE) )}),dilution_data = reactable::colDef(details =function(index) { filtered_data <- annotated_data[["dilution_data"]][index][[1]] htmltools::div(style ="padding: 1rem", reactable::reactable(data = filtered_data[1:5,],defaultPageSize =5,defaultColDef = reactable::colDef(minWidth =250),columns =list(Sample_Name = reactable::colDef(minWidth =150)),outlined =TRUE,highlight =TRUE) )}) ) )```## Add `rgoslin` annotations### Small exampleThe R package [rgoslin](https://github.com/lifs-tools/rgoslin){target="_blank"} is useful in getting additional information of lipid names such as the lipid classes. A column called `For_Rgoslin` was created to convert each transition name that can be parsed by [rgoslin](https://github.com/lifs-tools/rgoslin){target="_blank"}. Unfortunately only [Coenzyme Q10](https://www.lipidmaps.org/databases/lmsd/LMPR02010001?LMID=LMPR02010001){target="_blank"} is unable to be parsed by [rgoslin](https://github.com/lifs-tools/rgoslin){target="_blank"}. We thus add the relevant information manually.Below is an code example (not part of the pipeline) on how [rgoslin](https://github.com/lifs-tools/rgoslin){target="_blank"} can be used```{r}#| label: rgoslin demorgoslin_example <- annotated_data |> dplyr::select(dplyr::all_of("For_Rgoslin")) |> dplyr::mutate(GoslinOutput = purrr::map(.data[["For_Rgoslin"]], rgoslin::parseLipidNames) ) |> tidyr::unnest(dplyr::all_of("GoslinOutput")) |> dplyr::rows_update( tibble::tibble(`For_Rgoslin`="Coenzyme Q10",Lipid.Maps.Category ="PR",Lipid.Maps.Main.Class ="Quinones and hydroquinones",Species.Name ="Coenzyme Q10",Molecular.Species.Name ="Coenzyme Q10",Functional.Class.Abbr ="[Coenzyme]",Functional.Class.Synonyms ="[Coenzyme, Ubiquinone]" ),by ="For_Rgoslin")```Here is a preview of `rgoslin_example`.```{r}#| label: preview rgoslin demo#| code-fold: truergoslin_example |> reactable::reactable(defaultPageSize =5,defaultColDef = reactable::colDef(minWidth =250),columns =list(For_Rgoslin = reactable::colDef(# Freeze first columnsticky ="left",style =list(borderRight ="1px solid #eee"),headerStyle =list(borderRight ="1px solid #eee"),minWidth =150 )),bordered =TRUE,highlight =TRUE,searchable =TRUE,paginationType ="jump" )```### Pipeline exampleFor the workflow, we do not need all the columns that [rgoslin](https://github.com/lifs-tools/rgoslin){target="_blank"} provide.The following columns are kept instead.| Column Name | Column Description ||---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|| Lipid.Maps.Category | [LIPID MAPS'](https://www.lipidmaps.org/databases/lmsd/browse){target="_blank"} Structure Database [@Subramaniam2006] Lipid Category which the transition name belongs to. || Lipid.Maps.Main.Class | [LIPID MAPS'](https://www.lipidmaps.org/databases/lmsd/browse){target="_blank"} Structure Database [@Subramaniam2006] Lipid Main Class which the transition name belongs to. || Species.Name | Represented by the sum composition, i.e., sum of carbon atoms, double bond equivalent and number of additional oxygen atoms, e.g. FA 18:1;O. || Molecular.Species.Name | Represented by lipid species with identified fatty acyl/alkyl residues, e.g. TG 16:0_18:1_18:1. || Functional.Class.Abbr | Abbreviation used to describe the lipid's functional class based on the grammar used. || Functional.Class.Synonyms | Alternative abbreviations used to describe the lipid's functional class. |```{r}#| label: add rgoslin annotation#| warning: false#| message: falseannotated_data <- nested_data |> dplyr::inner_join(y = transition_name_annot, by ="Transition_Name")annotated_data_cols <-colnames(annotated_data)annotated_data <- annotated_data |> dplyr::mutate(GoslinOutput = purrr::map(.data[["For_Rgoslin"]], rgoslin::parseLipidNames) ) |> tidyr::unnest(.data$GoslinOutput) |> dplyr::select(dplyr::any_of(c(annotated_data_cols,"Lipid.Maps.Category", "Lipid.Maps.Main.Class","Species.Name", "Molecular.Species.Name","Functional.Class.Abbr", "Functional.Class.Synonyms") ) ) |> dplyr::rows_update( tibble::tibble(`For_Rgoslin`="Coenzyme Q10",Lipid.Maps.Category ="PR",Lipid.Maps.Main.Class ="Quinones and hydroquinones",Species.Name ="Coenzyme Q10",Molecular.Species.Name ="Coenzyme Q10",Functional.Class.Abbr ="[Coenzyme]",Functional.Class.Synonyms ="[Coenzyme, Ubiquinone]" ),by ="For_Rgoslin")```Here is a preview of the first five rows of the `annotated_data`.```{r}#| label: preview annotated data 2#| code-fold: trueannotated_data[1:5,] |> reactable::reactable(defaultColDef = reactable::colDef(minWidth =250),columns =list(Transition_Name = reactable::colDef(# Freeze first columnsticky ="left",style =list(borderRight ="1px solid #eee"),headerStyle =list(borderRight ="1px solid #eee"),minWidth =170),sample_data = reactable::colDef(details =function(index) { filtered_data <- annotated_data[["sample_data"]][index][[1]] htmltools::div(style ="padding: 1rem", reactable::reactable(data = filtered_data[1:10,],defaultPageSize =5,defaultColDef = reactable::colDef(minWidth =250),columns =list(Injection_Sequence = reactable::colDef(minWidth =150)),outlined =TRUE,highlight =TRUE) )}),dilution_data = reactable::colDef(details =function(index) { filtered_data <- annotated_data[["dilution_data"]][index][[1]] htmltools::div(style ="padding: 1rem", reactable::reactable(data = filtered_data[1:5,],defaultPageSize =5,defaultColDef = reactable::colDef(minWidth =250),columns =list(Sample_Name = reactable::colDef(minWidth =150)),outlined =TRUE,highlight =TRUE) )}) ) )```# Calculate Transition StatisticsWe now proceed to calculate some statistical summary for each transition using the nested `dilution_data` and `sample_data`.## Calculate RQC/Dilution StatisticsThe following statistics are calculated from `dilution_data`.| Column Name | Column Description ||-------------------|----------------------------------------------|| r_corr | Pearson Correlation $R$ value. || r2_linear | Linear Regression $R^2$ Value. || r2_linear\_\<=0.8 | Set to TRUE if `r2_linear` is less than 0.8. |The relevant functions to do these calculations are as follows```{r}#| label: dilution summary functions#| code-fold: true#' @title Create Linear Model#' @description A wrapper to create a linear model from dilution data#' @param dilution_data A data frame or tibble containing dilution data#' @param conc_var Column name in `dilution_data` to indicate concentration#' @param signal_var Column name in `dilution_data` to indicate signal#' @return A linear model object from `stats:lm()` with formula#' `signal_var ~ conc_var` from data `diltuion_data`#' @examples#' dilution_percent <- c(10, 20, 40, 60, 80, 100)#' area <- c(22561, 31178, 39981, 48390, 52171, 53410)#' dilution_data <- data.frame(Dilution_Percent = dilution_percent, Area = area)#' linear_model <- create_linear_model(dilution_data,#' "Dilution_Percent",#' "Area")#' linear_model#' @rdname create_linear_model#' @exportcreate_linear_model <-function(dilution_data, conc_var, signal_var) { conc_var <-paste0("`",conc_var,"`") signal_var <-paste0("`",signal_var,"`")# Create the formula linear_formula <- stats::as.formula(paste(signal_var, "~",paste(conc_var, collapse =" + ") ) )# Create the linear model on dilution data linear_model <- stats::lm(linear_formula, data = dilution_data)return(linear_model)}#' @title Create Quadratic Model#' @description A wrapper to create a quadratic model for dilution data#' @param dilution_data A data frame or tibble containing dilution data#' @param conc_var Column name in `dilution_data` to indicate concentration#' @param signal_var Column name in `dilution_data` to indicate signal#' @return A linear model object from `stats:lm()` with formula#' `signal_var ~ conc_var + I(conc_var * conc_var)`#' from data `diltuion_data`#' @examples#' dilution_percent <- c(10, 20, 40, 60, 80, 100)#' area <- c(22561, 31178, 39981, 48390, 52171, 53410)#' dilution_data <- data.frame(Dilution_Percent = dilution_percent, Area = area)#' quad_model <- create_quad_model(dilution_data,#' "Dilution_Percent",#' "Area")#' quad_model#' @rdname create_quad_model#' @exportcreate_quad_model <-function(dilution_data, conc_var, signal_var) { conc_var <-paste0("`",conc_var,"`") signal_var <-paste0("`",signal_var,"`")# Create the formula quad_formula <- stats::as.formula(paste(signal_var, "~",paste(conc_var, "+",paste0("I(", conc_var, " * ", conc_var, ")") ) ) )# Create the quadratic model on dilution data quad_model <- stats::lm(quad_formula, data = dilution_data)return(quad_model)}#' @title Calculate Linear Model's Goodness Of Fit#' @description Calculate the Goodness of Fit of the Dilution Linear Model#' @param dilution_data A data frame or tibble containing dilution data#' @param conc_var Column name in `dilution_data` to indicate concentration#' @param signal_var Column name in `dilution_data` to indicate signal#' @return A tibble containing the Goodness of Fit measures of the linear model#' The Goodness of Fit measures are the Pearson correlation coefficient (R) and#' the R^2#' @details The function will return a tibble with NA values#' if the number of dilution points is less than or equal to three#' @examples#' dilution_percent <- c(10, 20, 40, 60, 80, 100)#' area <- c(22561, 31178, 39981, 48390, 52171, 53410)#' dilution_data <- data.frame(Dilution_Percent = dilution_percent, Area = area)#' dil_linear_gof <- calculate_gof_linear(dilution_data,#' "Dilution_Percent", "Area")#' dil_linear_gof#' @rdname calculate_gof_linear#' @exportcalculate_gof_linear <-function(dilution_data, conc_var, signal_var) { dil_linear_gof <- tibble::tibble(r_corr =NA,r2_linear =NA)if (is.null(nrow(dilution_data))) {return(dil_linear_gof) }# Drop rows whose value of signal_var is NA dilution_data <- dilution_data |> tidyr::drop_na(.data[[signal_var]])# Return NA for too little points# Horizontal, Vertical line or single pointif (nrow(dilution_data) <=2) {return(dil_linear_gof) }if (stats::sd(dilution_data[[conc_var]]) ==0) {return(dil_linear_gof) }if (stats::sd(dilution_data[[signal_var]]) ==0) {return(dil_linear_gof) }# Get the correlation results cor_result <- broom::tidy(stats::cor.test(dilution_data[[signal_var]], dilution_data[[conc_var]],method ="pearson")) r_corr <-round(cor_result$estimate, digits =6)# Create the linear model on dilution data linear_model <-create_linear_model(dilution_data, conc_var, signal_var)# Get GOF for each model linear_gof <- broom::glance(linear_model)# Get R2 r2_linear <-round(linear_gof$r.squared, digits =6) dil_linear_gof <- tibble::tibble(r_corr = r_corr,r2_linear = r2_linear)return(dil_linear_gof)}#' @title Summarise Dilution Curve Data#' @description Get the summary statistics of the dilution data#' for one group or batch#' @param dilution_data A data frame or tibble containing dilution data#' @param conc_var Column name in `dilution_data` to indicate concentration (x-axis)#' @param signal_var Column name in `dilution_data` to indicate signal (y-axis)#' @return A tibble containing the Goodness of Fit measures of the linear model#' The Goodness of Fit measures are the Pearson correlation coefficient (R) and R^2get_dilution_summary <-function(dilution_data, conc_var, signal_var) { dil_linear_gof <-calculate_gof_linear(dilution_data, conc_var, signal_var) dilution_summary <- dil_linear_gofreturn(dilution_summary)}```Here are the codes for our workflow.```{r}#| label: calculate dilution statisticssummary_data <- annotated_data |> dplyr::mutate(dilution_summary = purrr::map(.x = .data[["dilution_data"]], .f = get_dilution_summary,conc_var ="Relative_Sample_Amount_[%]",signal_var ="Area") ) |> tidyr::unnest(dplyr::all_of("dilution_summary")) |> dplyr::mutate(`r2_linear_<=0.8`= dplyr::case_when( .data[["r2_linear"]] <=0.8~"TRUE", .data[["r2_linear"]] >0.8~"FALSE",TRUE~"TRUE" ) ) ```Here is a preview of the first five rows of the `summary_data`.```{r}#| label: preview dilution statistics#| code-fold: truesummary_data[1:5,] |> reactable::reactable(defaultColDef = reactable::colDef(minWidth =250),columns =list(Transition_Name = reactable::colDef(# Freeze first columnsticky ="left",style =list(borderRight ="1px solid #eee"),headerStyle =list(borderRight ="1px solid #eee"),minWidth =170),sample_data = reactable::colDef(details =function(index) { filtered_data <- annotated_data[["sample_data"]][index][[1]] htmltools::div(style ="padding: 1rem", reactable::reactable(data = filtered_data[1:10,],defaultPageSize =5,defaultColDef = reactable::colDef(minWidth =250),columns =list(Sample_Name = reactable::colDef(minWidth =150)),outlined =TRUE,highlight =TRUE) )}),dilution_data = reactable::colDef(details =function(index) { filtered_data <- annotated_data[["dilution_data"]][index][[1]] htmltools::div(style ="padding: 1rem", reactable::reactable(data = filtered_data[1:5,],defaultPageSize =5,defaultColDef = reactable::colDef(minWidth =250),columns =list(Sample_Name = reactable::colDef(minWidth =150)),outlined =TRUE,highlight =TRUE) )}) ) )```## Calculate QC Samples StatisticsThe following statistics are calculated from `sample_data`.| Column Name | Column Description ||--------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|| bqc_mean | Mean signal from batch/pooled QC samples defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || bqc_median | Median signal from batch/pooled QC samples defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || bqc_sd | Standard deviation of signal from batch/pooled QC samples defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || bqc_mad | Median absolute deviation of signal from batch/pooled QC samples defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || bqc_cv\_% | Coefficient of variation of signal in % from batch/pooled QC samples defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || bqc_cv_robust\_% | Robust coefficient of variation of signal in % from batch/pooled QC samples defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || tqc_mean | Mean signal from techincal QC (or pooled lipid extracts) samples defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || tqc_median | Median signal from techincal QC (or pooled lipid extracts) samples defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || tqc_sd | Standard deviation of signal from techincal QC (or pooled lipid extracts) samples defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || tqc_mad | Median absolute deviation of signal techincal QC (or pooled lipid extracts) samples defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || tqc_cv\_% | Coefficient of variation of signal in % from techincal QC (or pooled lipid extracts) samples defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || tqc_cv_robust\_% | Robust coefficient of variation in % of signal from techincal QC (or pooled lipid extracts) samples defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || spl_mean | Mean signal from experimental (unknown) samples defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || spl_median | Median signal from experimental (unknown) defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || spl_sd | Standard deviation of signal from experimental (unknown) defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || spl_mad | Median absolute deviation of signal from experimental (unknown) defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || spl_cv\_% | Coefficient of variation in % of signal from experimental (unknown) defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || spl_cv_robust\_% | Robust coefficient of variation in % of signal from experimental (unknown) defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || pblk_median | Median signal from processed blank samples defined by [Broadhurst et. al. 2018](https://doi.org/10.1007/s11306-018-1367-3){target="_blank"} [@Broadhurst2018]. || pblk_to_tqc_ratio\_% | Ratio of median signal from processed blank sample to median signal from techincal QC (or pooled lipid extracts) samples. || bqc_cv\_\>20% | Set to TRUE if `bqc_cv` is greater than 20%. || tqc_cv\_\>20% | Set to TRUE if `tqc_cv` is greater than 20%. || pblk_to_tqc_ratio\_\>10% | Set to TRUE if `pblk_to_tqc_ratio_%` is greater than 10%. |The relevant functions to do these calculations are as follows.```{r}#| label: sample summary functions#| code-fold: true#' Function used to calculate statistics summary (RSD,Signal to Blank Ratio) of a given input numeric vector#'#' @param lipid_data_vector Input lipid data numeric vector#' @param prepend_string Input characters to prepend to the result table column name#'#' @return Output statistical summary table (RSD,Signal to Blank Ratio)#' @exportget_sample_description <-function(lipid_data_vector, prepend_string) { description_table <- tibble::tibble(mean = lipid_data_vector |>mean(na.rm =TRUE),median = lipid_data_vector |> stats::median(na.rm =TRUE),sd = lipid_data_vector |> stats::sd(na.rm =TRUE),mad = lipid_data_vector |> stats::mad(na.rm =TRUE,constant =1), ) |> dplyr::mutate(`cv_%`= .data$sd/.data$mean *100,`cv_robust_%`= (1.4826* .data$mad)/.data$median *100 ) description_table <- description_table |> dplyr::rename_all(~paste0(prepend_string,"_",.))return(description_table)}#' Function used to calculate QC Sample Types statistics summary (RSD,Signal to Blank Ratio) of a given column name from the input data set#'#' @param lipid_Data Input lipid data that must contain the columns from `qc_sample_type_var` and `signal_var`#' @param qc_sample_type_var Input column name indicating QC sample_type#' @param signal_var Input column name to extract from the input lipid data and calculate qc sample type statistics#' #' @return Output QC Sample Types statistical summary table (RSD,Signal to Blank Ratio)#' @exportget_qc_sample_summary <-function(lipid_data, qc_sample_type_var, signal_var) { spl_summary <- lipid_data |> dplyr::filter(.data[[qc_sample_type_var]] =="SPL") |> dplyr::pull(.data[[signal_var]]) |>get_sample_description(prepend_string ="spl") bqc_summary <- lipid_data |> dplyr::filter(.data[[qc_sample_type_var]] =="BQC") |> dplyr::pull(.data[[signal_var]]) |>get_sample_description(prepend_string ="bqc") tqc_summary <- lipid_data |> dplyr::filter(.data[[qc_sample_type_var]] =="TQC") |> dplyr::pull(.data[[signal_var]]) |>get_sample_description(prepend_string ="tqc") tqc_median <- lipid_data |> dplyr::filter(.data[[qc_sample_type_var]] %in%c("TQC")) |> dplyr::pull(!!signal_var) |> stats::median(na.rm =TRUE) pblk_median <- lipid_data |> dplyr::filter(.data[[qc_sample_type_var]] =="PBLK") |> dplyr::pull(!!signal_var) |> stats::median(na.rm =TRUE)`pblk_to_tqc_ratio_%`<- pblk_median/tqc_median *100 pblk_summary <- tibble::tibble(pblk_median = pblk_median,`pblk_to_tqc_ratio_%`=`pblk_to_tqc_ratio_%` ) qc_stat_table <- dplyr::bind_cols(bqc_summary, tqc_summary, spl_summary, pblk_summary)return(qc_stat_table)}```Here are the codes for our workflow.```{r}#| label: calculate QC sample statisticssummary_data <- summary_data |> dplyr::mutate(sample_summary = purrr::map(.x = .data[["sample_data"]], .f = get_qc_sample_summary,qc_sample_type_var ="QC_Sample_Type",signal_var ="Area") ) |> tidyr::unnest(dplyr::all_of("sample_summary")) |> dplyr::mutate(`bqc_cv_>20%`= dplyr::case_when( .data[["bqc_cv_%"]] >20~"TRUE", .data[["bqc_cv_%"]] <=20~"FALSE",TRUE~"TRUE" ),`tqc_cv_>20%`= dplyr::case_when( .data[["tqc_cv_%"]] >20~"TRUE", .data[["tqc_cv_%"]] <=20~"FALSE",TRUE~"TRUE" ),`pblk_to_tqc_ratio_>10%`= dplyr::case_when( .data[["pblk_to_tqc_ratio_%"]] >10~"TRUE", .data[["pblk_to_tqc_ratio_%"]] <=20~"FALSE",TRUE~"TRUE" ) )```Here is a preview of the first five rows of the `summary_data`.```{r}#| label: preview QC sample statistics#| code-fold: truesummary_data[1:5,] |> reactable::reactable(defaultColDef = reactable::colDef(minWidth =250),columns =list(Transition_Name = reactable::colDef(# Freeze first columnsticky ="left",style =list(borderRight ="1px solid #eee"),headerStyle =list(borderRight ="1px solid #eee"),minWidth =170),sample_data = reactable::colDef(details =function(index) { filtered_data <- annotated_data[["sample_data"]][index][[1]] htmltools::div(style ="padding: 1rem", reactable::reactable(data = filtered_data[1:10,],defaultPageSize =5,defaultColDef = reactable::colDef(minWidth =250),columns =list(Sample_Name = reactable::colDef(minWidth =150)),outlined =TRUE,highlight =TRUE) )}),dilution_data = reactable::colDef(details =function(index) { filtered_data <- annotated_data[["dilution_data"]][index][[1]] htmltools::div(style ="padding: 1rem", reactable::reactable(data = filtered_data[1:5,],defaultPageSize =5,defaultColDef = reactable::colDef(minWidth =250),columns =list(Sample_Name = reactable::colDef(minWidth =150)),outlined =TRUE,highlight =TRUE) )}) ) )```# Plotting PaletteIn this section, we create the palette for our dilution plots as well as our injection sequence plots. The [Okato-Ito palette](https://jfly.uni-koeln.de/color/index.html){target="_blank"} [@OkabeIto2008] is used.The relevant utility functions to do this are as follows.```{r}#| label: palatte utility functions#| warning: false#| message: false#| code-fold: true#' @title Create Cyclic Character Sequence#' @description Create cyclic character sequence#' @param group_name A character vector as input#' @param output_length The length of the output sequence#' @return A cyclic character sequence with length `output_length`#' @details Taken from#' https://community.rstudio.com/t/fill-in-a-sequence-of-letters-based-on-a-given-order/88823/3#' @examples#' group_name <- c("red", "green", "blue")#' create_char_seq(group_name, output_length = 2)#' create_char_seq(group_name, output_length = 5)#'#' @rdname create_char_seq#' @exportcreate_char_seq <-function(group_name, output_length) {# whole integer division i <- output_length %/%length(group_name)# remainder r <- output_length %%length(group_name)# Set the number of cycles neededif (r >0) { t <- i +1 } else { t <- i }# Create the set and cut by the output_length set <-rep(group_name, t) output <- set[1:output_length]return(output)}```::: panel-tabset## Dilution Plot PaletteWe create a colour for each dilution batch name. The palette for the dilution plot are created as such.```{r}#| label: create dilution pal# Get the dilution batch name from dilution_annotdilution_batch_name <- dilution_annot |> dplyr::pull(.data[["Dilution_Batch_Name"]]) |>unique() |>as.character()# Create palette for each dilution batch for plottingdilution_pal <-c("#377eb8") |>create_char_seq(output_length =length(dilution_batch_name)) |> stats::setNames(dilution_batch_name)scales::show_col(dilution_pal)```## Injection Sequence Plot PaletteWe create a colour for each QC sample type. The palette for the injection sequence plot are created as such.```{r}#| label: create qc sample type pal#| warning: false#| message: falseqc_sample_type <-c("SPL","TQC", "BQC","NIST", "LTR", "PBLK","RQC")# Create palette for each qc sample type for plottingqc_sample_type_pal <-c("#F0E442", "#0072B2", "#D55E00","#009E73","#56B4E9", "#CC79A7", "#E69F00") |>create_char_seq(output_length =length(qc_sample_type)) |> stats::setNames(qc_sample_type)scales::show_col(qc_sample_type_pal)```:::# Plot Dilution CurvesWith the palette created, we will create a dilution plot for each transition.The relevant functions to do this plot are as follows.```{r}#| label: dilution plot functions#| code-fold: true#' @title Plot Dilution Curve Using `plotly`#' @description Plot Dilution Data using `plotly`#' @param dilution_data A data frame or tibble containing dilution data#' @param dilution_title Title to use for each dilution plot#' @param dilution_pal Input palette for each dilution batch group in `dil_batch_var`.#' It is a named char vector where each value is a colour and#' name is a dilution batch group given in `dil_batch_var`#' @param sample_name_var Column name in `dilution_data`#' to indicate the sample name#' @param dil_batch_var Column name in `dilution_data`#' to indicate the group name of each dilution batch,#' used to colour the points in the dilution plot#' @param conc_var Column name in `dilution_data` to indicate concentration#' @param conc_var_units Unit of measure for `conc_var` in the dilution plot#' @param conc_var_interval Distance between two tick labels#' @param signal_var Column name in `dilution_data` to indicate signal#' @param x_axis_title The x axis title for the graph. If there is no input,#' column name corresponding to `conc_var` will be used instead.#' @param y_axis_title The y axis title for the graph. If there is no input,#' column name corresponding to `signal_var` will be used instead.#' @param y_axis_rangemode Wrapper of `plotly` `rangemode`. If "normal" the#' y range should be computed in relation to the extrema of the input data.#' If "tozero", the range extends to 0. If "nonnegative",#' the range is non-negative, regardless of the input data.#' Default: "normal"#' @param lin_reg_name The name of the linear regression line used to fit#' the dilution data. The name will be displayed in the legend.#' Default: "lin reg"#' @param quad_reg_name The name of the quadratic regression line used to#' fit the dilution data. The name will be displayed in the legend.#' Default: "quad reg"#' @param show_legend Decide if we want to create legends for the plot.#' If we are creating a sub plot with shared legends, ensure that only#' the first plot has show_legend set to TRUE, the rest must be set to FALSE.#' Default: TRUE#' @param show_y_axis_title Decide if we want to show the y-axis title.#' Default: TRUE#' @param annotation_var Column name in `dilution_data` to show in hover point#' @return Output `plotly` dilution plot data of one dilution batch per transition#' @rdname plot_dil_curve_plotly#' @exportplot_dil_curve_plotly <-function(dilution_data, dilution_title, dilution_pal,sample_name_var ="Sample_Name",dil_batch_var ="Dilution_Batch_Name",conc_var ="Dilution_Percent",conc_var_units ="%",conc_var_interval =50,signal_var ="Area",x_axis_title ="",y_axis_title ="",y_axis_rangemode ="normal",lin_reg_name ="lin reg",quad_reg_name ="quad reg",show_legend =TRUE,show_y_axis_title =TRUE,annotation_var =c()) {# Drop rows whose value of signal_var is NA dilution_data <- dilution_data |> tidyr::drop_na(.data[[signal_var]])# For the hover text text_input <- glue::glue("<b>{dilution_data[[sample_name_var]]}</b>\\ <br>{conc_var}: {dilution_data[[conc_var]]}\\ <br>{signal_var}: {format(dilution_data[[signal_var]], big.mark = ",", nsmall = 1)}" )for(things in annotation_var) { text_input <- glue::glue("{text_input}\\ <br>{things}: {dilution_data[[things]]}") }# Convert the column that holds the dilution_batch_var# to factors dilution_data[[dil_batch_var]] <- dilution_data[[dil_batch_var]] |># To handle the case that batches are named "1", "2", "3"as.character() |># Factor batch based on the order of the palette colours# So that the order will matchfactor(levels =names(dilution_pal))# Create the dots in the dilution plot p <- plotly::plot_ly() |> plotly::add_trace(data = dilution_data,x = dilution_data[[conc_var]],y = dilution_data[[signal_var]],type ="scatter", mode ="markers",marker =list(size =10, opacity =1,line =list(color ="black", width =1.5)),name = dilution_data[[dil_batch_var]],color = dilution_data[[dil_batch_var]],# See https://github.com/plotly/plotly.R/issues/1985colors =unname(dilution_pal),hoverinfo ="text",text = dilution_data[[sample_name_var]],hovertemplate = text_input,legendgroup ="group1",showlegend = show_legend,inherit =FALSE)if (nrow(dilution_data) >3) {# When we need to plot a horizontal lineif (stats::sd(dilution_data[[signal_var]]) ==0 ) { min_x <-min(dilution_data[[conc_var]], na.rm =TRUE) max_x <-max(dilution_data[[conc_var]], na.rm =TRUE) cont_y <-unique(dilution_data[[signal_var]]) p <- p |> plotly::add_segments(x = min_x, xend = max_x,y = cont_y, yend = cont_y,name = lin_reg_name,line =list(color ="black", width =1),legendgroup ="group2",showlegend = show_legend,inherit =FALSE) } elseif (stats::sd(dilution_data[[conc_var]]) ==0) {# When we need to plot a vertical line min_y <-min(dilution_data[[signal_var]], na.rm =TRUE) max_y <-max(dilution_data[[signal_var]], na.rm =TRUE) cont_x <-unique(dilution_data[[conc_var]]) p <- p |> plotly::add_segments(x = cont_x, xend = cont_x,y = min_y, yend = max_y,name = lin_reg_name,line =list(color ="black", width =1),legendgroup ="group2",showlegend = show_legend,inherit =FALSE) } else {# Plot the curves# Model the data linear_model <-create_linear_model(dilution_data, conc_var, signal_var) quad_model <-create_quad_model(dilution_data, conc_var, signal_var) dilution <-seq(min(dilution_data[[conc_var]]),max(dilution_data[[conc_var]]),length.out =15)# See https://github.com/plotly/plotly.R/issues/1985 linear_prediction <- linear_model |> stats::predict(tibble::tibble(!!conc_var := dilution)) |>unname() quad_prediction <- quad_model |> stats::predict(tibble::tibble(!!conc_var := dilution)) |>unname()# Create the linear and quadratic curve in the dilution plot p <- p |> plotly::add_trace(data = dilution_data, x = dilution,y = linear_prediction,type ="scatter", mode ="lines", name = lin_reg_name,line =list(color ="black", width =1),legendgroup ="group2",showlegend = show_legend,inherit =FALSE) |> plotly::add_trace(data = dilution_data, x = dilution,y = quad_prediction,type ="scatter", mode ="lines", name = quad_reg_name,line =list(color ="red", width =1, opacity =0.25),legendgroup ="group3",showlegend = show_legend,inherit =FALSE) } }# Create x axis title# If conc_var_units is empty, do not add bracketsif (x_axis_title !="") { x_title <- x_axis_title } else { x_title <- conc_var }if (conc_var_units !="") { x_title <-paste0(x_title, " (", conc_var_units, ")") }# Create y axis titleif (y_axis_title !="") { y_title <- y_axis_title } else { y_title <- signal_var }# Create the layout to be the same as ggplot2 p <- p |> plotly::layout(title =list(text = dilution_title,x =0.1) ,xaxis =list(title = x_title,titlefont =list(size =10),gridcolor ="rgb(255,255,255)",showgrid =TRUE,showline =FALSE,showticklabels =TRUE,tickcolor ="rgb(127,127,127)",ticks ="outside",zeroline =FALSE,tickfont =list(size =10),tick0 =0,dtick = conc_var_interval,showspikes =TRUE,spikemode ="toaxis+marker",spikesnap ="data"),yaxis =list(title =NA,autorange =TRUE,fixedrange =FALSE,rangemode = y_axis_rangemode,titlefont =list(size =10),gridcolor ="rgb(255,255,255)",showgrid =TRUE,showline =FALSE,showticklabels =TRUE,tickcolor ="rgb(127,127,127)",ticks ="outside",zeroline =FALSE,tickfont =list(size =10),exponentformat ="e",showspikes =TRUE,spikemode ="toaxis+marker",spikesnap ="data"),hovermode ="x",legend =list(orientation ="v",font =list(size =10)),paper_bgcolor ="rgb(255,255,255)",plot_bgcolor ="rgb(229,229,229)"#showlegend = show_legend )# Add the y axis titleif (isTRUE(show_y_axis_title)) { p <- p |> plotly::add_annotations(x =0,y =1,xref ="paper",yref ="paper",xanchor ="left",yanchor ="bottom",text = y_title,showarrow =FALSE ) }return(p)}```Here is the code that plots the dilution curve for each transition. A new column `dilution_panel` is created to contain such a plot. Both nested data `dilution_data` and `sample_data` are removed in the process.```{r}#| label: plotting dilutiondilution_plot_table <- summary_data |> dplyr::mutate(dilution_panel = trelliscopejs::pmap_plot(list(dilution_data = .data[["dilution_data"]]), plot_dil_curve_plotly,dilution_title ="",dilution_pal = dilution_pal,sample_name_var ="Sample_Name",dil_batch_var ="Dilution_Batch_Name",conc_var ="Relative_Sample_Amount_[%]",conc_var_units ="%",conc_var_interval =25,signal_var ="Area",x_axis_title ="Relative Sample Amount",y_axis_title ="Area",y_axis_rangemode ="normal",lin_reg_name ="lin reg",quad_reg_name ="quad reg",show_legend =TRUE,show_y_axis_title =TRUE,annotation_var =c("Dilution_Batch_Name","Injection_Sequence","Acqusition_Time_Stamp","Vial_Position") ) ) |> dplyr::select(-dplyr::any_of(c("dilution_data", "sample_data")))```Here is a preview of the dilution plot of first row of the `dilution_plot_table`.```{r}#| label: preview dilution plot#| code-fold: truemanipulateWidget::combineWidgets(ncol =2,nrow =2, dilution_plot_table$dilution_panel[[1]], dilution_plot_table$dilution_panel[[2]], dilution_plot_table$dilution_panel[[3]], dilution_plot_table$dilution_panel[[4]])```# Plot Injection SequenceThe injection sequence plot consist of a scatter plot and a raincloud plot.## Scatter PlotThe relevant function to do the scatter plot is as follows.```{r}#| label: plot scatter plot functions#| code-fold: true#' @title Plot Scatter Plot Using `plotly`#' @description Plot Scatter Plot using `plotly`#' @param scatterplot_data A data frame or tibble containing data#' to be plotted in the scatter plot#' @param scatterplot_pal Input palette for category group#' It is a named char vector where each value is a colour and#' name is a category group.#' @param x_axis_var Column name in `scatterplot_data` to use#' as x axis of scatter plot. Default: 'Injection_Sequence'#' @param y_axis_var Column name in `scatterplot_data` to use#' as y axis of scatter plot. Default: 'Area'#' @param sample_name_var Column name in `scatterplot_data` to use#' in the hover text for each point in the scatter plot.#' Default: 'Sample_Name'#' @param legend_category_var Column name in `scatterplot_data` to use#' for legend and colour, Default: 'QC_Sample_Type'#' @param legend_title Input character to use as legend title. #' If there is no input, input character from `legend_category_var` #' will be used instead. Default: ''#' @param annotation_var Column name in `scatterplot_data` to show in hover point#' Default: c()#' @param lod_val Input numeric to indicate the limit of detection value in #' the scatter plot as a red horizontal line. It is not plotted if no input#' is provided. Default: NA#' @param show_y_axis_title Decide if we want to show the y-axis title.#' Default: FALSE#' @param show_legend Decide if we want to create legends for the plot.#' If we are creating a sub plot with shared legends, ensure that only#' the first plot has show_legend set to TRUE, the rest must be set to FALSE.#' Default: TRUE#' @return Output `plotly` scatter plot#' @rdname plot_scatterplot_plotly#' @exportplot_scatterplot_plotly <-function(scatterplot_data, scatterplot_pal,x_axis_var ="Injection_Sequence",y_axis_var ="Area",sample_name_var ="Sample_Name",legend_category_var ="QC_Sample_Type",legend_title ="",annotation_var =c(),lod_val =NA,show_y_axis_title =FALSE,show_legend =TRUE) {# Drop rows whose value of signal_var is NA scatterplot_data <- scatterplot_data |> tidyr::drop_na(.data[[y_axis_var]])# For the hover text text_input <- glue::glue("<b>{scatterplot_data[[sample_name_var]]}</b>\\ <br>{x_axis_var}: {scatterplot_data[[x_axis_var]]}\\ <br>{y_axis_var}: {format(scatterplot_data[[y_axis_var]], big.mark = ",", nsmall = 1)}" )for (things in annotation_var) { text_input <- glue::glue("{text_input}\\ <br>{things}: {scatterplot_data[[things]]}") }# Convert the column that holds the legend_category_var# to factors scatterplot_data[[legend_category_var]] <- scatterplot_data[[legend_category_var]] |># To handle the case that batches are named "1", "2", "3"as.character() |># Factor batch based on the order of the palette colours# So that the order will matchfactor(levels =names(scatterplot_pal)) scatter <- plotly::plot_ly() |> plotly::add_trace(data = scatterplot_data,x = scatterplot_data[[x_axis_var]], y = scatterplot_data[[y_axis_var]],type ="scattergl", mode ="markers",visible =TRUE,marker =list(size =8 ,opacity =1,line =list(color ="black", width =1.5) ),name = scatterplot_data[[legend_category_var]],color = scatterplot_data[[legend_category_var]],colors =unname(scatterplot_pal),legendgroup = scatterplot_data[[legend_category_var]],showlegend = show_legend,hoverinfo ="text",text = text_input,inherit =FALSE) |># Create the layout to be the same as ggplot2 plotly::layout(xaxis =list(title = x_axis_var, titlefont =list(size =10),tickfont =list(size =10), tickangle =0,gridcolor ="rgb(255,255,255)",showgrid =TRUE,showticklabels =TRUE,tickcolor ="rgb(127,127,127)",ticks ='outside',zeroline =FALSE,showspikes =TRUE, spikemode ="toaxis+marker",spikesnap ="data",rangeslider =list(thickness =0.05),autorange =TRUE, fixedrange =FALSE),yaxis =list(title ="",titlefont =list(size =10),tickfont =list(size =10),gridcolor ="rgb(255,255,255)",showgrid =TRUE,showline =FALSE,showticklabels =TRUE,tickcolor ="rgb(127,127,127)",ticks ="outside",zeroline =FALSE,showspikes =TRUE,spikemode ="toaxis+marker",spikesnap ="data",autorange =TRUE,fixedrange =FALSE),hovermode ="closest",legend =list(title =list(text = legend_title),orientation ='v', font =list(size =10)),paper_bgcolor ="rgb(255,255,255)",plot_bgcolor ="rgb(229,229,229)" )# Add the LOD lineif (!isTRUE(is.na(lod_val))) { scatter <- scatter |> plotly::add_trace(data = scatterplot_data,x = scatterplot_data[[x_axis_var]],y = lod_val, yend = lod_val,type ="scattergl" ,mode ="lines",name ="LOD Line",line =list(color ="red",width =2,dash ="solid"),inherit =FALSE) }# Add the y axis titleif (isTRUE(show_y_axis_title)) { scatter <- scatter |> plotly::add_annotations(x =0,y =1,xref ="paper",yref ="paper",yanchor ="bottom",text = y_axis_var,showarrow =FALSE ) }return(scatter)}```The first step is to create a scatter plot (Area vs Injection Sequence) for each transition. A new column `scatterplot_panel` is created to contain such a plot.```{r}#| label: plotting scatter plotscatterplot_table <- summary_data |> dplyr::mutate(scatterplot_panel = trelliscopejs::pmap_plot(list(scatterplot_data = .data[["sample_data"]]), plot_scatterplot_plotly,scatterplot_pal = qc_sample_type_pal,x_axis_var ="Injection_Sequence",y_axis_var ="Area",sample_name_var ="Sample_Name",legend_category_var ="QC_Sample_Type",legend_title ="<b>QC Sample Type</b>",annotation_var =c("Sample_Name","Injection_Sequence","Acqusition_Time_Stamp","Vial_Position","QC_Sample_Type"),lod_val =1500,show_y_axis_title =TRUE,show_legend =TRUE ) ) ```Here is a preview of the scatter plot of first row of the `scatterplot_table`.```{r}#| label: preview scatter plot#| code-fold: truescatterplot_table$scatterplot_panel[[1]]```## Raincloud PlotThe relevant utility function is as follows.```{r}#| label: raincloud plot utility functions#| code-fold: trueremove_rqc <-function(input_data) { input_data <- input_data |> dplyr::filter(.data[["QC_Sample_Type"]] !="RQC")return(input_data)}```The relevant function to do the raincloud plot is as follows.```{r}#| label: plot raincloud plot functions#| code-fold: true#' @title Plot Raincloud Plot Using `plotly`#' @description Plot Raincloud Plot using `plotly`#' @param raincloud_data A data frame or tibble containing data to be plotted#' into a raincloud plot#' @param raincloud_pal Input palette for category group#' It is a named char vector where each value is a colour and#' name is a category group#' @param x_axis_var Column name in `raincloud_data` to use#' as x axis of raincloud plot. Default: 'QC_Sample_Type'#' @param y_axis_var Column name in `raincloud_data` to use#' as y axis of raincloud plot. Default: 'Area'#' @param sample_name_var Column name in `raincloud _data` to use#' in the hover text for each point in the raincloud plot.#' Default: 'Sample_Name'#' @param legend_category_var Column name in `raincloud _data` to use#' for legend and colour, Default: 'QC_Sample_Type'#' @param legend_title Input character to use as legend title. #' If there is no input, input character from `legend_category_var` #' will be used instead. Default: ''#' @param annotation_var Column name in `raincloud_data` to show in hover point#' Default: c()#' @param show_y_axis_title Decide if we want to show the y-axis title.#' Default: FALSE#' @param show_legend Decide if we want to create legends for the plot.#' If we are creating a sub plot with shared legends, ensure that only#' the first plot has show_legend set to TRUE, the rest must be set to FALSE.#' Default: TRUE#' @return Output `plotly` raincloud plot#' @rdname plot_raincloud_plotly#' @exportplot_raincloud_plotly <-function(raincloud_data, raincloud_pal,x_axis_var ="QC_Sample_Type",y_axis_var ="Area",sample_name_var ="Sample_Name",legend_category_var ="QC_Sample_Type",legend_title ="",annotation_var =c(),show_y_axis_title =FALSE,show_legend =TRUE) {# Drop rows whose value of signal_var is NA raincloud_data <- raincloud_data |> tidyr::drop_na(.data[[y_axis_var]])# For the hover text text_input <- glue::glue("<b>{raincloud_data[[sample_name_var]]}</b>\\ <br>{x_axis_var}: {raincloud_data[[x_axis_var]]}\\ <br>{y_axis_var}: {format(raincloud_data[[y_axis_var]], big.mark = ",", nsmall = 1)}" )for (things in annotation_var) { text_input <- glue::glue("{text_input}\\ <br>{things}: {raincloud_data[[things]]}") }# Convert the column that holds the legend_category_var# to factors raincloud_data[[legend_category_var]] <- raincloud_data[[legend_category_var]] |># To handle the case that batches are named "1", "2", "3"as.character() |># Factor batch based on the order of the palette colours# So that the order will matchfactor(levels =names(raincloud_pal)) violin <- plotly::plot_ly() |> plotly::add_trace(data = raincloud_data,x = raincloud_data[[x_axis_var]],y = raincloud_data[[y_axis_var]],type ="violin",opacity =1,#type = 'box',color = raincloud_data[[legend_category_var]],colors =unname(raincloud_pal),legendgroup = raincloud_data[[legend_category_var]],showlegend = show_legend,text = text_input,orientation ="v",box =list(visible =TRUE,width =1,line =list(color ="black"),fillcolor ='white' ),side ="positive",#boxpoints = "all",points ="suspectedoutliers",jitter =0,#boxmean = "sd",meanline =list(visible =TRUE,color ="red"),marker =list(size =7 ,opacity =1,#outliercolor = 'rgba(133,231,1,1)',line =list(color ="black", width =1.5) ),pointpos =-1,inherit =FALSE) |> plotly::layout(xaxis =list(title = x_axis_var,titlefont =list(size =10),tickfont =list(size =10), tickangle =0,gridcolor ="rgb(255,255,255)",type ='category',showgrid =TRUE,showline =FALSE,showticklabels =TRUE,tickcolor ="rgb(127,127,127)",ticks ='outside',zeroline =FALSE,tickfont =list(size =10),autorange =TRUE, fixedrange =FALSE),yaxis =list(title ="",titlefont =list(size =10),tickfont =list(size =10),gridcolor ="rgb(255,255,255)",showgrid =TRUE,showline =FALSE,showticklabels =TRUE,tickcolor ="rgb(127,127,127)",ticks ="outside",zeroline =FALSE,tickfont =list(size =10),autorange =TRUE, fixedrange =FALSE),hovermode ="closest",legend =list(title =list(text = legend_title),orientation ="v", font =list(size =10)),paper_bgcolor ="rgb(255,255,255)",plot_bgcolor ="rgb(229,229,229)" )if (isTRUE(show_y_axis_title)) { violin <- violin |> plotly::add_annotations(x =0,y =1,xref ="paper",yref ="paper",yanchor ="bottom",text = y_axis_var,showarrow =FALSE ) }return(violin)}```The second step is to create a raincloud plot (QC Sample Type (without RQC) vs Injection Sequence) for each transition. A new column called `no_rqc_data` is created from `sample_data` to filter out the RQC samples. The filtered samples are then used to create the plot. A new column `raincloud_panel` is created to contain such a plot.```{r}#| label: plotting raincloud plot#| warning: falseraincloud_table <- scatterplot_table |> dplyr::mutate(no_rqc_data = purrr::map(.x = .data[["sample_data"]],.f = remove_rqc) ) |> dplyr::mutate(raincloud_panel = trelliscopejs::pmap_plot(list(raincloud_data = .data$no_rqc_data), plot_raincloud_plotly,raincloud_pal = qc_sample_type_pal,x_axis_var ="QC_Sample_Type",y_axis_var ="Area",sample_name_var ="Sample_Name",legend_category_var ="QC_Sample_Type",legend_title ="<b>QC Sample Type</b>",annotation_var =c("Sample_Name","Injection_Sequence","Acqusition_Time_Stamp","Vial_Position"),show_y_axis_title =FALSE,show_legend =FALSE ) )```Here is a preview of the raincloud plot of first row of the `raincloud_table`.```{r}#| label: preview raincloud plot#| code-fold: trueraincloud_table$raincloud_panel[[1]]```## Injection Sequence PlotThe relevant utility functions are as follows.```{r}#| label: injection sequence plot utility functions#| code-fold: truearrange_plots <-function(scatterplot_panel, raincloud_panel) { combined_plots <- plotly::subplot(scatterplot_panel, raincloud_panel,widths =c(0.6, 0.4),margin =0.01,shareX =TRUE,shareY =TRUE )return(combined_plots)}```The last step is to combine the two plots together. `dilution_data`, `sample_data`, `no_rqc_data`, `scatterplot_panel` and `raincloud_panel` are removed in the process.```{r}#| label: plotting injection sequence plot#| warning: falseinjection_sequence_table <- raincloud_table |> dplyr::mutate(panel = trelliscopejs::pmap_plot(.l =list(scatterplot_panel = .data$scatterplot_panel,raincloud_panel = .data$raincloud_panel ),.f = arrange_plots )) |> dplyr::select(-dplyr::any_of(c("dilution_data","sample_data","no_rqc_data","scatterplot_panel", "raincloud_panel") ) )```Here is a preview of the injection sequence plot of first row of the `injection_sequence_table`.```{r}#| label: preview injection sequence plot#| code-fold: trueinjection_sequence_table$panel[[1]]```# CognosticsTo output these interactive plots as a trellis plot in html, we need to convert our current tibble and `dilution_plot_table` and `injection_sequence_table` into a cognostics dataframe.The cognostics dataframe consist of three components1. A group of columns known as conditioning variables. They will form the unique id of the trellis plot. As such, each row of these columns must be unique. In our example, the column used is `Transition_Name`2. One column that holds the images to display each plot in the trellis. This column is known as the panel variable. We just created this column using `trelliscopejs::pmap_plot` earlier.3. The other columns will be grouped as general cognostics columns.## Cognostics Conversion FunctionsThe relevant functions to convert all columns, except the panel variable, to cognostics objects are as follows.```{r}#| label: cognostics functions#| code-fold: true#' @title Validate Cognostics Data#' @description Validate Cognostics Data#' @param cog_df A data frame or tibble that contains cognostics information#' @param needed_column A vector consisting of needed column names that#' must be found in `cog_df`,#' Default:#' `c("col_name_vec", "desc_vec", "type_vec")`#' @return An error if the things in `needed_column`#' is not found in the Cognostics Data#' @examples#' # Create Cognostics Dataframe#' col_name_vec <- c("Transition_Name", "Dilution_Batch_Name")#'#' desc_vec <- c("Transition_Name", "Dilution_Batch_Name")#'#' type_vec <- c("factor","factor")#'#' cog_df <- data.frame(col_name_vec = col_name_vec,#' desc_vec = desc_vec,#' type_vec = type_vec)#'#' validate_cog_df(cog_df)#'#' @rdname validate_cog_df#' @exportvalidate_cog_df <-function(cog_df,needed_column =c("col_name_vec","desc_vec","type_vec")) {# Check if things in needed_column are in cog_df assertable::assert_colnames(cog_df, needed_column,only_colnames =FALSE, quiet =TRUE)}#' @title Update Cognostics Manually#' @description Update cognostics on `summary_table` based on#' the cognostics parameters given by `cog_df`. We assume#' `cog_df` is created manually#' @param summary_table The summary data frame or tibble generated#' by function [summarise_dilution_data()]#' @param cog_df A data frame or tibble output#' from the function [create_default_cog_df()] or created manually#' @param col_name_vec Column name in `cog_df` to indicate the columns#' in `summary_table` that needs to be converted to a cognostics,#' Default: 'col_name_vec'#' @param desc_vec Column name in `cog_df` to indicate the description#' for each cognostics as define in `trelliscopejs::cog`,#' Default: 'desc_vec'#' @param type_vec Column name in `cog_df` to indicate the type#' of each cognostics as define in `trelliscopejs::cog`,#' Default: 'type_vec'#' @return `summary_table` with some columns converted#' to type cog as defined in `trelliscopejs::cog`#' @details#' More details in `trelliscopejs::cog` can be found in#' <https://rdrr.io/cran/trelliscopejs/man/cog.html>#'#' @rdname update_cog_manual#' @exportupdate_cog_manual <-function(summary_table, cog_df,col_name_vec ="col_name_vec",desc_vec ="desc_vec",type_vec ="type_vec") {if (is.null(cog_df)) {return(summary_table) }#Check if cog_df is valid with the relevant columnsvalidate_cog_df(cog_df,needed_column =c(col_name_vec, desc_vec, type_vec) )# See this webpage to learn how to mutate specific columns.# https://stackoverflow.com/questions/52998471/dynamically-determine-if-a-dataframe-column-exists-and-mutate-if-it-doesfor (colname incolnames(summary_table)) { row_index <-which(cog_df$col_name_vec == colname)if (length(row_index) ==1) { summary_table <- summary_table |> dplyr::mutate( dplyr::across(.cols = dplyr::one_of(cog_df[[col_name_vec]][row_index]),.fns = \(x) trelliscopejs::cog(val = x, desc = cog_df[[desc_vec]][row_index], type = cog_df[[type_vec]][row_index] ) ) ) } }return(summary_table)}#' Update Cognostics Automatically#' @description Update cognostics on the dilution summary based on#' the cognostics data frame given by the function `create_default_cog_df`.#' @param summary_table The summary table generated#' by function [summarise_dilution_data()]#' @return `summary_table` with some columns converted#' to type cog as defined in `trelliscopejs::cog`#' @details#' More details in `trelliscopejs::cog` can be found in#' <https://rdrr.io/cran/trelliscopejs/man/cog.html>#' @rdname update_cog_auto#' @exportupdate_cog_auto <-function(summary_table) {# Create the default cognostics table cog_df <-create_default_cog_df()# Update the dilution summary columns and# convert the relevant to cognostics summary_table <- summary_table |>update_cog_manual(cog_df = cog_df)return(summary_table)}#' @title Convert To Cognostics#' @description Convert columns in `summary_table` to `trelliscopejs` cognostics#' for the `Trelliscope` display#' @param summary_table Input summary table as a#' data frame or tibble#' @param cog_df A data frame or tibble that contains cognostics information#' If no input is given the cognostics information generated by function#' [create_default_cog_df()] will be used.#' Default: NULL#' @param grouping_variable A character vector of#' column names in `summary_table`to indicate how each dilution curve#' should be grouped by. It is also going to be used as a conditional#' cognostics in the `trelliscopejs` report,#' Default: c("Transition_Name")#' @param panel_variable A column name in `summary_table` to be converted#' into a panel for the `Trelliscope` display#' Default: NULL#' @param col_name_vec Column name in `cog_df` to indicate the columns#' in `summary_table` that needs to be converted to a cognostics,#' Default: 'col_name_vec'#' @param desc_vec Column name in `cog_df` to indicate the description#' for each cognostics as define in `trelliscopejs::cog`,#' Default: 'desc_vec'#' @param type_vec Column name in `cog_df` to indicate the type#' of each cognostics as define in `trelliscopejs::cog`,#' Default: 'type_vec'#' @return The dilution summary table with `grouping variable` columns#' converted to conditional cognostics,#' other columns in `summary_table` converted to cognostics#' to be used in the in the `trelliscopejs` report.#'#' @rdname convert_to_cog#' @exportconvert_to_cog <-function(summary_table, cog_df =NULL,grouping_variable =c("Transition_Name"),panel_variable =NULL,col_name_vec ="col_name_vec",desc_vec ="desc_vec",type_vec ="type_vec") {# Check if things in needed_column are in summary_table assertable::assert_colnames(summary_table, grouping_variable,only_colnames =FALSE, quiet =TRUE)# Check if panel_variable is also a grouping variableif (isTRUE(panel_variable %in% grouping_variable)) {stop(paste("panel_variable", panel_variable,"cannot be a grouping_variable") ) }# Convert logical columns to characters summary_table <- summary_table |> dplyr::mutate_if(is.logical,~as.character(.x))# Separate the panel variables if it is in summary_table panel_df <-NULLif (!is.null(panel_variable)) { panel_df <- summary_table |> dplyr::select(dplyr::any_of(c(grouping_variable, panel_variable))) summary_table <- summary_table |> dplyr::select(-dplyr::any_of(c(panel_variable))) }# Get cognostics for trellis report# First convert the columns based on default cognostics# from create_default_cog_df()# Next convert the columns based on user's input cognostics# Lastly, convert the rest of the columns based on class# Grouping variables must be the conditional columns summary_table <- summary_table |>update_cog_auto() |>update_cog_manual(cog_df = cog_df,col_name_vec = col_name_vec,desc_vec = desc_vec,type_vec = type_vec) |> trelliscopejs::as_cognostics(cond_cols = grouping_variable,needs_cond =TRUE, needs_key =FALSE)#Convert panel variables if any to trelliscope_panelsif (!is.null(panel_df) &&ncol(panel_df) !=length(grouping_variable)) {# Ensure that the grouping variable is converted to# conditional columns# Ensure that the panel variable is converted to# trelliscope_panel panel_df <- panel_df |> dplyr::select(dplyr::all_of(c(grouping_variable))) |> trelliscopejs::as_cognostics(cond_cols = grouping_variable,needs_cond =TRUE,needs_key =FALSE) |> dplyr::bind_cols(panel_df |> dplyr::select(dplyr::any_of(c(panel_variable))) ) |> dplyr::mutate( dplyr::across(.cols = dplyr::all_of(panel_variable),.fns = \(x) structure(x, class =c("trelliscope_panels","list")) ) )# Panel_df to do a left join with dilution summary# Move panel_variable to the end summary_table <- panel_df |> dplyr::left_join(summary_table, by = grouping_variable) |> dplyr::relocate(dplyr::any_of(c(panel_variable)),.after = dplyr::last_col()) }return(summary_table)}```## Cognostics SetupThe function [`trelliscopejs::cog`](https://hafen.github.io/trelliscopejs/reference/cog.html){target="_blank"} is used to convert a column into cognostic object. The following meta information must be provided in order for the conversion to be successful.Given a column name, we need to provide* Description of the column name (meta information)* The type of cognostics to used. For our example only "numeric" and "factor" are used. Other factor types can be found in the documentation of [`trelliscopejs::cog`](https://hafen.github.io/trelliscopejs/reference/cog.html){target="_blank"}Here are the relevant functions to create our meta information tibble.```{r}#| label: default cognostics functions#| code-fold: true#' @title Create Transition Name Annotation#' Cognostics Data Frame#' @description Create transition name annotation#' cognostics data frame to be used to#' convert columns to class cognostics#' @return A dataframe of default cognostics information#' @details Internal function to create a dataframe of default cognostics#' information to be used by the `trelliscopejs::cog`.#' @examples#' transition_name_annot_cog_df <- create_transition_name_annot_cog_df()#' transition_name_annot_cog_df#'#' @rdname create_transition_name_annot_cog_df#' @exportcreate_transition_name_annot_cog_df <-function() {# More details in `trelliscopejs::cog` can be found in# <https://rdrr.io/cran/trelliscopejs/man/cog.html> col_name_vec <-c("Transition_Name","Lipid_Name","For_Rgoslin","Precursor_Ion","Product_Ion","Retention_Time_[min]","Delta_Retention_Time_[min]","Collision_Energy","Cell_Accelerator_Voltage","Polarity","isISTD") desc_vec <-c("Name of the transition to be mesured in the mass spectrometer.","Converted transition name to suit lipid nomenclature set by Liebisch et. al. 2020.","Input transition for the R package rgoslin to annotate.","The ion to be fragmented into smaller fragment ions.","Ions created from fragmentation of the precursor ion.","Transition name's expectred retention time.","Time window to acquire the transition. Each transition is acquired in the range Retention Time +/- 0.5(Delta Retention Time).","Rate of acceleration as the precursor ions enter the Q2 for fragmentation.","Rate of acceleration as the product ions leave the Q2.","Settings (Positive or Negative) for the mass spectrometry to detect (positive or negative) ions.","Set to TRUE if the given transition name an internal standard.") type_vec <-c("factor","factor","factor","numeric","numeric","numeric","numeric","numeric","numeric","factor","factor") transition_annot_name_cog_df <-data.frame(col_name_vec = col_name_vec,desc_vec = desc_vec,type_vec = type_vec)return(transition_annot_name_cog_df)}#' @title Create Rgoslin Annotation#' Cognostics Data Frame#' @description Create rgoslin annotation#' cognostics data frame to be used to#' convert columns to class cognostics#' @return A dataframe of default cognostics information#' @details Internal function to create a dataframe of default cognostics#' information to be used by the `trelliscopejs::cog`.#' @examples#' rgoslin_cog_df <- create_rgoslin_cog_df()#' rgoslin_cog_df#'#' @rdname create_rgoslin_cog_df#' @exportcreate_rgoslin_cog_df <-function() {# More details in `trelliscopejs::cog` can be found in# <https://rdrr.io/cran/trelliscopejs/man/cog.html> col_name_vec <-c("Lipid.Maps.Category","Lipid.Maps.Main.Class","Species.Name","Molecular.Species.Name","Functional.Class.Abbr","Functional.Class.Synonyms" ) desc_vec <-c("Lipid MAPS' Lipid Category which the transition name belongs to.","Lipid MAPS' Lipid Main Class which the transition name belongs to.","Represented by the sum composition, i.e., sum of carbon atoms, double bond equivalent and number of additional oxygen atoms, e.g. FA 18:1;O.","Represented by lipid species with identified fatty acyl/alkyl residues, e.g. TG 16:0_18:1_18:1.","Abbreviation used to describe the lipid's functional class based on the grammar used.","Alternative abbreviations used to describe the lipid's functional class." ) type_vec <-c("factor","factor","factor","factor","factor","factor") rgoslin_cog_df <-data.frame(col_name_vec = col_name_vec,desc_vec = desc_vec,type_vec = type_vec)return(rgoslin_cog_df)}#' @title Create Dilution Stats#' Cognostics Data Frame#' @description Create dilution statistics#' cognostics data frame to be used to#' convert columns to class cognostics#' @return A dataframe of default cognostics information#' @details Internal function to create a dataframe of default cognostics#' information to be used by the `trelliscopejs::cog`.#' @examples#' dilution_stats_cog_df <- create_dilution_stats_cog_df()#' dilution_stats_cog_df#'#' @rdname create_dilution_stats_cog_df#' @exportcreate_dilution_stats_cog_df <-function() { col_name_vec <-c("r_corr","r2_linear","r2_linear_<=0.8") desc_vec <-c("Pearson Correlation R value.","Linear Regression R^2 Value.","Set to TRUE if r2_linear is less than 0.8.") type_vec <-c("numeric", "numeric","factor") dilution_stats_cog_df <-data.frame(col_name_vec = col_name_vec,desc_vec = desc_vec,type_vec = type_vec)return(dilution_stats_cog_df)}#' @title Create QC Sample Type Stats#' Cognostics Data Frame#' @description Create QC sample type statistics#' cognostics data frame to be used to#' convert columns to class cognostics#' @return A dataframe of default cognostics information#' @details Internal function to create a dataframe of default cognostics#' information to be used by the `trelliscopejs::cog`.#' @examples#' qc_sample_type_stats_cog_df <- create_qc_sample_type_stats_cog_df()#' qc_sample_type_stats_cog_df#'#' @rdname create_qc_sample_type_stats_cog_df#' @exportcreate_qc_sample_type_stats_cog_df <-function() { col_name_vec <-c("bqc_mean","bqc_median","bqc_sd","bqc_mad","bqc_cv_%","bqc_cv_robust_%","tqc_mean","tqc_median","tqc_sd","tqc_mad","tqc_cv_%","tqc_cv_robust_%","spl_mean","spl_median","spl_sd","spl_mad","spl_cv_%","spl_cv_robust_%","pblk_median","pblk_to_tqc_ratio_%","bqc_cv_>20%","tqc_cv_>20%","pblk_to_tqc_ratio_>10%" ) desc_vec <-c("Mean signal from batch / pooled QC samples defined by Broadhurst et. al. 2018.","Median signal from batch / pooled QC samples defined by Broadhurst et. al. 2018.","Standard deviation of signal from batch / pooled QC samples defined by Broadhurst et. al. 2018.","Median absolute deviation of signal from batch / pooled QC samples defined by Broadhurst et. al. 2018.","Coefficient of variation of signal in % from batch / pooled QC samples defined by Broadhurst et. al. 2018.","Robust coefficient of variation of signal in % from batch / pooled QC samples defined by Broadhurst et. al. 2018.","Mean signal from techincal QC (or pooled lipid extracts) samples defined by Broadhurst et. al. 2018.","Median signal from techincal QC (or pooled lipid extracts) samples defined by Broadhurst et. al. 2018.","Standard deviation of signal from techincal QC (or pooled lipid extracts) samples defined by Broadhurst et. al. 2018.","Median absolute deviation of signal techincal QC (or pooled lipid extracts) samples defined by Broadhurst et. al. 2018.","Coefficient of variation of signal in % from techincal QC (or pooled lipid extracts) samples defined by Broadhurst et. al. 2018.","Robust coefficient of variation in % of signal from techincal QC (or pooled lipid extracts) samples defined by Broadhurst et. al. 2018.","Mean signal from experimental (unknown) samples defined by Broadhurst et. al. 2018.","Median signal from experimental (unknown) defined by Broadhurst et. al. 2018.","Standard deviation of signal from experimental (unknown) defined by Broadhurst et. al. 2018.","Median absolute deviation of signal from experimental (unknown) defined by Broadhurst et. al. 2018.","Coefficient of variation in % of signal from experimental (unknown) defined by Broadhurst et. al. 2018.","Robust coefficient of variation in % of signal from experimental (unknown) defined by Broadhurst et. al. 2018.","Median signal from processed blank samples defined by Broadhurst et. al. 2018.","Ratio of median signal from processed blank sample to median signal from techincal QC (or pooled lipid extracts) samples defined by Broadhurst et. al. 2018.","Set to TRUE if bqc_cv is greater than 20%.","Set to TRUE if tqc_cv is greater than 20%.","Set to TRUE if pblk_to_tqc_ratio_ % is greater than 10%." ) type_vec <-c("numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","numeric","factor","factor","factor" ) qc_sample_type_stats_cog_df <-data.frame(col_name_vec = col_name_vec,desc_vec = desc_vec,type_vec = type_vec)return(qc_sample_type_stats_cog_df)}#' @title Create Default Cognostics Data Frame#' @description Create default cognostics data frame to be used to#' convert columns in `dilution_summary` to class cognostics#' @return A dataframe of default cognostics information#' @details Internal function to create a dataframe of default cognostics#' information to be used by the `trelliscopejs::cog`.#' @examples#' cog_df <- create_default_cog_df()#' cog_df#'#' @rdname create_default_cog_df#' @exportcreate_default_cog_df <-function() {# More details in `trelliscopejs::cog` can be found in# <https://rdrr.io/cran/trelliscopejs/man/cog.html> col_name_vec <-c("Transition_Name") desc_vec <-c("Transition_Name") type_vec <-c("factor") cog_df <-data.frame(col_name_vec = col_name_vec,desc_vec = desc_vec,type_vec = type_vec) transition_name_annot_cog_df <-create_transition_name_annot_cog_df() rgoslin_cog_df <-create_rgoslin_cog_df() dilution_stats_cog_df <-create_dilution_stats_cog_df() qc_sample_type_stats_cog_df <-create_qc_sample_type_stats_cog_df() cog_df <- dplyr::bind_rows(transition_name_annot_cog_df, rgoslin_cog_df, dilution_stats_cog_df, qc_sample_type_stats_cog_df, cog_df)return(cog_df)}```Here is a preview of the meta information tibble```{r}#| label: preview meta information#| code-fold: truecreate_default_cog_df() |> reactable::reactable(defaultPageSize =5,bordered =TRUE,highlight =TRUE,searchable =TRUE,paginationType ="jump" )```## Cognostics CreationWe use the function `convert_to_cog` which uses the default `cog_df` created by `create_default_cog_df`.::: panel-tabset### Dilution CurvesHere are the attributes of some columns in `dilution_plot_table` before conversion to cognostics.```{r}#| label: before dilution curve cognosticsattributes(dilution_plot_table$Transition_Name)attributes(dilution_plot_table$Lipid_Name)attributes(dilution_plot_table$Precursor_Ion)attributes(dilution_plot_table$dilution_panel)```We convert each column in `dilution_plot_table` to relevant cognostics using the function `convert_to_cog`. With `Transition_Name` as a conditioning variable, `dilution_panel` as a panel variable.```{r}#| label: dilution curve cognosticsdilution_trellis <- dilution_plot_table |>convert_to_cog(grouping_variable =c("Transition_Name"),panel_variable ="dilution_panel")```Here are the attributes of some columns in `dilution_plot_table` after conversion to cognostics. Observe that the attributes of the columns are different.For the column labelled as conditioning (grouping) variable column labelled we have.```{r}#| label: after dilution curve cognostics 1attributes(dilution_trellis$Transition_Name)```For the column labelled as a panel variable, we have```{r}#| label: after dilution curve cognostics 2attributes(dilution_trellis$dilution_panel)```For the rest of the column converted to a common cognostics, we have```{r}#| label: after dilution curve cognostics 3attributes(dilution_trellis$Lipid_Name)attributes(dilution_trellis$Precursor_Ion)```### Injection Sequence PlotsHere are the attributes of some columns in `injection_sequence_table` before conversion to cognostics.```{r}#| label: before injection sequence plot cognosticsattributes(injection_sequence_table$Transition_Name)attributes(injection_sequence_table$Lipid_Name)attributes(injection_sequence_table$Precursor_Ion)attributes(injection_sequence_table$panel)```We convert each column in `injection_sequence_table` to relevant cognostics using the function `convert_to_cog`. With `Transition_Name` as a conditioning variable, `panel` as a panel variable.```{r}#| label: injection sequence plot cognosticsinjection_sequence_trellis <- injection_sequence_table |>convert_to_cog(grouping_variable =c("Transition_Name"),panel_variable ="panel")```Here are the attributes of some columns in `injection_sequence_table` after conversion to cognostics. Observe that the attributes of the columns are different.For the column labelled as conditioning (grouping) variable column labelled we have.```{r}#| label: after injection sequence plot cognostics 1attributes(injection_sequence_trellis$Transition_Name)```For the column labelled as a panel variable, we have```{r}#| label: after injection sequence plot cognostics 2attributes(injection_sequence_trellis$panel)```For the rest of the column converted to a common cognostics, we have```{r}#| label: after injection sequence plot cognostics 3attributes(injection_sequence_trellis$Lipid_Name)attributes(injection_sequence_trellis$Precursor_Ion)```:::# Create Trellis Plot## Dilution CurvesHere is a trellis plot for the dilution curves.```{r}#| label: dilution curve trellis#| column: screen-inset-righttrelliscope_name <-"Dilution Plot"# Conditional cognostics must always be displayed as a labeltrellis_labels <-c("Transition_Name") |>unique()# If we only have one label to display# Set it as NULL as it will automatically# appeared in stateif (length(trellis_labels) ==1) { trellis_labels <-NULL}dilution_trellis |> trelliscopejs::trelliscope(name = trelliscope_name,panel_col ="dilution_panel",path ="Dilution_Plot_Folder",state =list(labels = trellis_labels,sort =list(trelliscopejs::sort_spec("Transition_Name", dir ="asc"))),nrow =2,ncol =2,height =520,width =1200,self_contained =FALSE,thumb =FALSE,auto_cog =FALSE )```## Injection Sequence PlotsHere is a trellis plot for the injection sequence plots.```{r}#| label: injection sequence plot trellis#| column: screen-inset-righttrelliscope_name <-"Injection Sequence Plot"# Conditional cognostics must always be displayed as a labeltrellis_labels <-c("Transition_Name") |>unique()# If we only have one label to display# Set it as NULL as it will automatically# appeared in stateif (length(trellis_labels) ==1) { trellis_labels <-NULL}injection_sequence_trellis |> trelliscopejs::trelliscope(name = trelliscope_name,panel_col ="panel",path ="Injection_Sequence_Plot_Folder",state =list(labels = trellis_labels,sort =list(trelliscopejs::sort_spec("Transition_Name", dir ="asc"))),nrow =1,ncol =1,height =520,width =1200,self_contained =FALSE,thumb =FALSE,auto_cog =FALSE )```## Combined PlotsIt is also possible to combined the two trellis plots together as they have the same conditioning cognostics variable. Simply set the `path` to the same folder name. In the example below, `path = "docs"` was used.Unfortunately, the trellis plot does not seems to function properly in the Quarto document. As such, the code below is called interactively via the console.```{r}#| label: dilution and injection sequence trellis print code#| eval: falsetrelliscope_name <-"Dilution Plot"# Conditional cognostics must always be displayed as a labeltrellis_labels <-c("Transition_Name") |>unique()# If we only have one label to display# Set it as NULL as it will automatically# appeared in stateif (length(trellis_labels) ==1) { trellis_labels <-NULL}dilution_trellis |> trelliscopejs::trelliscope(name = trelliscope_name,panel_col ="dilution_panel",path ="docs",state =list(labels = trellis_labels,sort =list(trelliscopejs::sort_spec("Transition_Name", dir ="asc"))),nrow =2,ncol =2,height =520,width =1100,self_contained =FALSE,thumb =FALSE,auto_cog =FALSE )trelliscope_name <-"Injection Sequence Plot"injection_sequence_trellis |> trelliscopejs::trelliscope(name = trelliscope_name,panel_col ="panel",path ="docs",state =list(labels = trellis_labels,sort =list(trelliscopejs::sort_spec("Transition_Name", dir ="asc"))),nrow =1,ncol =1,height =520,width =1100,self_contained =FALSE,thumb =FALSE,auto_cog =FALSE )```A `docs` folder will be created. It is currently placed in this [GitHub repository](https://github.com/JauntyJJS/Trelliscopejs_In_Quarto_Supplementary1){target="_blank"}. Below is just an embedded webpage of the trellis plot. It can be accessed via this [GitHub Page link](https://jauntyjjs.github.io/Trelliscopejs_In_Quarto_Supplementary1/){target="_blank"}.```{=html}<iframe width="1100" height="520" src="https://jauntyjjs.github.io/Trelliscopejs_In_Quarto_Supplementary1/"></iframe>```# Package References```{r}#| label: package references#| code-fold: true#| output: asisreport::cite_packages() |>suppressWarnings()```# References