--- title: "1D Time Series Preprocessing/Two-State Fitting" author: "Katerina M. Blejec and Colin A. Smith" output: bookdown::html_document2: number_sections: false fig_caption: true fig_width: 8.666667 fig_height: 4 bookdown::word_document2: number_sections: false fig_caption: true fig_width: 8.666667 reference_docx: template.docx pkgdown: as_is: true vignette: > %\VignetteIndexEntry{1D Time Series Preprocessing/Two-State Fitting} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} if (Sys.getenv("IN_PKGDOWN") == "" && ! interactive()) { knitr::opts_chunk$set(echo = FALSE, dev = "png", fig.retina = 1, dev.args = list(pointsize = 10)) } else if (knitr::opts_knit$get("rmarkdown.pandoc.to") %in% c("docx", "docx+styles")) { knitr::opts_chunk$set(echo = FALSE, dev = "png", dpi = 300, dev.args = list(pointsize = 10)) } else { knitr::opts_chunk$set(echo = FALSE, dev = "png", dpi = 300, dev.args = list(pointsize = 10)) } # figure plotting functions figure_letter <- function(letter) { old_usr <- par("usr") if (par("xlog")) old_usr[1:2] <- 10^old_usr[1:2] if (par("ylog")) old_usr[3:4] <- 10^old_usr[3:4] old_par <- par(mar=c(0.2,0.2,0.2,0.2)) plot.window(old_usr[1:2], old_usr[3:4]) mtext(letter, 3, -1, adj=0, font=2) par(old_par) plot.window(old_usr[1:2], old_usr[3:4]) } ``` ```{r results = if (Sys.getenv("IN_PKGDOWN")=="" && !interactive()) "asis" else "hide", echo = FALSE} cat("**Note:** A high-resolution version of this document is [available online](https://smith-group.github.io/fitnmr/articles/timeseries1d.html).") ``` To use this document to process your own data, change the `fid_dir` path below. ```{r fid-directory, echo=TRUE} # directory with each subdirectory containing an NMRPipe formatted FID file (*.fid) and Bruker acqus file fid_dir <- system.file("extdata", "noesy1d", package = "fitnmr") #fid_dir <- "path/to/your/directory" ``` # 1D Preprocessing ```{r read_fids} # find all the fid files within the fid directory fid_files <- list.files(fid_dir, "*.fid", full.names = TRUE, recursive = TRUE) # order fid files fid_files <- fid_files[do.call(order, read.table(text=fid_files, sep="/"))] # read all the FIDs with FitNMR if they haven't already been read if (!"fid_list" %in% ls()) { fid_list <- suppressWarnings(lapply(fid_files, fitnmr::read_nmrpipe, complex_data=TRUE)) fid_list <- fid_list fid_mat <- sapply(fid_list, `[[`, "int") } # extract times from acqus files acqus_files <- sub("[^/]+.fid", "acqus", fid_files) acqus_lines <- lapply(acqus_files, readLines) date_lines <- sapply(acqus_lines, function(x) grep("^\\$\\$ [0-9]", x, value=TRUE)) date_text <- sub("^[^ ]+ ([^ ]+ [^ ]+ [^ ]+) .*$", "\\1", date_lines) seconds <- as.numeric(as.POSIXct(date_text)) seconds <- seconds - min(seconds) hours <- seconds/60/60 ``` ```{r initial_ft, cache=TRUE} # perform initial Fourier transform if (!"ft_list" %in% ls()) { ft_list <- lapply(seq_along(fid_list), function(i) { fid <- fid_list[[i]] fid_ft <- fitnmr::nmrpipe_ft(fid) }) ft_mat <- sapply(ft_list, `[[`, "int") } ``` ```{r phase_opt, cache=TRUE} # phase optimization functions nmrpipe_p1_frac <- function(fheader) { as.vector(sapply(1, function(j) { frac <- seq(0, 1, length.out=fheader["FTSIZE",j]+1) frac <- frac[-length(frac)] if (all(fheader[c("X1","XN"),j] != 0)) { frac <- frac[seq(fheader["X1",j], fheader["XN",j])] } frac })) } phase_fn <- function(int, p0=0, p1=0, p1_frac=0) { p0 <- p0*pi/180 # convert degrees to rad p1 <- p1*pi/180 # convert degrees to rad pvec <- exp(1i*(p0+p1*p1_frac)) Re(int*pvec) } rss_fn <- function(x, spec, spec_ref, p1_frac) { spec_diff <- phase_fn(spec, x[1], x[2], p1_frac)-spec_ref sum(Re(spec_diff)^2, na.rm=TRUE) } spec_xlim <- c(10,0) spec_ppm <- as.vector(ft_list[[1]]$ppm[[1]]) phase_initial <- c(156, 0) phase_p0_range <- c(-180, 180) phase_p1_range <- c(-10, 10) #*.0001 phase_ref_int <- ft_list[[1]]$int phase_ref_int[] <- 0 phase_ref_int[spec_ppm < spec_xlim[1] & spec_ppm > spec_xlim[2]] <- NA phase_p1_frac <- nmrpipe_p1_frac(ft_list[[1]]$fheader) if (! "phase_vals" %in% ls()) { phase_vals <- sapply(seq_along(ft_list), function(i) { optim(phase_initial, rss_fn, method="L-BFGS-B", lower=c(phase_p0_range[1], phase_p1_range[1]), upper=c(phase_p0_range[2], phase_p1_range[2]), spec=ft_list[[i]]$int, spec_ref=phase_ref_int, p1_frac=phase_p1_frac)$par }) } if (!"ftps_list" %in% ls()) { ftps_list <- lapply(seq_along(ft_list), function(i) { ft <- ft_list[[i]] ftps <- fitnmr::nmrpipe_ps(ft, p0=phase_vals[1,i], p1=phase_vals[2,i]) }) ftps_mat <- sapply(ftps_list, `[[`, "int") } ``` Free induction decay (FID) data is first converted to NMRPipe format then read by FitNMR. Times for each dataset are extracted from the `acqus` files output by Bruker TopSpin. After performing an initial Fourier transform, the zero- and first-order phases are then optimized by finding values that minimize the residual sum of squares outside the 10-0 ppm window where signals are observed. The optimized phase values are shown in Figure \@ref(fig:phases). In this sample acquired at approximately 70 °C, the phase parameters didn't vary by more than `r signif(max(diff(range(phase_vals[1,])), diff(range(phase_vals[2,]))), 2)` degrees over all the individual NMR experiments. ```{r phases, fig.cap="**Automated optimization of zero- and first-order phases.** The red to purple color scheme is used in all figures to indicate the data acquisition time."} all_cols <- rainbow(ncol(phase_vals), end=0.80) old_par <- par(mar=c(1.5, 3.1, 0.5, 0.5), oma=c(1.6, 0, 0, 0), mgp=c(1.9, 0.6, 0), mfrow=c(2, 1), cex=1) plot(hours, phase_vals[1,], col=all_cols, pch=16, cex=0.75, xlab=NA, ylab="P0 (degrees)") points(hours, phase_vals[1,], type="l") plot(hours, phase_vals[2,], col=all_cols, pch=16, cex=0.75, xlab=NA, ylab="P1 (degrees)") points(hours, phase_vals[2,], type="l") #plot(hours, colSums(Re(ftps_mat)), col=all_cols, pch=16, cex=0.5, xlab=NA, ylab="Total Intensity") #points(hours, colSums(Re(ftps_mat)), type="l") title(xlab="Time (hours)", outer=TRUE, mgp=c(0.5, 0, 0)) ``` Solvent signals are removed using an inverted Gaussian function with standard deviation of 0.5 ppm as shown in Figure \@ref(fig:spec-phased). This results in attenuation of signals within approximately 1 ppm of the solvent. The filter is applied to all spectra equally, minimizing potential impact on quantitative analysis. ```{r solvent-filter, cache=TRUE} solvent_filter <- -dnorm(spec_ppm, fid_list[[1]]$fheader["CAR",], 0.5) solvent_filter <- solvent_filter-min(solvent_filter) solvent_filter <- solvent_filter/max(solvent_filter) if (!"ftpssf_list" %in% ls()) { ftpssf_list <- lapply(seq_along(ftps_list), function(i) { ftpssf <- ftps_list[[i]] ftpssf$int[] <- ftpssf$int[]*solvent_filter ftpssf }) ftpssf_mat <- sapply(ftpssf_list, `[[`, "int") } ``` ```{r spec-phased, cache=TRUE, fig.height=6, fig.cap="**Spectra before (A) and after (B) solvent removal with Gaussian filter (black line).**"} # positions of x-axis tick marks axis_integer <- seq(floor(min(spec_ppm)), ceiling(max(spec_ppm))) axis_tenths <- setdiff(seq(floor(min(spec_ppm)*10),ceiling(max(spec_ppm)*10))/10, axis_integer) spec_lwd <- 0.1 par(mar=c(3.1,3.1,0.5+0.5,0.5), mgp=c(1.9, 0.6, 0), mfrow=c(2, 1), cex=1) plot(1, 1, type="n", xlim=spec_xlim, ylim=range(Re(ftpssf_mat)), xlab="1H (ppm)", ylab="Intensity", xaxt="n") axis(1, axis_integer, TRUE) axis(1, axis_tenths, FALSE, lwd.ticks=0.5, tcl=-0.25) abline(h=0, col="gray") for (i in seq_len(ncol(ftpssf_mat))) { points(spec_ppm, Re(ftps_mat[,i]), type="l", lwd=spec_lwd, col=all_cols[i]) } sf_plot_idx <- solvent_filter < 0.9 points(spec_ppm[sf_plot_idx], (solvent_filter[sf_plot_idx]-1)*abs(par("usr")[3]), type="l", lwd=0.5) figure_letter("A") plot(1, 1, type="n", xlim=spec_xlim, ylim=range(Re(ftpssf_mat)), xlab="1H (ppm)", ylab="Intensity", xaxt="n") axis(1, axis_integer, TRUE) axis(1, axis_tenths, FALSE, lwd.ticks=0.5, tcl=-0.25) abline(h=0, col="gray") for (i in seq_len(ncol(ftpssf_mat))) { points(spec_ppm, Re(ftpssf_mat[,i]), type="l", lwd=spec_lwd, col=all_cols[i]) } sf_plot_idx <- solvent_filter < 0.9 points(spec_ppm[sf_plot_idx], (solvent_filter[sf_plot_idx]-1)*abs(par("usr")[3]), type="l", lwd=0.5) figure_letter("B") ``` After removal of solvent, the spectra are inverse Fourier transformed back into FIDs shown in Figure \@ref(fig:solvent-filtered-fid)B. ```{r inverse-fft, cache=FALSE} if (!"ftpssffti_list" %in% ls()) { ftpssffti_list <- lapply(seq_along(ftpssf_list), function(i) { ftpssf <- ftpssf_list[[i]] ftpssf$int[] <- ftpssf$int[]*solvent_filter ftpssffti <- fitnmr::nmrpipe_fti(ftpssf) ftpssffti }) ftpssffti_mat <- sapply(ftpssffti_list, `[[`, "int") } fid_seconds <- as.numeric(rownames(ftpssffti_mat)) ``` ```{r solvent-filtered-fid, cache=TRUE, fig.height=6, fig.cap="**Time domain data before (A) and after (B) solvent filtering in the frequency domain.**"} par(mar=c(3.1,3.1,0.5,0.5), mgp=c(1.9, 0.6, 0), mfrow=c(2, 1), cex=1) plot(1, 1, type="n", xlim=range(fid_seconds), ylim=max(abs(Re(sapply(fid_list, "[[", "int"))))*c(-1,1), xlab="Time (seconds)", ylab="Intensity") abline(h=0, v=0, col="gray") for (i in seq_along(fid_list)) { points(fid_seconds, Re(fid_list[[i]]$int), type="l", lwd=spec_lwd, col=all_cols[i]) } figure_letter("A") plot(1, 1, type="n", xlim=range(fid_seconds), ylim=max(abs(Re(ftpssffti_mat)))*c(-1,1), xlab="Time (seconds)", ylab="Intensity") abline(h=0, v=0, col="gray") for (i in seq_len(ncol(ftpssffti_mat))) { points(fid_seconds, Re(ftpssffti_mat[,i]), type="l", lwd=spec_lwd, col=all_cols[i]) } figure_letter("B") ``` ```{r fid-1-ratio-loess-calc, cache=TRUE} fid_1_ratio_mat <- ftpssffti_mat/ftpssffti_mat[,1] fid_pre_idx <- seq_len(fid_list[[1]]$header["FDDMXVAL"]) loess_span <- 0.05 # get field inhomogeneity from first scan if (!"fid_1_ratio_loess_mat" %in% ls()) { fid_1_ratio_loess_mat <- sapply(seq_len(ncol(ftpssffti_mat)), function(i) { data_re <- data.frame(x=seq_len(nrow(fid_1_ratio_mat)), y=Re(fid_1_ratio_mat[,i])) # don't fit first part of FID data_re <- data_re[-fid_pre_idx,] loess_fit_re <- loess(y~x, data_re, span=loess_span) data_im <- data.frame(x=seq_len(nrow(fid_1_ratio_mat)), y=Im(fid_1_ratio_mat[,i])) # don't fit first part of FID data_im <- data_im[-fid_pre_idx,] loess_fit_im <- loess(y~x, data_im, span=loess_span) loess_fit_re$fitted + loess_fit_im$fitted*1i }) } fid_seconds_loess <- fid_seconds[-fid_pre_idx] ``` To visualize shifts in field homogeneity and frequency over the experiment time course, the ratio of each FID to the first FID is calculated. This ratio has both real and imaginary components. The real and imaginary components of those those ratios are then separately smoothed with Locally Estimated Scatterplot Smoothing (LOESS) using polynomial regression spanning 5% of the points. The smoothed ratios for both components are shown in Figure \@ref(fig:fid-1-ratio-loess). ```{r fid-1-ratio-loess, cache=TRUE, fig.height=6, fig.cap="**Smoothed ratios of every FID to the first FID.** Real ratios indicate line broadening and imaginary ratios indicate frequency shifts relative to the first spectrum."} par(mar=c(3.1,3.1,0.5,0.5), mgp=c(1.9, 0.6, 0), mfrow=c(2, 1), cex=1) plot(1, 1, type="n", xlim=range(fid_seconds), ylim=range(Re(fid_1_ratio_loess_mat)), xlab="Time (seconds)", ylab="Real Ratio") abline(h=c(0, 1), col="gray") for (i in seq_len(ncol(ftpssffti_mat))) { points(fid_seconds_loess, Re(fid_1_ratio_loess_mat[,i]), type="l", lwd=spec_lwd, col=all_cols[i]) } plot(1, 1, type="n", xlim=range(fid_seconds), ylim=range(Im(fid_1_ratio_loess_mat)), xlab="Time (seconds)", ylab="Imaginary Ratio") abline(h=0, col="gray") for (i in seq_len(ncol(ftpssffti_mat))) { points(fid_seconds_loess, Im(fid_1_ratio_loess_mat[,i]), type="l", lwd=spec_lwd, col=all_cols[i]) } ``` For the real component, a ratio less than one indicates line broadening, likely due to a decrease in field homogeneity. For the imaginary component, a ratio that starts at zero but linearly increases or decreases indicates a frequency shift, which is especially apparent in the middle of the time course (green and teal). Though appearing large, ratios in the second half of the FIDs are less significant because the FID signal (Figure \@ref(fig:solvent-filtered-fid)B) has largely decayed by that point. The broadening and frequency shifts can be visualized more directly by applying a cosine-squared window function to those ratios and Fourier transforming them into the frequency domain as shown in Figure \@ref(fig:fid-1-ratio-ft). ```{r fid-1-ratio-ft-calc, cache=TRUE} if (!"fid_1_ratio_loess_ft" %in% ls()) { fid_1_ratio_loess_ft <- lapply(seq_len(ncol(fid_1_ratio_loess_mat)), function(i) { fid <- fid_list[[1]] fid$int[] <- 0 fid$int[-fid_pre_idx] <- fid_1_ratio_loess_mat[,i] fid$int[-fid_pre_idx][1] <- fid$int[-fid_pre_idx][1]*0.5 #fid$int[fid_pre_idx] <- fid_1_ratio_loess_mat[1,i] fid <- fitnmr::nmrpipe_sp(fid, off=0.5, pow=2, cval=0.5) fid_zf <- fitnmr::nmrpipe_zf(fid, 2) fid_zfft <- fitnmr::nmrpipe_ft(fid_zf) fid_zfft }) fid_1_ratio_loess_ft_mat <- sapply(fid_1_ratio_loess_ft, `[[`, "int") } if (!"fid_1_ratio_ft" %in% ls()) { fid_1_ratio_ft <- lapply(seq_len(ncol(fid_1_ratio_mat)), function(i) { fid <- fid_list[[1]] fid$int[] <- fid_1_ratio_mat[,i] fid <- fitnmr::nmrpipe_sp(fid, off=0.5, pow=2, cval=0.5) fid_zf <- fitnmr::nmrpipe_zf(fid, 2) fid_zfft <- fitnmr::nmrpipe_ft(fid_zf) fid_zfft }) fid_1_ratio_ft_mat <- sapply(fid_1_ratio_ft, `[[`, "int") } ``` ```{r fid-1-ratio-ft, cache=TRUE, fig.height=6, fig.cap="**Smoothed (A) and original (B) FID ratios (individual/first) transformed into the frequency domain.** The expected result for no deviation from the first spectrum is indicated by the black line. It is just the Fourier transform of the cosine-squared window function."} xlim <- c(1,-1)*.02 freq_ppm <- spec_ppm-fid_list[[1]]$fheader["CAR",] freq_ppm_2 <- as.numeric(rownames(fid_1_ratio_ft_mat))-fid_list[[1]]$fheader["CAR",] par(mar=c(3.1,3.1,0.5,0.5), mgp=c(1.9, 0.6, 0), mfrow=c(2, 1), cex=1) plot(1, 1, type="n", xlim=xlim, ylim=range(Re(fid_1_ratio_loess_ft_mat)), xlab="Frequency (ppm)", ylab="Intensity") abline(h=0, v=0, col="gray") for (i in seq_len(ncol(fid_1_ratio_loess_ft_mat))) { points(freq_ppm_2, Re(fid_1_ratio_loess_ft_mat[,i]), type="l", lwd=spec_lwd, col=all_cols[i]) } points(freq_ppm_2, Re(fid_1_ratio_loess_ft_mat[,1]), type="l", lwd=spec_lwd*6)#, col=all_cols[i]) figure_letter("A") plot(1, 1, type="n", xlim=xlim, ylim=range(Re(fid_1_ratio_ft_mat)), xlab="Frequency (ppm)", ylab="Intensity") abline(h=0, v=0, col="gray") for (i in seq_len(ncol(fid_1_ratio_ft_mat))) { points(freq_ppm_2, Re(fid_1_ratio_ft_mat[,i]), type="l", lwd=spec_lwd, col=all_cols[i]) } points(freq_ppm_2, Re(fid_1_ratio_ft_mat[,1]), type="l", lwd=spec_lwd*6)#, col=all_cols[i]) figure_letter("B") ``` To compensate for the differential broadening and frequency shifts between spectra, an average FID is first calculated as a reference for all other FIDs. For each FID, a transform is then calculated that normalizes its intensity envelope and frequencies to match the average reference FID. The numerical values for that transform come from the ratio of the mean FID to the given FID. (Note that when the first FID was used as reference for visualization above, it was in the denominator of the ratio. Here, the reference average FID is now in the numerator of the ratio.) The resulting LOESS smoothed ratios are shown in Figure \@ref(fig:fid-mean-ratio-loess). ```{r fid-mean-ratio-loess-calc, cache=TRUE} fid_mean_ratio_mat <- rowMeans(ftpssffti_mat)/ftpssffti_mat if (!"fid_mean_ratio_loess_mat" %in% ls()) { fid_mean_ratio_loess_mat <- sapply(seq_len(ncol(ftpssffti_mat)), function(i) { data_re <- data.frame(x=seq_len(nrow(fid_mean_ratio_mat)), y=Re(fid_mean_ratio_mat[,i])) # don't fit first part of FID data_re <- data_re[-fid_pre_idx,] loess_fit_re <- loess(y~x, data_re, span=loess_span) data_im <- data.frame(x=seq_len(nrow(fid_mean_ratio_mat)), y=Im(fid_mean_ratio_mat[,i])) # don't fit first part of FID data_im <- data_im[-fid_pre_idx,] loess_fit_im <- loess(y~x, data_im, span=loess_span) loess_fit_re$fitted + loess_fit_im$fitted*1i }) } ``` ```{r fid-mean-ratio-loess, cache=TRUE, fig.height=6, fig.cap="**Smoothed ratio of mean FID intensities to each experimental FID.**"} par(mar=c(3.1,3.1,0.5,0.5), mgp=c(1.9, 0.6, 0), mfrow=c(2, 1), cex=1) plot(1, 1, type="n", xlim=range(fid_seconds), ylim=range(Re(fid_1_ratio_loess_mat)), xlab="Time (seconds)", ylab="Real Ratio") abline(h=c(0, 1), col="gray") for (i in seq_len(ncol(ftpssffti_mat))) { points(fid_seconds_loess, Re(fid_mean_ratio_loess_mat[,i]), type="l", lwd=spec_lwd, col=all_cols[i]) } plot(1, 1, type="n", xlim=range(fid_seconds), ylim=range(Im(fid_1_ratio_loess_mat)), xlab="Time (seconds)", ylab="Imaginary Ratio") abline(h=0, col="gray") for (i in seq_len(ncol(ftpssffti_mat))) { points(fid_seconds_loess, Im(fid_mean_ratio_loess_mat[,i]), type="l", lwd=spec_lwd, col=all_cols[i]) } ``` The mean/individual FID ratios shown in Figure \@ref(fig:fid-mean-ratio-loess) are used as filters to normalize the FID envelopes. The effect of normalization on each spectrum can be visualized by applying a cosine-squared window function and Fourier transforming each ratio as shown in Figure \@ref(fig:fid-mean-ratio-ft). High frequency components of the the ratios have been removed by multiplying the frequency domain ratios by a Gaussian function centered at zero frequency. The earliest spectra (red) are broadened and have artifacts added. The middle spectra (green) are shifted to the right to compensate for their leftward shift shown in Figure \@ref(fig:fid-1-ratio-ft). ```{r fid-mean-ratio-ft-calc, cache=TRUE} if (!"fid_mean_ratio_loess_ft" %in% ls()) { fid_mean_ratio_loess_ft <- lapply(seq_len(ncol(fid_mean_ratio_loess_mat)), function(i) { fid <- fid_list[[1]] fid$int[] <- 0 fid$int[-fid_pre_idx] <- fid_mean_ratio_loess_mat[,i] fid$int[-fid_pre_idx][1] <- fid$int[-fid_pre_idx][1]*0.5 #fid$int[fid_pre_idx] <- fid_mean_ratio_loess_mat[1,i] fid <- fitnmr::nmrpipe_sp(fid, off=0.5, pow=2, cval=0.5) fid_zf <- fitnmr::nmrpipe_zf(fid, 1) fid_zfft <- fitnmr::nmrpipe_ft(fid_zf) fid_zfft }) fid_mean_ratio_loess_ft_mat <- sapply(fid_mean_ratio_loess_ft, `[[`, "int") } if (!"fid_mean_ratio_ft" %in% ls()) { fid_mean_ratio_ft <- lapply(seq_len(ncol(fid_mean_ratio_mat)), function(i) { fid <- fid_list[[1]] fid$int[] <- fid_mean_ratio_mat[,i] fid <- fitnmr::nmrpipe_sp(fid, off=0.5, pow=2, cval=0.5) fid_zf <- fitnmr::nmrpipe_zf(fid, 1) fid_zfft <- fitnmr::nmrpipe_ft(fid_zf) fid_zfft }) fid_mean_ratio_ft_mat <- sapply(fid_mean_ratio_ft, `[[`, "int") } ``` ```{r fid-mean-ratio-ft, cache=TRUE, fig.height=6, fig.cap="**Smoothed (A) and original (B) FID ratios (mean/individual) transformed into the frequency domain.** High frequency components of ratios have been suppressed by multiplying a Gaussian function with standard deviation of 0.01 ppm (black line)."} xlim <- c(1,-1)*.02 freq_ppm_1 <- as.numeric(rownames(fid_mean_ratio_ft_mat))-fid_list[[1]]$fheader["CAR",] freq_filter <- dnorm(freq_ppm_1, sd=0.01) freq_filter <- freq_filter/max(freq_filter) fid_mean_ratio_loess_filter_mat <- Re(fid_mean_ratio_loess_ft_mat) fid_mean_ratio_loess_filter_mat <- fid_mean_ratio_loess_filter_mat*freq_filter fid_mean_ratio_loess_filter_mat <- t(t(fid_mean_ratio_loess_filter_mat)/colSums(fid_mean_ratio_loess_filter_mat)) fid_mean_ratio_filter_mat <- Re(fid_mean_ratio_ft_mat) fid_mean_ratio_filter_mat <- fid_mean_ratio_filter_mat*freq_filter fid_mean_ratio_filter_mat <- t(t(fid_mean_ratio_filter_mat)/colSums(fid_mean_ratio_filter_mat)) par(mar=c(3.1,3.1,0.5,0.5), mgp=c(1.9, 0.6, 0), mfrow=c(2, 1), cex=1) ylim <- range(Re(fid_mean_ratio_loess_filter_mat)) plot(1, 1, type="n", xlim=xlim, ylim=ylim, xlab="Frequency (ppm)", ylab="Intensity") abline(h=0, v=0, col="gray") for (i in seq_len(ncol(fid_mean_ratio_loess_filter_mat))) { points(freq_ppm_1, Re(fid_mean_ratio_loess_filter_mat[,i]), type="l", lwd=spec_lwd, col=all_cols[i]) } #points(freq_ppm_1, Re(fid_mean_ratio_loess_filter_mat[,1]), type="l", lwd=spec_lwd*6) points(freq_ppm_1, 0.75*freq_filter*(-par("usr")[3])+par("usr")[3], type="l") figure_letter("A") ylim <- range(Re(range(Re(fid_mean_ratio_filter_mat)))) plot(1, 1, type="n", xlim=xlim, ylim=ylim, xlab="Frequency (ppm)", ylab="Intensity") abline(h=0, v=0, col="gray") for (i in seq_len(ncol(fid_mean_ratio_filter_mat))) { points(freq_ppm_1, Re(fid_mean_ratio_filter_mat[,i]), type="l", lwd=spec_lwd, col=all_cols[i]) } #points(freq_ppm_1, Re(fid_mean_ratio_filter_mat[,1]), type="l", lwd=spec_lwd*6) points(freq_ppm_1, 0.75*freq_filter*(-par("usr")[3])+par("usr")[3], type="l") figure_letter("B") ``` The original FID data is filtered by multiplying the inverse Fourier transform of the smoothed frequency domain filters shown in Figure \@ref(fig:fid-mean-ratio-ft)A. (Due to multiplication by the Gaussian, the nearly identical unsmoothed ratios in shown in Figure \@ref(fig:fid-mean-ratio-ft)B could have also been used.) Because a cosine-squared window function has already been applied to the filter for visualization, no additional window function is used. ```{r unfiltered-filtered-calc, cache=TRUE} if (!"filtered_list" %in% ls()) { filtered_list <- lapply(seq_along(fid_list), function(i) { sig_fid <- ftpssffti_list[[i]] sig_fid <- fitnmr::nmrpipe_zf(sig_fid) sig_ft <- fitnmr::nmrpipe_ft(sig_fid) sig_ft$int[] <- Re(sig_ft$int) filt_ft <- sig_ft filt_ft$int[] <- fid_mean_ratio_loess_filter_mat[,i] #filt_ft$int[] <- fid_mean_ratio_filter_mat[,i] sig_fid <- fitnmr::nmrpipe_fti(sig_ft) filt_fid <- fitnmr::nmrpipe_fti(filt_ft) out_fid <- sig_fid out_fid$int[] <- out_fid$int*filt_fid$int out_ft <- fitnmr::nmrpipe_ft(out_fid) }) filtered_mat <- sapply(filtered_list, `[[`, "int") } if (!"unfiltered_list" %in% ls()) { unfiltered_list <- lapply(seq_along(fid_list), function(i) { sig_fid <- ftpssffti_list[[i]] sig_fid <- fitnmr::nmrpipe_sp(sig_fid, off=0.5, pow=2, cval=0.5) sig_fid <- fitnmr::nmrpipe_zf(sig_fid) sig_ft <- fitnmr::nmrpipe_ft(sig_fid) sig_ft$int[] <- Re(sig_ft$int) #filt_ft <- sig_ft #filt_ft$int[] <- fid_mean_ratio_loess_filter_mat[,i] #filt_ft$int[] <- fid_mean_ratio_filter_mat[,i] sig_fid <- fitnmr::nmrpipe_fti(sig_ft) #filt_fid <- fitnmr::nmrpipe_fti(filt_ft) out_fid <- sig_fid #out_fid$int[] <- out_fid$int*filt_fid$int out_ft <- fitnmr::nmrpipe_ft(out_fid) }) unfiltered_mat <- sapply(unfiltered_list, `[[`, "int") } filter_mean_freq <- colSums(fid_mean_ratio_loess_filter_mat*freq_ppm_1) ``` As shown in Figure \@ref(fig:filter-data)A, the experiment-specific filters correct frequency shifts of `r paste(signif(range(filter_mean_freq), 2), collapse=" to ")` ppm. Relative to the first spectrum, the total spectrum intensities are up to `r paste0(signif((max(colSums(Re(filtered_mat)))/colSums(Re(filtered_mat))[1]-1)*100, 2), "%")` higher or `r paste0(-signif((min(colSums(Re(filtered_mat)))/colSums(Re(filtered_mat))[1]-1)*100, 2), "%")` lower. (Figure \@ref(fig:filter-data)B) As a last step in the data preprocessing, the intensities of each spectrum are normalized to compensate for the variation in total intensity. ```{r filter-data, cache=TRUE, fig.cap="**Data from the calculated FID filter. A)** Frequency shift induced by the filter calculated from the weighted mean frequency in Figure \\@ref(fig:fid-mean-ratio-ft)A. **B)** Variation in the total intensity of the spectrum after applying the filter."} par(mar=c(3.1,3.1,0.5,0.5), mgp=c(1.9, 0.6, 0), mfrow=c(2, 1), cex=1) plot(hours, filter_mean_freq, col=all_cols, pch=16, cex=0.75, xlab="Time (hours)", ylab="Filter Frequency (ppm) ") abline(h=0, col="gray") points(hours, filter_mean_freq, type="l") figure_letter("A") plot(hours, colSums(Re(filtered_mat)), col=all_cols, pch=16, cex=0.75, xlab="Time (hours)", ylab="Total Intensity") points(hours, colSums(Re(filtered_mat)), type="l") figure_letter("B") ``` A representative portion of the unfiltered and filtered spectra are shown in Figure \@ref(fig:unfiltered-filtered). The unfiltered data show different amounts of line broadening and frequency shifts that are largely removed in the filtered spectra. For instance, the peak at 3.2 ppm shows frequency shifts in the unfiltered data that go away upon filtering. The unfiltered frequency shifts obscure the appearance of right shoulders (that eventually surpass the initial intensity) on the set of three peaks around 2.94-2.90 ppm that is much clearer in the filtered spectra. ```{r unfiltered-filtered, cache=TRUE, fig.height=6, fig.cap="**Spectra before (A) and after (B) filtering with smoothed FID ratios (mean/individual).** Both sets of spectra have had total intensity normalization applied."} filtered_mat_norm <- t(t(filtered_mat)*Re(mean(filtered_mat)/colMeans(filtered_mat))) unfiltered_mat_norm <- t(t(unfiltered_mat)*Re(mean(unfiltered_mat)/colMeans(unfiltered_mat))) spec_ppm_1 <- as.vector(filtered_list[[1]]$ppm[[1]]) solvent_filter_1 <- -dnorm(spec_ppm_1, fid_list[[1]]$fheader["CAR",], 0.5) solvent_filter_1 <- solvent_filter_1-min(solvent_filter_1) solvent_filter_1 <- solvent_filter_1/max(solvent_filter_1) spec_xlim <- c(3.25,2.85) spec_ylim <- range(Re(unfiltered_mat_norm)[spec_ppm_1 < spec_xlim[1] & spec_ppm_1 > spec_xlim[2],]) par(mar=c(3.1,3.1,0.5,0.5), mgp=c(1.9, 0.6, 0), mfrow=c(2, 1), cex=1) plot(1, 1, type="n", xlim=spec_xlim, ylim=spec_ylim, xlab="1H (ppm)", ylab="Intensity")#, xaxt="n") #axis(1, axis_integer, TRUE) #axis(1, axis_tenths, FALSE, lwd.ticks=0.5, tcl=-0.25) abline(h=0, col="gray") for (i in seq_len(ncol(unfiltered_mat_norm))) { points(spec_ppm_1, Re(unfiltered_mat_norm[,i]), type="l", lwd=spec_lwd, col=all_cols[i]) } sf_plot_idx <- solvent_filter_1 < 0.9 points(spec_ppm_1[sf_plot_idx], (solvent_filter_1[sf_plot_idx]-1)*abs(par("usr")[3]), type="l", lwd=0.5) figure_letter("A") spec_ylim <- range(Re(filtered_mat_norm)[spec_ppm_1 < spec_xlim[1] & spec_ppm_1 > spec_xlim[2],]) plot(1, 1, type="n", xlim=spec_xlim, ylim=spec_ylim, xlab="1H (ppm)", ylab="Intensity")#, xaxt="n") #axis(1, axis_integer, TRUE) #axis(1, axis_tenths, FALSE, lwd.ticks=0.5, tcl=-0.25) abline(h=0, col="gray") for (i in seq_len(ncol(filtered_mat_norm))) { points(spec_ppm_1, Re(filtered_mat_norm[,i]), type="l", lwd=spec_lwd, col=all_cols[i]) } sf_plot_idx <- solvent_filter_1 < 0.9 points(spec_ppm_1[sf_plot_idx], (solvent_filter_1[sf_plot_idx]-1)*abs(par("usr")[3]), type="l", lwd=0.5) figure_letter("B") ``` # Rate Fitting Spectral regions can be excluded by creating a 2 x N matrix with pairs of ppm values between which data will be excluded from fitting. The excluded regions are shown in Figure \@ref(fig:exclude-regions). ```{r exclude-regions-matrix, echo=TRUE} # consecutive pairs of numbers give starting and stopping ppm values to exclude exclude_ppm <- matrix(c(12.4, 10, 0, -3.7), nrow=2) exclude_ppm ``` ```{r exclude-regions, fig.cap="**Spectral regions excluded from fitting.** Excluded regions are shown in gray between the boundaries shown in red. The y-axis limits are determined by either the whole spectrum **(A)** or just the non-excluded regions **(B)**. In this case both limits are the same."} ppm <- spec_ppm_1 int <- Re(filtered_mat_norm[,1]) exclude_idx <- logical(length(ppm)) for (i in seq_len(ncol(exclude_ppm))) { exclude_idx[ppm < max(exclude_ppm[,i]) & ppm > min(exclude_ppm[,i])] <- TRUE } int_excluded <- int int_excluded[!exclude_idx] <- NA int[exclude_idx] <- NA # line width for all spectra spec_lwd <- 0.33 # x-axis limits xlim <- rev(range(ppm, exclude_ppm)) par(mar=c(3.1,3.1,0.5,0.5), mgp=c(1.9, 0.6, 0), mfrow=c(2,1), cex=1) plot(ppm, int, type="l", lwd=spec_lwd, xlim=xlim, ylim=range(int, int_excluded, na.rm=TRUE), xaxt="n", xlab=expression(phantom()^1*H ~ (ppm)), ylab=NA) axis(1, axis_integer, TRUE) axis(1, axis_tenths, FALSE, lwd.ticks=0.5, tcl=-0.25) points(ppm, int_excluded, type="l", lwd=spec_lwd, col="gray", xlab=expression(phantom()^1*H ~ (ppm)), ylab=NA) abline(v=exclude_ppm, col="red", lwd=0.25) figure_letter("A") plot(ppm, int, type="l", lwd=spec_lwd, xlim=xlim, xaxt="n", xlab=expression(phantom()^1*H ~ (ppm)), ylab=NA) axis(1, axis_integer, TRUE) axis(1, axis_tenths, FALSE, lwd.ticks=0.5, tcl=-0.25) points(ppm, int_excluded, type="l", lwd=spec_lwd, col="gray", xlab=expression(phantom()^1*H ~ (ppm)), ylab=NA) abline(v=exclude_ppm, col="red", lwd=0.25) figure_letter("B") ``` The entire spectral time series is then fit to a two-state model with each spectrum consisting of a linear combination of two spectra whose intensities are also simultaneously fit. After that two-state fit, the population A components of each spectrum are fit to an exponential decay. This is shown in Figure \@ref(fig:update-populations). ```{r two-state-fitting, fig.height=8} #### Spectrum model fitting utility functions #### # convert a list of parameters to a vector (pop: a vector of state A populations, int: a list of two vectors with spectrum intensities) pack_int_param <- function(param_list) { int_mat <- sapply(param_list$int, function(x) x[!is.na(x)]) c(param_list$pop, int_mat) } # convert a vector of parameters to a list (pop: a vector of state A populations, int: a list of two vectors with spectrum intensities) unpack_int_param <- function(param_vec, template) { template$pop <- param_vec[seq_along(template$pop)] vec_offset <- length(template$pop) not_na_idx <- !is.na(template$int[[1]]) template$int[[1]][not_na_idx] <- param_vec[vec_offset+seq_len(sum(not_na_idx))] vec_offset <- vec_offset+sum(not_na_idx) not_na_idx <- !is.na(template$int[[2]]) template$int[[2]][not_na_idx] <- param_vec[vec_offset+seq_len(sum(not_na_idx))] template } # calculate the sum of squared differences between model and data lsq_fn <- function(par, template, spec_int_mat) { param_list <- unpack_int_param(par, template) i1 <- param_list$int[[1]] i2 <- param_list$int[[2]] p <- param_list$pop sim_int_mat <- i1 %o% p + i2 %o% (1-p) resid_mat <- spec_int_mat - sim_int_mat sum(resid_mat^2) } # calculate gradient of the sum of squared differences between model and data lsq_gr <- function(par, template, spec_int_mat) { param_list <- unpack_int_param(par, template) i1 <- param_list$int[[1]] i2 <- param_list$int[[2]] p <- param_list$pop sim_int_mat <- i1 %o% p + i2 %o% (1-p) resid_mat <- spec_int_mat - sim_int_mat deriv_list <- param_list # D[(d - (p*i1 + (1 - p)*i2))^2, {{p, i1, i2}}] deriv_list$pop <- colSums(2*(i2-i1)*resid_mat) deriv_list$int[[1]] <- colSums(-2*p*t(resid_mat)) deriv_list$int[[2]] <- colSums(-2*(1-p)*t(resid_mat)) pack_int_param(deriv_list) } #### Make Spectrum Fitting Input #### # create matrix of non-excluded spectrum intensities spec_int_mat <- Re(filtered_mat_norm[!exclude_idx,]) # normalize spectrum intensities so that the maximum intensity is 1 spec_int_mat <- spec_int_mat/max(abs(spec_int_mat)) fit_spec <- function(spec_int_mat, seconds) { # starting parameters in list (unpacked) format par_start_list <- list( pop = seq(1, 0, length.out=ncol(spec_int_mat)), int = list(spec_int_mat[,1], spec_int_mat[,ncol(spec_int_mat)]) ) # starting parameters in vector format par_start <- pack_int_param(par_start_list) # lower limits (A populations greater than or equal to 0) lower_list <- par_start_list lower_list$pop[] <- 0 lower_list$int[[1]][] <- -Inf lower_list$int[[2]][] <- -Inf lower <- pack_int_param(lower_list) # upper limits (A populations less than or equal to 0) upper_list <- par_start_list upper_list$pop[] <- 1 upper_list$int[[1]][] <- Inf upper_list$int[[2]][] <- Inf upper <- pack_int_param(upper_list) # optimize basis spectra and populations simultaneously optim_out <- optim(par_start, lsq_fn, lsq_gr, template=par_start_list, spec_int_mat=spec_int_mat, method="L-BFGS-B", lower=lower, upper=upper) # get list of optimized parameters par_optim_list <- unpack_int_param(optim_out$par, par_start_list) #### Analyze residuals #### sim_int_mat <- par_optim_list$int[[1]] %o% par_optim_list$pop + par_optim_list$int[[1]] %o% (1-par_optim_list$pop) resid_mat <- spec_int_mat-sim_int_mat #### Fit exponential decay #### # data for exponential decay fitting rate_data <- data.frame(seconds=seconds, pop=par_optim_list$pop) # formula for exponential decay exp_form <- pop ~ pop1*exp(-r1*seconds) + popf rate_starts <- exp(seq(log(1/rate_data$seconds[2]), log(1/max(rate_data$seconds)), length.out=3)) # fit the exponential decay rate_fit <- try(minpack.lm::nlsLM(exp_form, rate_data, c(popf=max(rate_data$pop), pop1=diff(range(rate_data$pop)), r1=rate_starts[2]), algorithm="port", lower=c(popf=-Inf, pop1=0, r1=0)), silent=TRUE) if (class(rate_fit) != "try-error") { # formula for multiexponential decay exp_form <- pop ~ pop1*exp(-r1*seconds) + pop2*exp(-r2*seconds) + popf rate_starts <- exp(seq(log(1/rate_data$seconds[2]), log(1/max(rate_data$seconds)), length.out=4)) rate_starts[2:3] <- rate_fit$m$getPars()["r1"]*c(2, 0.5) # fit the multiexponential decay fit_start <- c(popf=max(rate_data$pop), pop1=diff(range(rate_data$pop))/2, pop2=diff(range(rate_data$pop))/2, r1=rate_starts[2], r2=rate_starts[3]) fit_lower <- c(popf=-Inf, pop1=0, pop2=0, r1=unname(rate_fit$m$getPars()["r1"]), r2=0) fit_upper <- c(popf=Inf, pop1=Inf, pop2=Inf, r1=1/mean(diff(seconds)), r2=unname(rate_fit$m$getPars()["r1"])) rate_fit_multiexp <- try(minpack.lm::nlsLM(exp_form, rate_data, fit_start, algorithm="port", lower=fit_lower, upper=fit_upper), silent=TRUE) } else { rate_fit_multiexp <- rate_fit } list(par_optim_list=par_optim_list, rate_data=rate_data, rate_fit=rate_fit, rate_fit_multiexp=rate_fit_multiexp) } fit_spec_out <- fit_spec(spec_int_mat, seconds) par_optim_list <- fit_spec_out$par_optim_list rate_data <- fit_spec_out$rate_data rate_fit <- fit_spec_out$rate_fit #print(summary(rate_fit)) #### Plot results from fitting #### # x-axis limits xlim <- rev(range(ppm[!is.na(int)])) # positions of x-axis tick marks axis_integer <- seq(floor(min(xlim)), ceiling(max(xlim))) axis_tenths <- setdiff(seq(floor(min(xlim)*10),ceiling(max(xlim)*10))/10, axis_integer) # compute spectrum using the initial population fit by the exponential decay spec0 <- par_optim_list$int[[1]] if (class(rate_fit) != "try-error") { spec0 <- par_optim_list$int[[1]] * sum(rate_fit$m$getPars()[c("popf","pop1")]) + par_optim_list$int[[2]] * (1-sum(rate_fit$m$getPars()[c("popf","pop1")])) } # compute spectrum using the final population fit by the exponential decay specf <- par_optim_list$int[[2]] if (class(rate_fit) != "try-error") { specf <- par_optim_list$int[[1]] * rate_fit$m$getPars()["popf"] + par_optim_list$int[[2]] * (1-rate_fit$m$getPars()["popf"]) } # create a vector to map from the excluded intensities full_idx <- rep(NA_integer_, length(int)) full_idx[!exclude_idx] <- seq_along(spec0) if (FALSE) { par(mar=c(3.1,3.1,0.5,0.5), mgp=c(1.9, 0.6, 0), mfrow=c(4,1), cex=1) plot(ppm, spec_int_mat[full_idx,1], type="l", lwd=spec_lwd, xlim=xlim, ylim=range(spec_int_mat), col="blue", xaxt="n", xlab=expression(phantom()^1*H ~ (ppm)), ylab="Intensity") axis(1, axis_integer, TRUE) axis(1, axis_tenths, FALSE, lwd.ticks=0.5, tcl=-0.25) points(ppm, spec_int_mat[full_idx,ncol(spec_int_mat)], type="l", lwd=spec_lwd, col="purple") abline(h=0, col="gray", lwd=0.1) legend("topright", legend=c("First Spectrum", "Last Spectrum"), col=c("blue", "purple"), lwd=spec_lwd, bty="n") plot(ppm, spec0[full_idx], type="l", lwd=spec_lwd, xlim=xlim, ylim=range(spec_int_mat), col="blue", xaxt="n", xlab=expression(phantom()^1*H ~ (ppm)), ylab="Intensity") axis(1, axis_integer, TRUE) axis(1, axis_tenths, FALSE, lwd.ticks=0.5, tcl=-0.25) points(ppm, specf[full_idx], type="l", lwd=spec_lwd, col="red") abline(h=0, col="gray", lwd=0.1) legend("topright", legend=c("Modeled Initial Spectrum", "Modeled Final Spectrum"), col=c("blue", "red"), lwd=spec_lwd, bty="n") plot(ppm, specf[full_idx]-spec0[full_idx], type="l", lwd=spec_lwd, xlim=xlim, ylim=c(-0.1, 0.1)*5, xaxt="n", xlab=expression(phantom()^1*H ~ (ppm)), ylab="Delta Intensity") axis(1, axis_integer, TRUE) axis(1, axis_tenths, FALSE, lwd.ticks=0.5, tcl=-0.25) abline(h=0, col="gray", lwd=0.1) if (FALSE) { plot(ppm, apply(resid_mat, 1, median)[full_idx], type="l", lwd=spec_lwd, xlim=xlim, col="black", xaxt="n", xlab=expression(phantom()^1*H ~ (ppm)), ylab="Median Residual") axis(1, axis_integer, TRUE) axis(1, axis_tenths, FALSE, lwd.ticks=0.5, tcl=-0.25) abline(h=0, col="gray") } ylim <- range(rate_data$pop) if (class(rate_fit) != "try-error") { ylim <- range(rate_data$pop, rate_fit$m$getPars()[1:2]) } plot(rate_data, pch=16, ylim=ylim, xlab="Seconds", ylab="Fit Population A") if (class(rate_fit) != "try-error") { points(rate_data$seconds, rate_fit$m$predict(), type="l", col="purple") abline(h=sum(summary(rate_fit)$coef[c("popf","pop1"),"Estimate"]), col="blue") # didn't do any propagation of uncertainty here... abline(h=sum(summary(rate_fit)$coef[c("popf","pop1"),"Estimate"])+c(-1,1)*summary(rate_fit)$coef["pop1","Std. Error"], col="blue", lty="dashed") abline(h=summary(rate_fit)$coef["popf","Estimate"], col="red") abline(h=summary(rate_fit)$coef["popf","Estimate"]+c(-1,1)*summary(rate_fit)$coef["popf","Std. Error"], col="red", lty="dashed") } if (class(rate_fit) != "try-error") { legend("topright", legend=parse(text=sprintf("\"k = %0.2e\" ~ s^{-1}", rate_fit$m$getPars()[3])), col=c("purple"), lwd=spec_lwd, bty="n", title=" ") } } ``` ```{r update-populations, fig.height=8, fig.cap="**NMR spectra time series fit to a two-state model and then exponential decay.** **A)** First and last spectra in the time series. **B)** Modeled initial and final spectra. **C)** Differences in intensity between the modeled initial and final spectra. **D)** Expoenential fit to the population A component of the two-state fit."} pop_rss <- function(pop_A, spec, spec_A, spec_B) { #print(pop_A) sum(((pop_A*spec_A + (1-pop_A)*spec_B) - spec)^2) } pop_A_updated <- apply(spec_int_mat, 2, function(spec) { optimize(pop_rss, c(-1, 2), spec, spec0, specf)$minimum }) exp_form <- pop ~ pop1*exp(-r1*seconds) + popf exp_form_simp <- pop ~ pop1*exp(-r1*seconds) + popf rate_data_updated <- rate_data rate_data_updated$pop <- pop_A_updated rate_fit_updated <- minpack.lm::nlsLM(exp_form_simp, rate_data_updated, c(popf=max(rate_data_updated$pop), pop1=diff(range(rate_data_updated$pop)), r1=1/max(rate_data$seconds)), algorithm="port", lower=c(popf=-Inf, pop1=0, r1=0)) #rate_fit_updated <- nls(exp_form, rate_data_updated, c(popf=max(rate_data_updated$pop), pop1=diff(range(rate_data_updated$pop)), r1=(1/max(rate_data$seconds))))#, algorithm="port", lower=c(popf=-Inf, pop1=0, r1=0)) # unname(rate_fit$m$getPars()["r1"]) #stop() par(mar=c(3.1,3.1,0.5,0.5), mgp=c(1.9, 0.6, 0), mfrow=c(4,1), cex=1) plot(ppm, spec_int_mat[full_idx,1], type="l", lwd=spec_lwd, xlim=xlim, ylim=range(spec_int_mat), col="firebrick", xaxt="n", xlab=expression(phantom()^1*H ~ (ppm)), ylab="Intensity", yaxt="n") axis(2, seq(0, 1, by=0.5), tick=FALSE) axis(2, seq(0, 1, by=0.25), label=FALSE) axis(1, axis_integer, TRUE) axis(1, axis_tenths, FALSE, lwd.ticks=0.5, tcl=-0.25) points(ppm, spec_int_mat[full_idx,ncol(spec_int_mat)], type="l", lwd=spec_lwd, col="darkslateblue") abline(h=0, col="gray", lwd=0.1) legend("topleft", legend=c("First Spectrum", "Last Spectrum"), col=c("firebrick", "darkslateblue"), lwd=spec_lwd, bty="n") figure_letter("A") plot(ppm, spec0[full_idx], type="l", lwd=spec_lwd, xlim=xlim, ylim=range(spec_int_mat), col="red", xaxt="n", xlab=expression(phantom()^1*H ~ (ppm)), ylab="Intensity", yaxt="n") axis(2, seq(0, 1, by=0.5), tick=FALSE) axis(2, seq(0, 1, by=0.25), label=FALSE) axis(1, axis_integer, TRUE) axis(1, axis_tenths, FALSE, lwd.ticks=0.5, tcl=-0.25) points(ppm, specf[full_idx], type="l", lwd=spec_lwd, col="mediumslateblue") abline(h=0, col="gray", lwd=0.1) legend("topleft", legend=c("Modeled Initial Spectrum (Population A)", "Modeled Final Spectrum (Population B)"), col=c("red", "mediumslateblue"), lwd=spec_lwd, bty="n") figure_letter("B") plot(1, 1, type="n", xlim=xlim, ylim=max(abs(specf[full_idx]-spec0[full_idx]), na.rm=TRUE)*c(-1,1), xaxt="n", xlab=expression(phantom()^1*H ~ (ppm)), ylab="Delta Intensity", yaxt="n") axis(1, axis_integer, TRUE) axis(1, axis_tenths, FALSE, lwd.ticks=0.5, tcl=-0.25) #axis(2, seq(-1, 1, by=0.5), tick=FALSE) axis(2, seq(-1, 1, by=0.25), label=TRUE) abline(h=0, col="gray", lwd=0.1) points(ppm, specf[full_idx]-spec0[full_idx], type="l", lwd=spec_lwd) figure_letter("C") if (FALSE) { plot(ppm, apply(resid_mat, 1, median)[full_idx], type="l", lwd=spec_lwd, xlim=c(10,0), col="black", xaxt="n", xlab=expression(phantom()^1*H ~ (ppm)), ylab="Median Residual") axis(1, axis_integer, TRUE) axis(1, axis_tenths, FALSE, lwd.ticks=0.5, tcl=-0.25) abline(h=0, col="gray") } ylim <- c(0, 1) ylim <- range(summary(rate_fit_updated)$coef[c("popf","pop1"),"Estimate"]) #if (class(rate_fit) != "try-error") { # ylim <- range(rate_data$pop, rate_fit$m$getPars()[1:2]) #} plot(rate_data_updated, pch=16, ylim=ylim, xlab="Seconds", ylab="Fit Population A", yaxt="n") axis(2, seq(0, 1, by=0.5), tick=FALSE) axis(2, seq(0, 1, by=0.25), label=FALSE) #points(rate_data$seconds, exp(-rate_data$seconds*rate_fit$m$getPars()["r1"]), type="l", col="green") if (class(rate_fit) != "try-error") { points(rate_data$seconds, rate_fit_updated$m$predict(), type="l", col="purple", lwd=2) abline(h=sum(summary(rate_fit_updated)$coef[c("popf","pop1"),"Estimate"]), col="red") # didn't do any propagation of uncertainty here... abline(h=sum(summary(rate_fit_updated)$coef[c("popf","pop1"),"Estimate"])+c(-1,1)*summary(rate_fit_updated)$coef["pop1","Std. Error"], col="red", lty="dashed") abline(h=summary(rate_fit_updated)$coef["popf","Estimate"], col="mediumslateblue") abline(h=summary(rate_fit_updated)$coef["popf","Estimate"]+c(-1,1)*summary(rate_fit_updated)$coef["popf","Std. Error"], col="mediumslateblue", lty="dashed") } if (class(rate_fit) != "try-error") { legend("topright", legend=parse(text=sprintf("\"k = \"*%s ~ s^{-1}", sub("e(-?)0?", " %*% 10^\\1", sprintf("%.2e", rate_fit$m$getPars()[3])))), col=c("purple"), lwd=2, bty="n", title=" ") } figure_letter("D") ``` ```{r, include = FALSE} par(old_par) ```