From 6993cfb9c45e504a3f4f1099fef426b9fb39510b Mon Sep 17 00:00:00 2001 From: Jodie Li Date: Fri, 9 Oct 2026 01:52:11 -0500 Subject: [PATCH 1/6] Add plotting diagnostics and paper-inspired visualizations --- DESCRIPTION | 8 +- NAMESPACE | 6 + NEWS.md | 23 ++ R/plot-cv-profile.R | 81 +++++ R/plot-simulation.R | 120 +++++++ R/plot-spaces.R | 190 +++++++++++ R/plotting.R | 417 +++++++++++++++++++++++++ inst/examples/plot-diagnostics.R | 40 +++ inst/examples/plot-paper-displays.R | 64 ++++ inst/examples/plotting-guide.md | 97 ++++++ man/plot_contributions.Rd | 69 ++++ man/plot_cv.Rd | 115 +++++++ man/plot_cv_profile.Rd | 76 +++++ man/plot_objective.Rd | 45 +++ man/plot_simulation_results.Rd | 61 ++++ man/plot_source_spaces.Rd | 72 +++++ tests/testthat/helper-plotting.R | 15 + tests/testthat/test-cv-heatmap-style.R | 55 ++++ tests/testthat/test-paper-plots.R | 91 ++++++ tests/testthat/test-plotting.R | 144 +++++++++ 20 files changed, 1787 insertions(+), 2 deletions(-) create mode 100644 R/plot-cv-profile.R create mode 100644 R/plot-simulation.R create mode 100644 R/plot-spaces.R create mode 100644 R/plotting.R create mode 100644 inst/examples/plot-diagnostics.R create mode 100644 inst/examples/plot-paper-displays.R create mode 100644 inst/examples/plotting-guide.md create mode 100644 man/plot_contributions.Rd create mode 100644 man/plot_cv.Rd create mode 100644 man/plot_cv_profile.Rd create mode 100644 man/plot_objective.Rd create mode 100644 man/plot_simulation_results.Rd create mode 100644 man/plot_source_spaces.Rd create mode 100644 tests/testthat/helper-plotting.R create mode 100644 tests/testthat/test-cv-heatmap-style.R create mode 100644 tests/testthat/test-paper-plots.R create mode 100644 tests/testthat/test-plotting.R diff --git a/DESCRIPTION b/DESCRIPTION index de98bdc..4feae84 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -30,11 +30,15 @@ URL: https://github.com/stmcg/learner BugReports: https://github.com/stmcg/learner/issues Imports: ScreeNOT, Rcpp (>= 1.0.11), - RcppEigen + RcppEigen, + graphics, + grDevices, + stats, + ggplot2 (>= 3.4.0) LinkingTo: Rcpp, RcppEigen Suggests: testthat (>= 3.0.0) Config/testthat/edition: 3 Depends: - R (>= 2.10) + R (>= 3.6.0) Config/roxygen2/version: 8.1.0 diff --git a/NAMESPACE b/NAMESPACE index 1ab3843..2238f12 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -3,6 +3,12 @@ export(cv.learner) export(dlearner) export(learner) +export(plot_contributions) +export(plot_cv) +export(plot_cv_profile) +export(plot_objective) +export(plot_simulation_results) +export(plot_source_spaces) import(RcppEigen) importFrom(Rcpp,sourceCpp) useDynLib(learner, .registration = TRUE) diff --git a/NEWS.md b/NEWS.md index 56127db..c23e481 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,26 @@ +# learner (development) + +* Add `plot_objective()` for full and recent objective histories, with stopping + codes and an explicitly labeled recorded minimum. +* Add `plot_cv()` for common and separate penalty grids. The ggplot2 heatmaps + show relative error above the minimum using reversed magma colors, percentage + labels, and a black selection box. Separate panels share one legend. +* Report absolute SSE or MSE ranges, exact ties, and boundary selections. + Finite errors above an optional relative cutoff are labeled as high error, + never optimizer divergence. Zero minima use absolute scores instead. + Return ggplot objects for customization and `ggsave()` export. +* Add `plot_cv_profile()` for penalty sensitivity curves, including separate + row and column penalties and optional base-10 horizontal coordinates. +* Add `plot_source_spaces()` for population projector submatrices and + `plot_contributions()` for squared singular-vector contribution scores. +* Add `plot_simulation_results()` for replicate-level errors, with shared + scales and optional explicitly labeled SD or SE bars. +* Include reproducible plotting examples and automated tests. These plots do + not establish convergence, assign biological labels, or reproduce the + published experiments. +* Add ggplot2 as a plotting dependency and require R >= 3.6.0 for the color + palette functions used by the simulation plots. + # learner 1.1.0 * Integrate multiple-source support and separate space penalties from the diff --git a/R/plot-cv-profile.R b/R/plot-cv-profile.R new file mode 100644 index 0000000..cf2260e --- /dev/null +++ b/R/plot-cv-profile.R @@ -0,0 +1,81 @@ +#' Plot cross-validation sensitivity curves +#' +#' Compare validation scores across the common space penalty, or across the +#' row-space penalty at each fixed column-space penalty. Each line corresponds +#' to one balance penalty, making nearly overlapping curves visible. +#' +#' @inheritParams plot_cv +#' @return Invisibly, a list of absolute-score diagnostics: \code{scores}, +#' \code{scale}, \code{grids}, \code{best_index}, \code{shown_lambda_2_index}, +#' \code{color_limits}, \code{unavailable_count}, \code{log_labels}, +#' \code{score_range}, \code{minimum}, \code{exact_minima}, \code{boundary}, +#' \code{lambda_2_slice_range}, and \code{x_order} (horizontal drawing order). +#' This base-graphics function does not return ggplot objects or the relative +#' display fields returned by \code{plot_cv()}. +#' @details +#' Supply the exact original grids. The horizontal candidates are sorted only +#' for drawing lines; arrays and selected indices retain CV order. Duplicate +#' horizontal candidates are rejected. In separate mode there is one panel per +#' column-space penalty; \code{lambda_2_index} selects the balance-penalty lines. +#' At most four panels are drawn per page. With \code{log_labels=TRUE}, the +#' horizontal coordinate is the actual base-10 logarithm, not a rounded label. +#' All vertical limits use the complete score array. Non-finite values break +#' lines. The graph does not quantify uncertainty or establish that a parameter +#' has no effect; exact ties and boundary selections are reported separately. +#' @examples +#' set.seed(42) +#' cv <- cv.learner(dat_highsim$Y_source[1:12, 1:8], +#' dat_highsim$Y_target[1:12, 1:8], r = 2, +#' lambda_1_all = c(0.1, 1, 10), lambda_2_all = c(0.1, 1), +#' step_size = 0.0003, n_folds = 2, control = list(max_iter = 20)) +#' plot_cv_profile(cv, lambda_1_all = c(0.1, 1, 10), +#' lambda_2_all = c(0.1, 1), n_observed = 96, log_labels = TRUE) +#' @export +plot_cv_profile <- function(x, lambda_1_all = NULL, lambda_2_all = NULL, + lambda_1_row_all = NULL, lambda_1_col_all = NULL, + n_observed = NULL, lambda_2_index = NULL, log_labels = FALSE) { + d <- prepare_cv_plot(x, lambda_1_all, lambda_2_all, lambda_1_row_all, + lambda_1_col_all, n_observed, lambda_2_index, log_labels) + separate <- length(dim(d$scores)) == 3L + horizontal <- if (separate) d$grids$row else d$grids$common + if (anyDuplicated(horizontal)) stop('Profile horizontal candidates must be distinct.') + order_x <- order(horizontal) + horizontal <- horizontal[order_x] + if(log_labels) horizontal <- log10(horizontal) + balance <- if(separate) d$shown_lambda_2_index else seq_along(d$grids$balance) + panels <- if(separate) seq_along(d$grids$col) else 1L + colors <- grDevices::colorRampPalette(c('#2166AC','#1B9E77','#D95F02','#984EA3'))(length(d$grids$balance)) + limits <- d$color_limits + old <- graphics::par(no.readonly=TRUE);on.exit(graphics::par(old),add=TRUE) + for(page in split(panels,ceiling(seq_along(panels)/4))) { + nc <- min(2,length(page)) + graphics::par(mfrow=c(ceiling(length(page)/nc),nc),mar=c(3.8,4.5,2.5,7), + oma=c(4.2,0,0,0),mgp=c(2.5,0.6,0),cex.axis=0.9,cex.lab=0.95) + for(j in page) { + label <- if(separate) expression(lambda[1*','*plain(row)]) else expression(lambda[1]) + if(log_labels)label<-bquote(log[10](.(label[[1]]))) + title <- if(separate) bquote('CV profile at '~lambda[1*','*plain(col)] == .(d$grids$col[j])) else 'Cross-validation sensitivity' + graphics::plot(horizontal,rep(NA_real_,length(horizontal)),type='n',ylim=limits, + xlab=label,ylab=sub('Validation','Held-out',d$scale),main=title,cex.main=1.1) + graphics::grid(col='gray90',lty=1) + for(k in balance) { + y <- if(separate) d$scores[order_x,j,k] else d$scores[order_x,k] + y[!is.finite(y)]<-NA_real_ + graphics::lines(horizontal,y,type='b',pch=16,cex=0.55,col=colors[k],lty=1+(k-1)%%6,lwd=1.5) + } + if(!separate || (j==d$best_index[2] && d$best_index[3] %in% balance)) { + selected_x<-if(separate)d$grids$row[d$best_index[1]] else d$grids$common[d$best_index[1]] + if(log_labels)selected_x<-log10(selected_x) + graphics::points(selected_x,d$minimum,pch=4,cex=1.3,lwd=2) + } + usr <- graphics::par('usr') + graphics::legend(usr[2] + 0.06 * diff(usr[1:2]), usr[4], xjust=0, yjust=1, + xpd=NA,bty='n',title=expression(lambda[2]), + legend=format(d$grids$balance[balance],digits=3,trim=TRUE), + col=colors[balance],lty=1+(balance-1)%%6,pch=16,cex=0.8) + } + plot_cv_caption(d) + } + d$x_order<-order_x + invisible(d) +} diff --git a/R/plot-simulation.R b/R/plot-simulation.R new file mode 100644 index 0000000..da44a43 --- /dev/null +++ b/R/plot-simulation.R @@ -0,0 +1,120 @@ +#' Compare methods across simulation settings +#' +#' Plot replicate means by noise-variance ratio, with panels for each scenario +#' and rank. The default method colors and line types follow the simulation +#' figures in the LEARNER paper. This function summarizes supplied results; +#' it does not run simulations or reproduce the paper's experiments. +#' +#' @param data Data frame with columns \code{scenario}, \code{rank}, +#' \code{variance_ratio}, \code{method}, and \code{error}. Each row must be one +#' replicate result, not an already aggregated mean. Scenario and method are +#' nonempty character strings or factors; rank is a positive integer; the +#' variance ratio is positive; error is finite and nonnegative. Use the same +#' error definition across all methods and scenarios. For the paper's metric, +#' supply the Frobenius norm of estimated minus true signal. +#' @param uncertainty One of \code{"none"}, \code{"sd"}, or \code{"se"}. +#' Bars show one sample standard deviation or one standard error of the mean, +#' not a confidence interval. Bars are omitted for groups with one replicate. +#' @param main Overall title. +#' @param ylab Vertical label naming the supplied error metric. +#' @param xlab Horizontal label; defaults to the target/source noise variance ratio. +#' @return Invisibly, a data frame with grouping columns and \code{mean}, +#' \code{sd}, \code{se}, and \code{n}. Missing groups are not filled with zero. +#' @details +#' Scenarios and methods follow their first appearance in the data; ranks and +#' ratios are sorted numerically. At most two ranks and three scenarios are +#' drawn per page. All pages share vertical limits and method styles. Missing +#' method-by-ratio groups break curves. Canonical method names are +#' \code{"Target-only SVD"}, \code{"D-LEARNER"}, and \code{"LEARNER"}. +#' Other names are permitted and receive distinct styles. Replicate identifiers +#' are not inferred: callers must avoid duplicate records or mixing experiments. +#' @examples +#' results <- expand.grid(scenario = c("High similarity", "Low similarity"), +#' rank = 2, variance_ratio = c(1, 3, 5), +#' method = c("Target-only SVD", "D-LEARNER", "LEARNER"), +#' replicate = 1:2, stringsAsFactors = FALSE) +#' # Illustrative values only; replace with measured errors from simulations. +#' results$error <- 3 + 1 / results$variance_ratio + results$replicate / 10 +#' plot_simulation_results(results, main = "Illustrative input format") +#' @export +plot_simulation_results <- function(data, uncertainty=c('none','sd','se'), + main='Simulation comparison', ylab='Estimation error', + xlab=expression(sigma[0]^2/sigma[1]^2)) { + uncertainty<-match.arg(uncertainty) + required<-c('scenario','rank','variance_ratio','method','error') + if(!is.data.frame(data)||!all(required %in% names(data))||nrow(data)==0L) + stop('data must be a nonempty data frame with scenario, rank, variance_ratio, method, and error.') + data<-data[,required,drop=FALSE] + for(name in c('scenario','method')) { + if(!(is.character(data[[name]])||is.factor(data[[name]]))||anyNA(data[[name]])|| + any(!nzchar(trimws(as.character(data[[name]])))))stop(name,' must contain nonempty labels.') + data[[name]]<-as.character(data[[name]]) + } + for(name in c('rank','variance_ratio','error')) { + z<-data[[name]] + if(!is.numeric(z)||is.complex(z)||any(!is.finite(z)))stop(name,' must be finite and numeric.') + } + if(any(data$rank<1|data$rank!=floor(data$rank)))stop('rank must contain positive integers.') + if(any(data$variance_ratio<=0))stop('variance_ratio must be positive.') + if(any(data$error<0))stop('error must be nonnegative.') + # Integer codes avoid collisions when labels contain punctuation. + keys<-lapply(data[,required[1:4]],function(z)match(z,unique(z))) + groups<-split(seq_len(nrow(data)),interaction(keys,drop=TRUE)) + summary<-do.call(rbind,lapply(groups,function(i) { + first<-data[i[1],required[1:4],drop=FALSE] + n<-length(i);s<-if(n>1)stats::sd(data$error[i])else NA_real_ + cbind(first,mean=mean(data$error[i]),sd=s,se=s/sqrt(n),n=n) + })) + rownames(summary)<-NULL + scenarios<-unique(data$scenario);ranks<-sort(unique(data$rank));methods<-unique(data$method) + summary<-summary[order(match(summary$scenario,scenarios),summary$rank, + match(summary$method,methods),summary$variance_ratio),] + rownames(summary)<-NULL + canonical<-c('Target-only SVD','D-LEARNER','LEARNER') + colors<-stats::setNames(grDevices::hcl.colors(length(methods),'Dark 3'),methods) + types<-stats::setNames(1+(seq_along(methods)-1)%%6,methods) + for(j in seq_along(canonical))if(canonical[j] %in% methods) { + colors[canonical[j]]<-c('#E41A1C','#4DAF4A','#377EB8')[j] + types[canonical[j]]<-c(1,2,6)[j] + } + bar<-if(uncertainty=='none')rep(0,nrow(summary))else summary[[uncertainty]] + bar[is.na(bar)]<-0 + ylim<-c(min(0,summary$mean-bar),max(summary$mean+bar)) + if(diff(ylim)==0)ylim<-c(0,1) + ylim[2]<-ylim[2]+0.05*diff(ylim) + old<-graphics::par(no.readonly=TRUE);on.exit(graphics::par(old),add=TRUE) + for(sp in split(scenarios,ceiling(seq_along(scenarios)/3))) { + for(rp in split(ranks,ceiling(seq_along(ranks)/2))) { + graphics::par(mfrow=c(length(sp),length(rp)),mar=c(3.5,4,2.8,1), + oma=c(4.5,0,2.5,0),mgp=c(2.3,0.6,0),cex.axis=0.9,cex.lab=0.95) + for(sc in sp)for(rk in rp) { + panel<-summary[summary$scenario==sc & summary$rank==rk,,drop=FALSE] + ratios<-sort(unique(data$variance_ratio)) + graphics::plot(range(ratios),ylim,type='n',ylim=ylim,xlab=xlab,ylab=ylab, + main=paste0(sc,' (Rank = ',rk,')'),cex.main=1.05) + graphics::grid(col='gray85',lty=1) + for(m in methods) { + z<-panel[panel$method==m,,drop=FALSE] + pos<-match(ratios,z$variance_ratio) + graphics::lines(ratios,z$mean[pos],type='b',pch=16,col=colors[m],lty=types[m],lwd=2) + if(uncertainty!='none') { + valid<-is.finite(z[[uncertainty]]) & z[[uncertainty]]>0 + if(any(valid))graphics::arrows(z$variance_ratio[valid],z$mean[valid]-z[[uncertainty]][valid], + z$variance_ratio[valid],z$mean[valid]+z[[uncertainty]][valid], + angle=90,code=3,length=0.035,col=colors[m]) + } + } + } + # A full-device overlay places one shared legend beneath the panel grid. + graphics::par(fig=c(0,1,0,1),new=TRUE,mar=rep(0,4),oma=rep(0,4),cex=1) + graphics::plot.new();graphics::plot.window(c(0,1),c(0,1),xaxs='i',yaxs='i') + graphics::legend('bottom',inset=0.022,legend=methods,col=colors,lty=types,pch=16, + ncol=min(3,length(methods)),bty='n',cex=0.9,xpd=NA) + label<-if(uncertainty=='none') 'Replicate means; no uncertainty bars.' else + paste0('Replicate means; bars: +/- 1 ',toupper(uncertainty),'. No bars for n = 1.') + graphics::text(0.5,0.012,label,cex=0.75,xpd=NA) + graphics::text(0.5,0.98,main,font=2,cex=1.05,xpd=NA) + } + } + invisible(summary) +} diff --git a/R/plot-spaces.R b/R/plot-spaces.R new file mode 100644 index 0000000..3d280f9 --- /dev/null +++ b/R/plot-spaces.R @@ -0,0 +1,190 @@ +# Internal helpers shared by the space and contribution plots. +check_plot_matrix <- function(x, name) { + if (!is.matrix(x) || !is.numeric(x) || is.complex(x) || any(dim(x)==0) || any(!is.finite(x))) { + stop(name, ' must be a complete finite real numeric matrix.') + } +} + +plot_indices <- function(index, n, max_display, name) { + if(is.null(index)) return(unique(as.integer(round(seq(1,n,length.out=min(n,max_display)))))) + if(!is.numeric(index)||is.complex(index)||length(index)==0||any(!is.finite(index))|| + any(index!=floor(index))||any(index<1|index>n)||anyDuplicated(index)) { + stop(name, ' must contain distinct valid integer indices.') + } + as.integer(index) +} + +plot_svd <- function(x, r) { + s<-svd(x,nu=r,nv=r) + if(s$d[r] <= max(dim(x))*s$d[1]*.Machine$double.eps) { + stop('r includes numerically zero singular directions; use a smaller rank.') + } + s +} + +plot_limits_pair <- function(limits, automatic) { + if(is.null(limits))return(automatic) + if(!is.numeric(limits)||is.complex(limits)||length(limits)!=2L|| + any(!is.finite(limits))||any(limits<=0))stop('limits must contain two finite positive values: row then column.') + stats::setNames(as.numeric(limits),c('row','column')) +} + +# Use a raster to avoid drawing hundreds of thousands of individual rectangles. +draw_matrix_panel <- function(values, x_index, y_index, limits, colors, title, xlab, ylab) { + nx<-nrow(values);ny<-ncol(values) + clipped<-pmax(limits[1],pmin(limits[2],values)) + bins<-1L+floor((clipped-limits[1])/diff(limits)*(length(colors)-1L)) + pixels<-matrix(colors[pmax(1L,pmin(length(colors),bins))],nx,ny) + graphics::plot.new() + # Reserve a constant fraction of the panel width for the color key. + graphics::plot.window(xlim=c(0.5,nx+0.5+nx*0.15),ylim=c(0.5,ny+0.5),xaxs='i',yaxs='i') + graphics::rasterImage(grDevices::as.raster(t(pixels[,ny:1,drop=FALSE])), + 0.5,0.5,nx+0.5,ny+0.5,interpolate=FALSE) + graphics::rect(0.5,0.5,nx+0.5,ny+0.5,border='gray40',lwd=0.7) + ix<-unique(as.integer(round(seq(1,nx,length.out=min(5,nx))))) + iy<-unique(as.integer(round(seq(1,ny,length.out=min(8,ny))))) + graphics::axis(1,at=ix,labels=x_index[ix],cex.axis=0.85) + graphics::axis(2,at=iy,labels=y_index[iy],las=1,cex.axis=0.85) + graphics::title(main=title,xlab=xlab,ylab=ylab,cex.main=1.05,cex.lab=0.9) + draw_color_key(nx+0.5+nx*0.05,nx+0.5+nx*0.075,0.5,ny+0.5,limits,colors) +} + +#' Compare target and source latent spaces +#' +#' Plot column-space and row-space projection submatrices for the target and +#' each source, following the layout of Figure 4 in the LEARNER paper. +#' +#' @param Y_target Complete finite target matrix, or a completed estimate. +#' Missing values are rejected; the function does not impute them. +#' @param Y_source Aligned complete source matrix or nonempty list of matrices. +#' @param r Positive integer giving the retained rank for every matrix. +#' @param row_index,column_index Optional distinct integer indices selecting +#' variables to display, in display order. The same indices are used in all +#' populations. SVD always uses the full matrices before subsetting. +#' @param max_display Maximum number of displayed variables on each side when +#' indices are omitted; default 200. Deterministic evenly spaced indices are +#' used, without changing the random-number state. +#' @param limits Optional two positive color limits, in row-side then column-side +#' order. Each side uses a symmetric scale about zero shared across populations. +#' Values beyond supplied limits are clipped for display and counts are returned. +#' @param row_label,column_label Axis labels. Use Variant index and Phenotype +#' index only when these accurately describe the input rows and columns. +#' @param target_name Panel label describing the supplied target matrix. +#' @return Invisibly, a list with \code{row_projectors}, \code{column_projectors}, +#' \code{row_index}, \code{column_index}, \code{limits}, and \code{clipped_count}. +#' Each projector is the displayed submatrix, computed without allocating the +#' full row-by-row projector. Its population name identifies the input. +#' @details +#' For an SVD basis B, the displayed matrix is a submatrix of B B-transpose. +#' Column-side panels appear on top, row-side panels below. Red denotes negative +#' values and blue positive values. Color scales are shared within each side, +#' including across pages, but can differ between the two sides. At most three +#' populations appear per page. Source names are taken from the input list when +#' supplied. No name-based alignment or matching of factors is performed. +#' These are individual population projectors, not the weighted source operator. +#' A submatrix need not itself be an idempotent projector. +#' Numerically zero retained singular directions are rejected. With repeated +#' singular values at the truncation boundary the retained space is not unique. +#' The input order must already encode any desired phenotype or genomic ordering. +#' @examples +#' a <- dat_highsim$Y_source[1:20,1:10] +#' y <- dat_highsim$Y_target[1:20,1:10] +#' plot_source_spaces(y, list(A=a, B=a*1.1), r=2, max_display=20) +#' @export +plot_source_spaces <- function(Y_target, Y_source, r, row_index=NULL, column_index=NULL, + max_display=200L, limits=NULL, row_label='Row index', + column_label='Column index', target_name='Target population') { + check_plot_matrix(Y_target,'Y_target') + sources<-prepare_sources(Y_source,Y_target,allow_missing_target=FALSE)$matrices + for(s in sources)check_plot_matrix(s,'Y_source') + r<-resolve_rank(r,Y_target);validate_count(max_display,'max_display') + ri<-plot_indices(row_index,nrow(Y_target),max_display,'row_index') + ci<-plot_indices(column_index,ncol(Y_target),max_display,'column_index') + labels<-names(sources) + if(is.null(labels))labels<-rep('',length(sources)) + labels[is.na(labels)]<-'' + labels<-ifelse(nzchar(labels),paste('Source',labels),paste('Source',seq_along(sources))) + populations<-c(list(Y_target),sources);names(populations)<-make.unique(c(target_name,labels)) + bases<-lapply(populations,plot_svd,r=r) + up<-lapply(bases,function(s)tcrossprod(s$u[ri,,drop=FALSE])) + vp<-lapply(bases,function(s)tcrossprod(s$v[ci,,drop=FALSE])) + auto<-c(row=max(vapply(up,function(p)max(abs(p)),numeric(1))), + column=max(vapply(vp,function(p)max(abs(p)),numeric(1)))) + auto[auto==0]<-1e-8 + cap<-plot_limits_pair(limits,auto) + counts<-list(row=vapply(up,function(p)sum(abs(p)>cap[1]),integer(1)), + column=vapply(vp,function(p)sum(abs(p)>cap[2]),integer(1))) + colors<-grDevices::colorRampPalette(c('firebrick3','white','dodgerblue3'))(101) + old<-graphics::par(no.readonly=TRUE);on.exit(graphics::par(old),add=TRUE) + for(page in split(seq_along(populations),ceiling(seq_along(populations)/3))) { + graphics::par(mfrow=c(2,length(page)),mar=c(3.5,3.8,2.5,4),oma=c(2,0,0,0),mgp=c(2.2,0.6,0)) + for(i in page)draw_matrix_panel(vp[[i]],ci,ci,c(-cap[2],cap[2]),colors,names(populations)[i],column_label,column_label) + for(i in page)draw_matrix_panel(up[[i]],ri,ri,c(-cap[1],cap[1]),colors,names(populations)[i],row_label,row_label) + caption<-paste('Red: negative; blue: positive. Shared scales within each row.', + if(sum(unlist(counts))>0)paste(sum(unlist(counts)),'displayed entries clipped across all panels.')else '') + graphics::mtext(caption,outer=TRUE,side=1,line=0.5,cex=0.8) + } + invisible(list(row_projectors=up,column_projectors=vp,row_index=ri,column_index=ci, + limits=cap,clipped_count=counts)) +} + +#' Plot variable contributions to latent components +#' +#' Show squared singular-vector entries for a fitted target matrix, following +#' Figure 5 of the LEARNER paper. Higher contribution scores are darker red. +#' +#' @param x A complete estimated matrix, a \code{learner()} result, or a +#' \code{dlearner()} result. No rotation of the singular vectors is performed. +#' @param r Number of components. Required for a matrix; defaults to the stored +#' rank for a fitting result. Numerically zero directions are rejected. +#' @inheritParams plot_source_spaces +#' @param main Title describing the input estimate. +#' @return Invisibly, a list with full \code{row_scores} and \code{column_scores}, +#' displayed \code{row_index} and \code{column_index}, \code{limits}, and +#' \code{clipped_count}. Score matrices have variables in rows and components +#' in columns; all values are returned without display clipping. +#' @details +#' An SVD of the estimated matrix supplies U and V. Row scores are U squared +#' elementwise; column scores are V squared elementwise. Scores sum to one per +#' component over all variables on the corresponding side. They are not squared +#' entries of the raw optimization factors, effect sizes, or correlations. +#' Subsetting is applied only for display and scores are not renormalized. +#' The left panel displays columns and the right panel rows. Color limits are +#' separate for the two sides and start at zero. Optional limits specify row +#' then column upper limits, with clipped-entry counts reported. +#' Signs do not affect squared scores, but rotations within repeated singular +#' subspaces can change component-specific scores. Components from different +#' populations are not automatically aligned or given biological labels. +#' @examples +#' fit <- dlearner(dat_highsim$Y_source,dat_highsim$Y_target,r=3) +#' plot_contributions(fit,max_display=50) +#' @export +plot_contributions <- function(x, r=NULL, row_index=NULL, column_index=NULL, + max_display=200L, limits=NULL, row_label='Row index', + column_label='Column index', main='Estimated target contributions') { + if(is.list(x)) { + estimate<-if(!is.null(x$learner_estimate))x$learner_estimate else x$dlearner_estimate + if(is.null(r))r<-x$r + } else estimate<-x + check_plot_matrix(estimate,'x') + if(is.null(r))stop('Supply r for a matrix without a stored rank.') + r<-resolve_rank(r,estimate);validate_count(max_display,'max_display') + ri<-plot_indices(row_index,nrow(estimate),max_display,'row_index') + ci<-plot_indices(column_index,ncol(estimate),max_display,'column_index') + s<-plot_svd(estimate,r) + row_scores<-s$u^2;col_scores<-s$v^2 + cap<-plot_limits_pair(limits,c(row=max(row_scores[ri,,drop=FALSE]),column=max(col_scores[ci,,drop=FALSE]))) + cap[cap==0]<-1e-8 + counts<-c(row=sum(row_scores[ri,,drop=FALSE]>cap[1]),column=sum(col_scores[ci,,drop=FALSE]>cap[2])) + colors<-grDevices::colorRampPalette(c('white','firebrick3'))(100) + old<-graphics::par(no.readonly=TRUE);on.exit(graphics::par(old),add=TRUE) + graphics::par(mfrow=c(1,2),mar=c(3.8,3.8,2.2,4),oma=c(2,0,2,0),mgp=c(2.2,0.6,0)) + draw_matrix_panel(col_scores[ci,,drop=FALSE],ci,seq_len(r),c(0,cap[2]),colors,'Column contributions',column_label,'Component') + draw_matrix_panel(row_scores[ri,,drop=FALSE],ri,seq_len(r),c(0,cap[1]),colors,'Row contributions',row_label,'Component') + graphics::mtext(main,outer=TRUE,side=3,line=0.4,font=2,cex=1.1) + caption<-paste('Squared singular-vector entries; separate color scales.', + if(sum(counts)>0)paste(sum(counts),'displayed entries clipped.')else '') + graphics::mtext(caption,outer=TRUE,side=1,line=0.4,cex=0.8) + invisible(list(row_scores=row_scores,column_scores=col_scores,row_index=ri,column_index=ci, + limits=cap,clipped_count=counts)) +} diff --git a/R/plotting.R b/R/plotting.R new file mode 100644 index 0000000..3ad2cfd --- /dev/null +++ b/R/plotting.R @@ -0,0 +1,417 @@ +#' Plot the recorded LEARNER objective history +#' +#' Inspect the objective values returned by \code{learner()}. Optionally show +#' the last updates beside the full history to reveal small oscillations. +#' +#' @param x A result returned by \code{learner()}. +#' @param last Optional positive integer specifying the number of final updates +#' to show in a second panel. \code{NULL} draws only the full history. +#' @param main Character scalar used as the title of the full-history panel. +#' +#' @return Invisibly, a list containing \code{iteration}, \code{objective}, +#' \code{last_indices}, \code{recorded_min_index}, and +#' \code{convergence_criterion}. Plotting does not modify \code{x}. +#' +#' @details +#' Update numbers start at one. The initialization objective is not stored in +#' \code{x$objective_values}. The marked minimum is therefore the lowest +#' \emph{recorded} objective, not necessarily the objective of the returned +#' estimate, which can retain the initialization. Panels use their own vertical +#' scales. A stopping code of one indicates the objective-change rule was met; +#' it does not establish a global optimum or stability of the factors. +#' +#' Non-finite recorded values are shown as red triangles at the top of a panel, +#' with a legend; their vertical positions do not represent objective values. +#' At least one recorded value must be finite. Graphics parameters are restored +#' on exit; this function sets its own panel arrangement. +#' +#' @examples +#' fit <- learner(dat_highsim$Y_source, dat_highsim$Y_target, +#' r = 3, lambda_1 = 1, lambda_2 = 1, step_size = 0.0003, +#' control = list(max_iter = 100)) +#' plot_objective(fit, last = 30) +#' +#' @export +plot_objective <- function(x, last = NULL, main = "Recorded objective history") { + if (!is.list(x) || !is.numeric(x$objective_values) || + is.complex(x$objective_values) || length(x$objective_values) == 0L || + !is.null(dim(x$objective_values))) { + stop('x must contain a nonempty numeric objective_values vector.') + } + objective <- x$objective_values + finite <- is.finite(objective) + if (!any(finite)) stop('No finite objective values are available to plot.') + if (!is.null(last)) validate_count(last, 'last') + if (!is.character(main) || length(main) != 1L || is.na(main)) { + stop('main must be a nonmissing character scalar.') + } + code <- x$convergence_criterion + if (!is.numeric(code) || length(code) != 1L || is.na(code) || + !(code %in% 1:3)) { + stop('x must contain a convergence_criterion of 1, 2, or 3.') + } + status <- c('Objective-change rule met', 'Iteration limit reached', + 'Excessive growth or numerical breakdown')[code] + indices <- seq_along(objective) + last_indices <- if (is.null(last)) integer() else + seq.int(max(1L, length(objective) - last + 1L), length(objective)) + safe <- objective + safe[!finite] <- Inf + minimum <- which.min(safe) + old <- graphics::par(no.readonly = TRUE) + on.exit(graphics::par(old), add = TRUE) + graphics::par(mfrow = c(1, if (is.null(last)) 1 else 2), + mar = c(4.5, 4.5, 3.5, 1), oma = c(2.5, 0, 1, 0)) + draw <- function(index, title) { + y <- objective[index] + available <- is.finite(y) + limits <- if (any(available)) range(y[available]) else range(objective[finite]) + if (diff(limits) == 0) { + pad <- max(abs(limits[1]) * 0.01, 1e-8) + limits <- limits + c(-pad, pad) + } + y[!available] <- NA_real_ + graphics::plot(index, y, type = if (sum(available) == 1) 'b' else 'l', + col = '#2166AC', lwd = 1.5, xlab = 'Iteration', + ylab = 'Objective value', main = title, ylim = limits) + if (minimum %in% index) { + graphics::points(minimum, objective[minimum], pch = 4, lwd = 2, + col = '#202020', cex = 1.2) + } + if (any(!available)) { + graphics::points(index[!available], rep(limits[2], sum(!available)), + pch = 17, col = '#B2182B') + graphics::legend('topright', 'Non-finite update (position only)', + pch = 17, col = '#B2182B', bty = 'n', cex = 0.7) + } + } + draw(indices, main) + if (!is.null(last)) draw(last_indices, paste('Last', length(last_indices), 'updates')) + graphics::mtext(paste0('Stop code ', code, ': ', status), outer = TRUE, + side = 3, line = -0.5, cex = 0.85) + graphics::mtext('X: minimum recorded objective; initialization is not included.', + outer = TRUE, side = 1, line = 0.7, cex = 0.8) + invisible(list(iteration = indices, objective = objective, + last_indices = last_indices, recorded_min_index = minimum, + convergence_criterion = code)) +} + +#' Plot the LEARNER cross-validation scores +#' +#' Draw a common-penalty heatmap or separate-penalty heatmaps, with a shared +#' color scale and a black box at the first minimum in R array order. +#' +#' @param x A result returned by \code{cv.learner()}. +#' @param lambda_1_all Common-penalty candidates in the exact order used for CV. +#' Required for a two-dimensional score array; omit for separate penalties. +#' @param lambda_2_all Balance-penalty candidates in the exact order used for CV. +#' @param lambda_1_row_all,lambda_1_col_all Separate-penalty candidates in the +#' exact order used for CV. Both are required for a three-dimensional array. +#' @param n_observed Optional positive integer giving the number of observed +#' target entries used in CV. If supplied, scores are divided by this count and +#' labeled Validation MSE. Otherwise the original sums are labeled Validation +#' SSE. Missing target entries must not be included in this count. +#' @param lambda_2_index Optional vector of distinct integer indices into +#' \code{lambda_2_all}, selecting panels to draw in separate-penalty mode. +#' \code{NULL} draws all panels. Not used in common-penalty mode. +#' +#' @param log_labels Logical scalar. If TRUE, display base-10 logarithms of +#' the candidates on the heatmap axes, as in the paper's plotting script. +#' All candidates on those axes must be strictly positive. Log labels are +#' rounded to two decimal places. Defaults to FALSE. +#' +#' @param relative_limit Optional positive relative increase cutoff, default 0.3 +#' (30 percent). Larger finite errors are gray and labeled above the cutoff, +#' never as diverged. NULL keeps all finite values in the color scale. +#' @param show_values Whether to print scores inside cells. Defaults to TRUE. +#' @param main Plot title. +#' +#' @return Invisibly, a list with \code{scores}, \code{scale}, \code{grids}, +#' \code{best_index}, \code{shown_lambda_2_index}, \code{color_limits}, and +#' \code{unavailable_count}, \code{log_labels}, \code{score_range}, \code{minimum}, +#' \code{exact_minima}, \code{boundary}, and \code{lambda_2_slice_range}. +#' Indices refer to the original candidate order. Boundary flags indicate a +#' selected numerical endpoint on a grid with at least two distinct values. +#' Also returns \code{display_data}, \code{display_limits}, \code{relative}, +#' \code{above_limit_count}, \code{plot} (the first ggplot page), and \code{plots} +#' (all ggplot pages). The original absolute score diagnostics are unchanged. +#' +#' @details +#' Existing CV results do not store the full candidate grids, so callers must +#' provide them. Lengths and the selected penalty values are checked, but these +#' checks cannot detect every incorrectly supplied grid. Use the original grids, +#' without sorting or removing duplicates. Axes are equally spaced candidate +#' positions labeled with actual values, or their base-10 logarithms when +#' \code{log_labels=TRUE}. This changes labels only, not scores, candidate order, +#' or cell spacing. The reversed magma palette uses pale colors for lower error +#' and dark colors for higher error. Cell labels show increases over the minimum. +#' +#' Common mode shows \code{lambda_1_all} on the horizontal axis and +#' \code{lambda_2_all} on the vertical axis. Separate mode shows the U-space +#' penalty horizontally and the V-space penalty vertically, with one panel for +#' each balance penalty. All panels share limits computed from the full score +#' array, even when only some panels are selected. Up to six panels are drawn +#' per page. Gray cells have non-finite scores and are excluded from selection. +#' Only the first minimum is marked; a panel need not contain a black box. +#' Captions report the score range, exact ties, and boundary selections. These are +#' descriptive diagnostics, not significance or convergence tests. +#' +#' The stored \code{mse_all} values are sums of held-out squared errors. MSE +#' conversion requires the correct \code{n_observed}; the function cannot infer +#' this count from the scores. Neither this plot nor CV certifies convergence +#' of the individual fits. No fitting is performed and \code{x} is unchanged. +#' Draws with ggplot2 without modifying base graphics parameters. Each page shares +#' one legend. A sufficiently large device is recommended for multiple panels. +#' Relative increases are undefined when the minimum is zero; the display then +#' uses absolute scores and ignores relative_limit. High relative error alone +#' never establishes optimizer divergence. Non-finite scores are unavailable. +#' Use ggplot2::ggsave(plot = result$plot, ...) to export the first page, or +#' export each member of result$plots for multiple pages. +#' +#' @examples +#' row_grid <- c(0.1, 1) +#' col_grid <- c(0.1, 2) +#' balance_grid <- c(0.1, 1) +#' target <- dat_highsim$Y_target[1:12, 1:8] +#' set.seed(42) +#' cv <- cv.learner(dat_highsim$Y_source[1:12, 1:8], target, +#' r = 2, lambda_1_row_all = row_grid, +#' lambda_1_col_all = col_grid, lambda_2_all = balance_grid, +#' step_size = 0.0003, n_folds = 2, +#' control = list(max_iter = 20)) +#' plot_cv(cv, lambda_1_row_all = row_grid, lambda_1_col_all = col_grid, +#' lambda_2_all = balance_grid, n_observed = sum(!is.na(target))) +#' +#' @export +plot_cv <- function(x, lambda_1_all = NULL, lambda_2_all = NULL, + lambda_1_row_all = NULL, lambda_1_col_all = NULL, + n_observed = NULL, lambda_2_index = NULL, log_labels = FALSE, + relative_limit = 0.3, show_values = TRUE, + main = 'Cross-validation error') { + d <- prepare_cv_plot(x, lambda_1_all, lambda_2_all, lambda_1_row_all, + lambda_1_col_all, n_observed, lambda_2_index, log_labels) + if (!is.null(relative_limit) && (!is.numeric(relative_limit) || + is.complex(relative_limit) || length(relative_limit) != 1L || + !is.finite(relative_limit) || relative_limit <= 0)) { + stop('relative_limit must be NULL or a finite positive number.') + } + if (!is.logical(show_values) || length(show_values) != 1L || is.na(show_values)) { + stop('show_values must be TRUE or FALSE.') + } + if (!is.character(main) || length(main) != 1L || is.na(main)) { + stop('main must be one nonmissing character string.') + } + separate <- length(dim(d$scores)) == 3L + indices <- as.data.frame(arrayInd(seq_along(d$scores), dim(d$scores))) + names(indices) <- if (separate) c('x_index', 'y_index', 'slice') else c('x_index', 'y_index') + if (!separate) indices$slice <- 1L + indices$score <- as.vector(d$scores) + indices$available <- is.finite(indices$score) + # Relative increases are undefined when the best score is zero. + relative <- d$minimum > 0 + indices$display <- if (relative) indices$score / d$minimum - 1 else indices$score + indices$above_limit <- indices$available & relative & !is.null(relative_limit) + if (!is.null(relative_limit)) indices$above_limit <- indices$above_limit & indices$display > relative_limit + 8 * .Machine$double.eps * max(1, relative_limit) + indices$fill_value <- indices$display + indices$fill_value[!indices$available | indices$above_limit] <- NA_real_ + finite_fill <- indices$fill_value[is.finite(indices$fill_value)] + upper <- max(finite_fill) + limits <- c(0, if (upper > 0) upper else if (relative) 0.01 else 1e-8) + # One global scale across all slices, even if only a subset is displayed. + label_digits <- if (limits[2] <= 0.05) 2L else 1L + indices$label <- if (relative) sprintf(paste0('%.', label_digits, 'f%%'), 100 * indices$display) else + formatC(indices$display, digits = 4, format = 'g') + indices$label[!indices$available] <- 'NA' + if (any(indices$above_limit)) indices$label[indices$above_limit] <- + paste0('>', format(100 * relative_limit, trim = TRUE), '%') + indices$text_color <- ifelse(is.finite(indices$fill_value) & + indices$fill_value > limits[2] * 0.55, 'white', '#202020') + best_slice <- if (separate) d$best_index[3] else 1L + indices$selected <- indices$x_index == d$best_index[1] & + indices$y_index == d$best_index[2] & indices$slice == best_slice + panels <- if (separate) d$shown_lambda_2_index else 1L + labels_x <- if (separate) d$grids$row else d$grids$common + labels_y <- if (separate) d$grids$col else d$grids$balance + xlab <- if (separate) expression(lambda[1*','*plain(row)]) else expression(lambda[1]) + ylab <- if (separate) expression(lambda[1*','*plain(col)]) else expression(lambda[2]) + if (log_labels) { + labels_x <- round(log10(labels_x), 2) + labels_y <- round(log10(labels_y), 2) + xlab <- bquote(log[10](.(xlab[[1]]))) + ylab <- bquote(log[10](.(ylab[[1]]))) + } + metric <- if (is.null(n_observed)) 'SSE' else 'MSE' + selected_values <- vapply(seq_along(d$grids), function(i) d$grids[[i]][d$best_index[i]], numeric(1)) + selected_names <- if (separate) c('row', 'col', 'balance') else c('lambda1', 'lambda2') + subtitle <- paste0('Selected ', paste(paste0(selected_names, ' = ', + trimws(formatC(selected_values, digits = 4, format = 'g'))), collapse = ', '), + ' | Minimum ', metric, ' = ', formatC(d$minimum, digits = 6, format = 'g')) + caption <- paste0('Black box: selected parameters. Cell labels rounded.\n', metric, ' range: ', + formatC(d$score_range[1], digits = 6, format = 'g'), ' to ', + formatC(d$score_range[2], digits = 6, format = 'g'), '.') + if (any(d$boundary)) caption <- paste0(caption, ' Selection on grid boundary.') + if (d$exact_minima > 1) caption <- paste0(caption, '\n', d$exact_minima, + ' exact minima; first in CV order selected.') + if (any(indices$above_limit)) caption <- paste0(caption, '\nGray >', + format(100 * relative_limit, trim = TRUE), '%: high error, not a divergence diagnosis.') + if (d$unavailable_count > 0) caption <- paste0(caption, '\nGray NA: unavailable score.') + if (!relative) caption <- paste0(caption, '\nMinimum is zero; colors and labels show absolute ', metric, '.') + breaks <- seq(0, limits[2], length.out = 5L) + key_labels <- if (relative) sprintf(paste0('%.', label_digits, 'f%%'), 100 * breaks) else + formatC(breaks, digits = 4, format = 'g') + # Declare aesthetic bindings for package checks; ggplot evaluates them in data. + x_index <- y_index <- fill_value <- label <- text_color <- NULL + plots <- list() + for (page in split(panels, ceiling(seq_along(panels) / 6))) { + data <- indices[indices$slice %in% page, , drop = FALSE] + facet_labels <- paste0('lambda[2] == ', format(d$grids$balance[page], digits = 4, trim = TRUE)) + data$panel <- factor(data$slice, levels = page, labels = facet_labels) + plot <- ggplot2::ggplot(data, ggplot2::aes(x = x_index, y = y_index, fill = fill_value)) + + ggplot2::geom_tile(color = 'white', linewidth = 0.35, width = 1, height = 1) + + ggplot2::scale_fill_viridis_c(option = 'magma', direction = -1, + na.value = 'grey85', limits = limits, breaks = breaks, labels = key_labels, + name = if (relative) paste0('CV ', metric, '\nabove minimum') else paste('CV', metric)) + + ggplot2::scale_x_continuous(breaks = seq_along(labels_x), labels = labels_x, + expand = ggplot2::expansion(mult = 0)) + + ggplot2::scale_y_continuous(breaks = seq_along(labels_y), labels = labels_y, + expand = ggplot2::expansion(mult = 0)) + + ggplot2::labs(title = main, subtitle = subtitle, x = xlab, y = ylab, caption = caption) + + ggplot2::theme_minimal(base_size = 11) + + ggplot2::theme(panel.grid = ggplot2::element_blank(), + plot.title = ggplot2::element_text(face = 'bold', size = 14), + plot.subtitle = ggplot2::element_text(size = 10, margin = ggplot2::margin(b = 10)), + plot.caption = ggplot2::element_text(size = 9, hjust = 0, margin = ggplot2::margin(t = 10)), + legend.title = ggplot2::element_text(size = 10), + legend.text = ggplot2::element_text(size = 9), + axis.text = ggplot2::element_text(color = '#303030', size = 10), + strip.text = ggplot2::element_text(size = 11), + plot.margin = ggplot2::margin(12, 18, 10, 12)) + if (show_values) plot <- plot + + ggplot2::geom_text(ggplot2::aes(label = label, color = text_color), size = 2.8) + + ggplot2::scale_color_identity() + plot <- plot + ggplot2::geom_tile(data = data[data$selected, , drop = FALSE], + fill = NA, color = 'black', linewidth = 0.9, width = 0.94, height = 0.94) + if (separate) plot <- plot + ggplot2::facet_wrap(~panel, ncol = min(3L, length(page)), + labeller = ggplot2::label_parsed) + print(plot) + plots[[length(plots) + 1L]] <- plot + } + d$display_data <- indices + d$display_limits <- limits + d$relative <- relative + d$above_limit_count <- sum(indices$above_limit) + d$plots <- plots + d$plot <- plots[[1L]] + invisible(d) +} + +prepare_cv_plot <- function(x, lambda_1_all = NULL, lambda_2_all = NULL, + lambda_1_row_all = NULL, lambda_1_col_all = NULL, + n_observed = NULL, lambda_2_index = NULL, log_labels = FALSE) { + if (!is.list(x) || !is.numeric(x$mse_all) || is.complex(x$mse_all) || + !(length(dim(x$mse_all)) %in% c(2L, 3L)) || any(dim(x$mse_all) < 1L)) { + stop('x must contain a nonempty two- or three-dimensional mse_all array.') + } + if (!is.logical(log_labels) || length(log_labels) != 1L || is.na(log_labels)) { + stop('log_labels must be TRUE or FALSE.') + } + scores <- x$mse_all + finite <- is.finite(scores) + if (!any(finite)) stop('No finite validation scores are available to plot.') + if (any(scores[finite] < 0)) stop('Validation squared-error scores cannot be negative.') + separate <- length(dim(scores)) == 3L + check_grid <- function(values, size, name) { + validate_penalty_grid(values, name) + if (!is.null(dim(values)) || length(values) != size) { + stop(name, ' must match its score-array dimension in the original CV order.') + } + } + if (separate) { + if (!is.null(lambda_1_all)) stop('Do not supply lambda_1_all for separate penalties.') + check_grid(lambda_1_row_all, dim(scores)[1], 'lambda_1_row_all') + check_grid(lambda_1_col_all, dim(scores)[2], 'lambda_1_col_all') + check_grid(lambda_2_all, dim(scores)[3], 'lambda_2_all') + grids <- list(row = lambda_1_row_all, col = lambda_1_col_all, + balance = lambda_2_all) + panels <- if (is.null(lambda_2_index)) seq_along(lambda_2_all) else lambda_2_index + if (!is.numeric(panels) || length(panels) == 0L || any(!is.finite(panels)) || + any(panels != floor(panels)) || any(panels < 1 | panels > length(lambda_2_all)) || + anyDuplicated(panels)) { + stop('lambda_2_index must contain distinct valid balance-grid indices.') + } + } else { + if (!is.null(lambda_1_row_all) || !is.null(lambda_1_col_all) || + !is.null(lambda_2_index)) stop('Separate-penalty arguments require a 3D score array.') + check_grid(lambda_1_all, dim(scores)[1], 'lambda_1_all') + check_grid(lambda_2_all, dim(scores)[2], 'lambda_2_all') + grids <- list(common = lambda_1_all, balance = lambda_2_all) + panels <- 1L + } + axis_grids <- if (separate) c(grids$row, grids$col) else c(grids$common, grids$balance) + if (log_labels && any(axis_grids <= 0)) { + stop('log_labels requires strictly positive candidates on both heatmap axes.') + } + selectable <- scores + selectable[!finite] <- Inf + best <- as.integer(arrayInd(which.min(selectable), dim(scores))) + expected <- if (separate) { + c(lambda_1_row_min = grids$row[best[1]], lambda_1_col_min = grids$col[best[2]], + lambda_2_min = grids$balance[best[3]]) + } else c(lambda_1_min = grids$common[best[1]], lambda_2_min = grids$balance[best[2]]) + for (name in names(expected)) { + if (!is.numeric(x[[name]]) || length(x[[name]]) != 1L || + !isTRUE(all.equal(unname(x[[name]]), unname(expected[[name]]), tolerance = 1e-12))) { + stop('Supplied grids do not match the selected penalties stored in x: ', name) + } + } + scale <- 'Validation SSE' + if (!is.null(n_observed)) { + validate_count(n_observed, 'n_observed', upper = Inf) + scores <- scores / n_observed + scale <- 'Validation MSE' + } + limits <- range(scores[finite]) + if (diff(limits) == 0) { + pad <- max(abs(limits[1]) * 0.01, 1e-8) + limits <- c(max(0, limits[1] - pad), limits[2] + pad) + } + score_range <- range(scores[finite]) + minimum <- min(scores[finite]) + boundary <- vapply(seq_along(grids), function(i) { + g <- grids[[i]] + length(unique(g)) > 1L && g[best[i]] %in% range(g) + }, logical(1)) + names(boundary) <- names(grids) + slice <- if (separate) scores[best[1], best[2], ] else scores[best[1], ] + list(scores = scores, scale = scale, grids = grids, best_index = best, + shown_lambda_2_index = if (separate) panels else NULL, + color_limits = limits, unavailable_count = sum(!finite), log_labels = log_labels, + score_range = score_range, minimum = minimum, exact_minima = sum(scores[finite] == minimum), + boundary = boundary, lambda_2_slice_range = range(slice[is.finite(slice)])) +} + +# Caption reports numerical facts rather than inferring a flat direction from color. +plot_cv_caption <- function(d) { + number <- function(z) formatC(z, digits = 6, format = 'fg', flag = '#') + relative <- if (d$minimum > 0) paste0(' (span ', formatC(100 * diff(d$score_range) / d$minimum, digits = 2, format = 'f'), '% of minimum)') else '' + line1 <- paste0('Range: ', number(d$score_range[1]), ' to ', number(d$score_range[2]), relative, + '; selected minimum = ', number(d$minimum), '.') + boundary <- names(d$boundary)[d$boundary] + line2 <- paste0(if(length(boundary)) paste0('Grid boundary: ', paste(boundary, collapse = ', '), '; ') else '', + 'lambda_2 slice span at selected space penalty: ', + formatC(diff(d$lambda_2_slice_range), digits = 3, format = 'g'), '.') + line3 <- if (d$exact_minima > 1) paste(d$exact_minima, 'exact minima; first in CV order selected.') else 'Unique minimum among evaluated candidates.' + line3 <- paste('X: selected parameters.', line3, + if(d$unavailable_count > 0) 'Gray: unavailable.' else '') + for (i in 1:3) graphics::mtext(c(line1,line2,line3)[i], outer = TRUE, + side = 1, line = 0.4 + (i-1)*1.1, cex = 0.78) +} + +# Five ticks include the numerical endpoints even for a narrow score range. +draw_color_key <- function(left, right, bottom, top, limits, colors) { + edges <- seq(bottom, top, length.out=length(colors)+1L) + graphics::rect(left,edges[-length(edges)],right,edges[-1],col=colors,border=NA) + ticks <- seq(limits[1],limits[2],length.out=5) + graphics::axis(4,pos=right,at=seq(bottom,top,length.out=5), + labels=formatC(ticks,format='g',digits=5),las=1,cex.axis=0.9,tck=-0.015) +} diff --git a/inst/examples/plot-diagnostics.R b/inst/examples/plot-diagnostics.R new file mode 100644 index 0000000..23031ab --- /dev/null +++ b/inst/examples/plot-diagnostics.R @@ -0,0 +1,40 @@ +# Run after installing the plotting development version of learner. +# Synthetic data demonstrate the interface, not statistical performance. +set.seed(803) +source_A <- learner::dat_highsim$Y_source[1:20, 1:10] +source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), 20, 10) +target <- learner::dat_highsim$Y_target[1:20, 1:10] +sources <- list(source_A, source_B) +weights <- c(0.7, 0.3) +row_grid <- c(0, 1, 10) +col_grid <- c(0, 1, 3) +balance_grid <- c(0.1, 1) + +set.seed(815) +cv <- learner::cv.learner( + sources, target, r = 2, source_weights = weights, + lambda_1_row_all = row_grid, lambda_1_col_all = col_grid, + lambda_2_all = balance_grid, step_size = 0.0003, n_folds = 3, + control = list(max_iter = 500)) +learner::plot_cv( + cv, lambda_1_row_all = row_grid, lambda_1_col_all = col_grid, + lambda_2_all = balance_grid, n_observed = sum(!is.na(target))) + +fit <- learner::learner( + sources, target, r = cv$r, source_weights = weights, + lambda_1_row = cv$lambda_1_row_min, lambda_1_col = cv$lambda_1_col_min, + lambda_2 = cv$lambda_2_min, step_size = 0.0003, + control = list(max_iter = 2000)) +learner::plot_objective(fit, last = 100) + +# Common-penalty results are supported as well. +set.seed(815) +common_grid <- c(0, 1, 10) +common_cv <- learner::cv.learner( + sources, target, r = 2, source_weights = weights, + lambda_1_all = common_grid, lambda_2_all = balance_grid, + step_size = 0.0003, n_folds = 3, control = list(max_iter = 500)) +learner::plot_cv(common_cv, lambda_1_all = common_grid, + lambda_2_all = balance_grid, n_observed = sum(!is.na(target))) + +# Inspect stopping codes as well as curves. Plotting does not prove convergence. diff --git a/inst/examples/plot-paper-displays.R b/inst/examples/plot-paper-displays.R new file mode 100644 index 0000000..30dc95e --- /dev/null +++ b/inst/examples/plot-paper-displays.R @@ -0,0 +1,64 @@ +# Paper-inspired displays using simulated data, not published study results. +# Run after loading the plotting development version of learner. +set.seed(803) +space_source_A <- learner::dat_highsim$Y_source +space_source_B <- space_source_A + matrix(rnorm(length(space_source_A), sd=0.3), + nrow(space_source_A), ncol(space_source_A)) +space_target <- learner::dat_highsim$Y_target +space_sources <- list(A=space_source_A, B=space_source_B) +learner::plot_source_spaces(space_target, space_sources, r=3, + limits=c(0.08,0.15), target_name='Target (simulated)') +space_fit <- learner::dlearner(space_sources,space_target,r=3,source_weights=c(0.7,0.3)) +learner::plot_contributions(space_fit, main='D-LEARNER contributions (simulated data)') + +# A small, reproducible simulation demonstrates the comparison-plot interface. +# Fixed penalties and only three replicates: this is not a performance study. +# The target matrix is reused across noise ratios within each replicate. +# The error is the Frobenius norm, not MSE or RMSE. +run_toy_simulation <- function() { + set.seed(910) + + p <- 24L; q <- 16L + ratios <- c(1,3,5,10) + results <- list(); index <- 0L + for (similarity in c('High similarity','Moderate similarity','Low similarity')) { + alignment <- switch(similarity,'High similarity'=0.98,'Moderate similarity'=0.75, + 'Low similarity'=0.35) + for (rank in c(2L,3L)) for (replicate in 1:3) { + u_all <- qr.Q(qr(matrix(rnorm(p*2*rank),p,2*rank))) + v_all <- qr.Q(qr(matrix(rnorm(q*2*rank),q,2*rank))) + u <- u_all[,seq_len(rank),drop=FALSE];v <- v_all[,seq_len(rank),drop=FALSE] + source_u <- alignment*u+sqrt(1-alignment^2)*u_all[,rank+seq_len(rank),drop=FALSE] + source_v <- alignment*v+sqrt(1-alignment^2)*v_all[,rank+seq_len(rank),drop=FALSE] + truth <- u %*% diag(seq(12,8,length.out=rank)) %*% t(v) + source_truth <- source_u %*% diag(seq(12,8,length.out=rank)) %*% t(source_v) + target <- truth+matrix(rnorm(p*q),p,q) + source_noise <- matrix(rnorm(p*q),p,q) + s <- svd(target,nu=rank,nv=rank) + baseline <- s$u %*% diag(s$d[seq_len(rank)]) %*% t(s$v) + for (ratio in ratios) { + source <- source_truth+source_noise/sqrt(ratio) + direct <- learner::dlearner(source,target,r=rank) + learned <- learner::learner(source,target,r=rank,lambda_1=2,lambda_2=0.1, + step_size=0.001,control=list(max_iter=10000)) + estimates <- list('Target-only SVD'=baseline,'D-LEARNER'=direct$dlearner_estimate, + 'LEARNER'=learned$learner_estimate) + for (method in names(estimates)) { + index <- index+1L + results[[index]] <- data.frame(scenario=similarity,rank=rank,variance_ratio=ratio, + method=method,replicate=replicate,error=norm(estimates[[method]]-truth,'F'), + stop_code=if(method=='LEARNER') learned$convergence_criterion else NA_integer_) + } + } + } + } + simulation_results <- do.call(rbind,results) + simulation_results +} +simulation_results <- run_toy_simulation() +learner::plot_simulation_results(simulation_results, + main='Toy simulation: fixed penalties, 3 replicates',ylab='Frobenius estimation error') +# Report optimizer stopping codes before interpreting method comparisons. +print(table(simulation_results$stop_code,useNA='no')) +# Error bars are optional; these are SEs, not confidence intervals. +# learner::plot_simulation_results(simulation_results, uncertainty='se') diff --git a/inst/examples/plotting-guide.md b/inst/examples/plotting-guide.md new file mode 100644 index 0000000..69e7dba --- /dev/null +++ b/inst/examples/plotting-guide.md @@ -0,0 +1,97 @@ +# Plotting guide + +These functions display existing fits or measured results. Plot appearance alone +cannot establish convergence or statistical superiority. + +## CV heatmaps and profiles + +```r +plot_cv(cv, lambda_1_all = common_grid, lambda_2_all = balance_grid, + n_observed = sum(!is.na(target)), log_labels = TRUE) +plot_cv_profile(cv, lambda_1_all = common_grid, lambda_2_all = balance_grid, + n_observed = sum(!is.na(target)), log_labels = TRUE) +``` + +Use the exact grids supplied to `cv.learner()`. With independent penalties, +replace `lambda_1_all` with `lambda_1_row_all` and `lambda_1_col_all`. +Heatmaps then have one panel per balance penalty; profiles have one panel per +column penalty and one line per balance penalty. Zero candidates require +`log_labels = FALSE`. Heatmap positions are equally spaced grid slots, whereas +profile positions use the numerical candidate values (or their base-10 logs). + +The CV object's `mse_all` contains pooled held-out squared-error sums. Supplying +the total number of observed target entries converts these to MSE. Without that +number the plots explicitly show SSE. Source entries are not in the denominator. +Heatmaps use a reversed magma palette: pale cells have lower error. Labels and +colors show percentage increases over the minimum, with a black selection box. +Finite errors more than 30% above the minimum are gray and labeled >30%, not +as optimizer divergence. Use `relative_limit = NULL` to color all finite errors +or `show_values = FALSE` to hide cell text. If the minimum is zero, the heatmap +uses absolute scores instead. Cell labels are rounded, so matching printed +percentages do not establish an exact tie. + +The plots report the score range, minimum, exact ties, and boundary selections. +A boundary minimum deserves investigation; a small change across a parameter +is not an exact tie or evidence that the parameter is irrelevant. + +For a customized export, capture the returned object: + +```r +result <- plot_cv(cv, lambda_1_all = common_grid, lambda_2_all = balance_grid) +ggplot2::ggsave("cv-heatmap.png", plot = result$plot, width = 8, height = 5.5, dpi = 200) +``` + +For multiple pages, `result$plots` contains every ggplot page. Inspect exported +images at the intended size to check text and legend placement. + +## Population latent spaces (paper Figure 4) + +```r +plot_source_spaces(target, list(A = source_A, B = source_B), r = 3) +``` + +For each population, compute an SVD and retain orthonormal bases B_U and B_V. +Plot B_U B_U^T for the row side and B_V B_V^T for the column side. These are +individual population projectors, not raw data matrices or the weighted average +used by the optimizer. Shared color limits within each side support comparisons. +Negative entries are red and positive entries blue. + +SVD uses full matrices before display subsetting. `row_index`, `column_index`, +or `max_display` limit plotting cost without changing the fitted spaces. All +populations must have aligned variables in the same order. Labels default to +Row index and Column index; use biological labels only when appropriate. + +## Variable contributions (paper Figure 5) + +```r +plot_contributions(fit, r = 3) +``` + +Recompute the SVD of the estimated signal. For component j, a row's score is +B_U[i,j]^2 and a column's score is B_V[i,j]^2. The scores sum to one per component +on each side over the full set of variables. They are not squared optimization +factors, which include scaling. The plot uses separate color scales on the two +sides and reports display clipping if explicit limits are supplied. +These component-specific scores depend on basis choice in repeated singular +subspaces; the function does not match components or assign biological meaning. + +## Method comparisons (paper-style simulation panels) + +```r +plot_simulation_results(results, uncertainty = "none", + ylab = "Frobenius estimation error") +``` + +Supply one row per replicate and method, with `scenario`, `rank`, +`variance_ratio`, `method`, and `error`. For the paper's error definition, calculate +`norm(estimate - truth, "F")`. Use the same metric throughout the input. +The function computes means, sample SDs, SEs, and counts and returns the summary +invisibly. Optional bars are one SD or SE, not confidence intervals. + +`plot-paper-displays.R` includes a small reproducible simulation with three +replicates, fixed penalties, and ranks 2 and 3. It is an interface demonstration, +not a reproduction of the paper or a tuned comparison of methods. It prints +LEARNER stopping codes; code 1 means the current stopping rule was met, not that +a global optimum has been established. For a formal study, choose and validate +optimization and tuning procedures and use enough replications to assess +uncertainty before drawing performance conclusions. diff --git a/man/plot_contributions.Rd b/man/plot_contributions.Rd new file mode 100644 index 0000000..d389ecf --- /dev/null +++ b/man/plot_contributions.Rd @@ -0,0 +1,69 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-spaces.R +\name{plot_contributions} +\alias{plot_contributions} +\title{Plot variable contributions to latent components} +\usage{ +plot_contributions( + x, + r = NULL, + row_index = NULL, + column_index = NULL, + max_display = 200L, + limits = NULL, + row_label = "Row index", + column_label = "Column index", + main = "Estimated target contributions" +) +} +\arguments{ +\item{x}{A complete estimated matrix, a \code{learner()} result, or a +\code{dlearner()} result. No rotation of the singular vectors is performed.} + +\item{r}{Number of components. Required for a matrix; defaults to the stored +rank for a fitting result. Numerically zero directions are rejected.} + +\item{row_index, column_index}{Optional distinct integer indices selecting +variables to display, in display order. The same indices are used in all +populations. SVD always uses the full matrices before subsetting.} + +\item{max_display}{Maximum number of displayed variables on each side when +indices are omitted; default 200. Deterministic evenly spaced indices are +used, without changing the random-number state.} + +\item{limits}{Optional two positive color limits, in row-side then column-side +order. Each side uses a symmetric scale about zero shared across populations. +Values beyond supplied limits are clipped for display and counts are returned.} + +\item{row_label, column_label}{Axis labels. Use Variant index and Phenotype +index only when these accurately describe the input rows and columns.} + +\item{main}{Title describing the input estimate.} +} +\value{ +Invisibly, a list with full \code{row_scores} and \code{column_scores}, +displayed \code{row_index} and \code{column_index}, \code{limits}, and +\code{clipped_count}. Score matrices have variables in rows and components +in columns; all values are returned without display clipping. +} +\description{ +Show squared singular-vector entries for a fitted target matrix, following +Figure 5 of the LEARNER paper. Higher contribution scores are darker red. +} +\details{ +An SVD of the estimated matrix supplies U and V. Row scores are U squared +elementwise; column scores are V squared elementwise. Scores sum to one per +component over all variables on the corresponding side. They are not squared +entries of the raw optimization factors, effect sizes, or correlations. +Subsetting is applied only for display and scores are not renormalized. +The left panel displays columns and the right panel rows. Color limits are +separate for the two sides and start at zero. Optional limits specify row +then column upper limits, with clipped-entry counts reported. +Signs do not affect squared scores, but rotations within repeated singular +subspaces can change component-specific scores. Components from different +populations are not automatically aligned or given biological labels. +} +\examples{ +fit <- dlearner(dat_highsim$Y_source,dat_highsim$Y_target,r=3) +plot_contributions(fit,max_display=50) +} diff --git a/man/plot_cv.Rd b/man/plot_cv.Rd new file mode 100644 index 0000000..683c70d --- /dev/null +++ b/man/plot_cv.Rd @@ -0,0 +1,115 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plotting.R +\name{plot_cv} +\alias{plot_cv} +\title{Plot the LEARNER cross-validation scores} +\usage{ +plot_cv( + x, + lambda_1_all = NULL, + lambda_2_all = NULL, + lambda_1_row_all = NULL, + lambda_1_col_all = NULL, + n_observed = NULL, + lambda_2_index = NULL, + log_labels = FALSE, + relative_limit = 0.3, + show_values = TRUE, + main = "Cross-validation error" +) +} +\arguments{ +\item{x}{A result returned by \code{cv.learner()}.} + +\item{lambda_1_all}{Common-penalty candidates in the exact order used for CV. +Required for a two-dimensional score array; omit for separate penalties.} + +\item{lambda_2_all}{Balance-penalty candidates in the exact order used for CV.} + +\item{lambda_1_row_all, lambda_1_col_all}{Separate-penalty candidates in the +exact order used for CV. Both are required for a three-dimensional array.} + +\item{n_observed}{Optional positive integer giving the number of observed +target entries used in CV. If supplied, scores are divided by this count and +labeled Validation MSE. Otherwise the original sums are labeled Validation +SSE. Missing target entries must not be included in this count.} + +\item{lambda_2_index}{Optional vector of distinct integer indices into +\code{lambda_2_all}, selecting panels to draw in separate-penalty mode. +\code{NULL} draws all panels. Not used in common-penalty mode.} + +\item{log_labels}{Logical scalar. If TRUE, display base-10 logarithms of +the candidates on the heatmap axes, as in the paper's plotting script. +All candidates on those axes must be strictly positive. Log labels are +rounded to two decimal places. Defaults to FALSE.} + +\item{relative_limit}{Optional positive relative increase cutoff, default 0.3 +(30 percent). Larger finite errors are gray and labeled above the cutoff, +never as diverged. NULL keeps all finite values in the color scale.} + +\item{show_values}{Whether to print scores inside cells. Defaults to TRUE.} + +\item{main}{Plot title.} +} +\value{ +Invisibly, a list with \code{scores}, \code{scale}, \code{grids}, +\code{best_index}, \code{shown_lambda_2_index}, \code{color_limits}, and +\code{unavailable_count}, \code{log_labels}, \code{score_range}, \code{minimum}, +\code{exact_minima}, \code{boundary}, and \code{lambda_2_slice_range}. +Indices refer to the original candidate order. Boundary flags indicate a +selected numerical endpoint on a grid with at least two distinct values. +Also returns \code{display_data}, \code{display_limits}, \code{relative}, +\code{above_limit_count}, \code{plot} (the first ggplot page), and \code{plots} +(all ggplot pages). The original absolute score diagnostics are unchanged. +} +\description{ +Draw a common-penalty heatmap or separate-penalty heatmaps, with a shared +color scale and a black box at the first minimum in R array order. +} +\details{ +Existing CV results do not store the full candidate grids, so callers must +provide them. Lengths and the selected penalty values are checked, but these +checks cannot detect every incorrectly supplied grid. Use the original grids, +without sorting or removing duplicates. Axes are equally spaced candidate +positions labeled with actual values, or their base-10 logarithms when +\code{log_labels=TRUE}. This changes labels only, not scores, candidate order, +or cell spacing. The reversed magma palette uses pale colors for lower error +and dark colors for higher error. Cell labels show increases over the minimum. + +Common mode shows \code{lambda_1_all} on the horizontal axis and +\code{lambda_2_all} on the vertical axis. Separate mode shows the U-space +penalty horizontally and the V-space penalty vertically, with one panel for +each balance penalty. All panels share limits computed from the full score +array, even when only some panels are selected. Up to six panels are drawn +per page. Gray cells have non-finite scores and are excluded from selection. +Only the first minimum is marked; a panel need not contain a black box. +Captions report the score range, exact ties, and boundary selections. These are +descriptive diagnostics, not significance or convergence tests. + +The stored \code{mse_all} values are sums of held-out squared errors. MSE +conversion requires the correct \code{n_observed}; the function cannot infer +this count from the scores. Neither this plot nor CV certifies convergence +of the individual fits. No fitting is performed and \code{x} is unchanged. +Draws with ggplot2 without modifying base graphics parameters. Each page shares +one legend. A sufficiently large device is recommended for multiple panels. +Relative increases are undefined when the minimum is zero; the display then +uses absolute scores and ignores relative_limit. High relative error alone +never establishes optimizer divergence. Non-finite scores are unavailable. +Use ggplot2::ggsave(plot = result$plot, ...) to export the first page, or +export each member of result$plots for multiple pages. +} +\examples{ +row_grid <- c(0.1, 1) +col_grid <- c(0.1, 2) +balance_grid <- c(0.1, 1) +target <- dat_highsim$Y_target[1:12, 1:8] +set.seed(42) +cv <- cv.learner(dat_highsim$Y_source[1:12, 1:8], target, + r = 2, lambda_1_row_all = row_grid, + lambda_1_col_all = col_grid, lambda_2_all = balance_grid, + step_size = 0.0003, n_folds = 2, + control = list(max_iter = 20)) +plot_cv(cv, lambda_1_row_all = row_grid, lambda_1_col_all = col_grid, + lambda_2_all = balance_grid, n_observed = sum(!is.na(target))) + +} diff --git a/man/plot_cv_profile.Rd b/man/plot_cv_profile.Rd new file mode 100644 index 0000000..6818211 --- /dev/null +++ b/man/plot_cv_profile.Rd @@ -0,0 +1,76 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-cv-profile.R +\name{plot_cv_profile} +\alias{plot_cv_profile} +\title{Plot cross-validation sensitivity curves} +\usage{ +plot_cv_profile( + x, + lambda_1_all = NULL, + lambda_2_all = NULL, + lambda_1_row_all = NULL, + lambda_1_col_all = NULL, + n_observed = NULL, + lambda_2_index = NULL, + log_labels = FALSE +) +} +\arguments{ +\item{x}{A result returned by \code{cv.learner()}.} + +\item{lambda_1_all}{Common-penalty candidates in the exact order used for CV. +Required for a two-dimensional score array; omit for separate penalties.} + +\item{lambda_2_all}{Balance-penalty candidates in the exact order used for CV.} + +\item{lambda_1_row_all, lambda_1_col_all}{Separate-penalty candidates in the +exact order used for CV. Both are required for a three-dimensional array.} + +\item{n_observed}{Optional positive integer giving the number of observed +target entries used in CV. If supplied, scores are divided by this count and +labeled Validation MSE. Otherwise the original sums are labeled Validation +SSE. Missing target entries must not be included in this count.} + +\item{lambda_2_index}{Optional vector of distinct integer indices into +\code{lambda_2_all}, selecting panels to draw in separate-penalty mode. +\code{NULL} draws all panels. Not used in common-penalty mode.} + +\item{log_labels}{Logical scalar. If TRUE, display base-10 logarithms of +the candidates on the heatmap axes, as in the paper's plotting script. +All candidates on those axes must be strictly positive. Log labels are +rounded to two decimal places. Defaults to FALSE.} +} +\value{ +Invisibly, a list of absolute-score diagnostics: \code{scores}, +\code{scale}, \code{grids}, \code{best_index}, \code{shown_lambda_2_index}, +\code{color_limits}, \code{unavailable_count}, \code{log_labels}, +\code{score_range}, \code{minimum}, \code{exact_minima}, \code{boundary}, +\code{lambda_2_slice_range}, and \code{x_order} (horizontal drawing order). +This base-graphics function does not return ggplot objects or the relative +display fields returned by \code{plot_cv()}. +} +\description{ +Compare validation scores across the common space penalty, or across the +row-space penalty at each fixed column-space penalty. Each line corresponds +to one balance penalty, making nearly overlapping curves visible. +} +\details{ +Supply the exact original grids. The horizontal candidates are sorted only +for drawing lines; arrays and selected indices retain CV order. Duplicate +horizontal candidates are rejected. In separate mode there is one panel per +column-space penalty; \code{lambda_2_index} selects the balance-penalty lines. +At most four panels are drawn per page. With \code{log_labels=TRUE}, the +horizontal coordinate is the actual base-10 logarithm, not a rounded label. +All vertical limits use the complete score array. Non-finite values break +lines. The graph does not quantify uncertainty or establish that a parameter +has no effect; exact ties and boundary selections are reported separately. +} +\examples{ +set.seed(42) +cv <- cv.learner(dat_highsim$Y_source[1:12, 1:8], + dat_highsim$Y_target[1:12, 1:8], r = 2, + lambda_1_all = c(0.1, 1, 10), lambda_2_all = c(0.1, 1), + step_size = 0.0003, n_folds = 2, control = list(max_iter = 20)) +plot_cv_profile(cv, lambda_1_all = c(0.1, 1, 10), + lambda_2_all = c(0.1, 1), n_observed = 96, log_labels = TRUE) +} diff --git a/man/plot_objective.Rd b/man/plot_objective.Rd new file mode 100644 index 0000000..0ac992c --- /dev/null +++ b/man/plot_objective.Rd @@ -0,0 +1,45 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plotting.R +\name{plot_objective} +\alias{plot_objective} +\title{Plot the recorded LEARNER objective history} +\usage{ +plot_objective(x, last = NULL, main = "Recorded objective history") +} +\arguments{ +\item{x}{A result returned by \code{learner()}.} + +\item{last}{Optional positive integer specifying the number of final updates +to show in a second panel. \code{NULL} draws only the full history.} + +\item{main}{Character scalar used as the title of the full-history panel.} +} +\value{ +Invisibly, a list containing \code{iteration}, \code{objective}, +\code{last_indices}, \code{recorded_min_index}, and +\code{convergence_criterion}. Plotting does not modify \code{x}. +} +\description{ +Inspect the objective values returned by \code{learner()}. Optionally show +the last updates beside the full history to reveal small oscillations. +} +\details{ +Update numbers start at one. The initialization objective is not stored in +\code{x$objective_values}. The marked minimum is therefore the lowest +\emph{recorded} objective, not necessarily the objective of the returned +estimate, which can retain the initialization. Panels use their own vertical +scales. A stopping code of one indicates the objective-change rule was met; +it does not establish a global optimum or stability of the factors. + +Non-finite recorded values are shown as red triangles at the top of a panel, +with a legend; their vertical positions do not represent objective values. +At least one recorded value must be finite. Graphics parameters are restored +on exit; this function sets its own panel arrangement. +} +\examples{ +fit <- learner(dat_highsim$Y_source, dat_highsim$Y_target, + r = 3, lambda_1 = 1, lambda_2 = 1, step_size = 0.0003, + control = list(max_iter = 100)) +plot_objective(fit, last = 30) + +} diff --git a/man/plot_simulation_results.Rd b/man/plot_simulation_results.Rd new file mode 100644 index 0000000..56122f2 --- /dev/null +++ b/man/plot_simulation_results.Rd @@ -0,0 +1,61 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-simulation.R +\name{plot_simulation_results} +\alias{plot_simulation_results} +\title{Compare methods across simulation settings} +\usage{ +plot_simulation_results( + data, + uncertainty = c("none", "sd", "se"), + main = "Simulation comparison", + ylab = "Estimation error", + xlab = expression(sigma[0]^2/sigma[1]^2) +) +} +\arguments{ +\item{data}{Data frame with columns \code{scenario}, \code{rank}, +\code{variance_ratio}, \code{method}, and \code{error}. Each row must be one +replicate result, not an already aggregated mean. Scenario and method are +nonempty character strings or factors; rank is a positive integer; the +variance ratio is positive; error is finite and nonnegative. Use the same +error definition across all methods and scenarios. For the paper's metric, +supply the Frobenius norm of estimated minus true signal.} + +\item{uncertainty}{One of \code{"none"}, \code{"sd"}, or \code{"se"}. +Bars show one sample standard deviation or one standard error of the mean, +not a confidence interval. Bars are omitted for groups with one replicate.} + +\item{main}{Overall title.} + +\item{ylab}{Vertical label naming the supplied error metric.} + +\item{xlab}{Horizontal label; defaults to the target/source noise variance ratio.} +} +\value{ +Invisibly, a data frame with grouping columns and \code{mean}, +\code{sd}, \code{se}, and \code{n}. Missing groups are not filled with zero. +} +\description{ +Plot replicate means by noise-variance ratio, with panels for each scenario +and rank. The default method colors and line types follow the simulation +figures in the LEARNER paper. This function summarizes supplied results; +it does not run simulations or reproduce the paper's experiments. +} +\details{ +Scenarios and methods follow their first appearance in the data; ranks and +ratios are sorted numerically. At most two ranks and three scenarios are +drawn per page. All pages share vertical limits and method styles. Missing +method-by-ratio groups break curves. Canonical method names are +\code{"Target-only SVD"}, \code{"D-LEARNER"}, and \code{"LEARNER"}. +Other names are permitted and receive distinct styles. Replicate identifiers +are not inferred: callers must avoid duplicate records or mixing experiments. +} +\examples{ +results <- expand.grid(scenario = c("High similarity", "Low similarity"), + rank = 2, variance_ratio = c(1, 3, 5), + method = c("Target-only SVD", "D-LEARNER", "LEARNER"), + replicate = 1:2, stringsAsFactors = FALSE) +# Illustrative values only; replace with measured errors from simulations. +results$error <- 3 + 1 / results$variance_ratio + results$replicate / 10 +plot_simulation_results(results, main = "Illustrative input format") +} diff --git a/man/plot_source_spaces.Rd b/man/plot_source_spaces.Rd new file mode 100644 index 0000000..a0f23ab --- /dev/null +++ b/man/plot_source_spaces.Rd @@ -0,0 +1,72 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plot-spaces.R +\name{plot_source_spaces} +\alias{plot_source_spaces} +\title{Compare target and source latent spaces} +\usage{ +plot_source_spaces( + Y_target, + Y_source, + r, + row_index = NULL, + column_index = NULL, + max_display = 200L, + limits = NULL, + row_label = "Row index", + column_label = "Column index", + target_name = "Target population" +) +} +\arguments{ +\item{Y_target}{Complete finite target matrix, or a completed estimate. +Missing values are rejected; the function does not impute them.} + +\item{Y_source}{Aligned complete source matrix or nonempty list of matrices.} + +\item{r}{Positive integer giving the retained rank for every matrix.} + +\item{row_index, column_index}{Optional distinct integer indices selecting +variables to display, in display order. The same indices are used in all +populations. SVD always uses the full matrices before subsetting.} + +\item{max_display}{Maximum number of displayed variables on each side when +indices are omitted; default 200. Deterministic evenly spaced indices are +used, without changing the random-number state.} + +\item{limits}{Optional two positive color limits, in row-side then column-side +order. Each side uses a symmetric scale about zero shared across populations. +Values beyond supplied limits are clipped for display and counts are returned.} + +\item{row_label, column_label}{Axis labels. Use Variant index and Phenotype +index only when these accurately describe the input rows and columns.} + +\item{target_name}{Panel label describing the supplied target matrix.} +} +\value{ +Invisibly, a list with \code{row_projectors}, \code{column_projectors}, +\code{row_index}, \code{column_index}, \code{limits}, and \code{clipped_count}. +Each projector is the displayed submatrix, computed without allocating the +full row-by-row projector. Its population name identifies the input. +} +\description{ +Plot column-space and row-space projection submatrices for the target and +each source, following the layout of Figure 4 in the LEARNER paper. +} +\details{ +For an SVD basis B, the displayed matrix is a submatrix of B B-transpose. +Column-side panels appear on top, row-side panels below. Red denotes negative +values and blue positive values. Color scales are shared within each side, +including across pages, but can differ between the two sides. At most three +populations appear per page. Source names are taken from the input list when +supplied. No name-based alignment or matching of factors is performed. +These are individual population projectors, not the weighted source operator. +A submatrix need not itself be an idempotent projector. +Numerically zero retained singular directions are rejected. With repeated +singular values at the truncation boundary the retained space is not unique. +The input order must already encode any desired phenotype or genomic ordering. +} +\examples{ +a <- dat_highsim$Y_source[1:20,1:10] +y <- dat_highsim$Y_target[1:20,1:10] +plot_source_spaces(y, list(A=a, B=a*1.1), r=2, max_display=20) +} diff --git a/tests/testthat/helper-plotting.R b/tests/testthat/helper-plotting.R new file mode 100644 index 0000000..2e9ca4f --- /dev/null +++ b/tests/testthat/helper-plotting.R @@ -0,0 +1,15 @@ +# Devices are temporary and closed even when an assertion fails. +plot_device <- function() { + path <- tempfile(fileext = '.pdf') + grDevices::pdf(path, width = 10, height = 5) + path +} + +cv_plot_example <- function(separate = TRUE) { + if (separate) { + scores <- array(c(8, 7, 6, 5, 4, 3, 2, 1), c(2, 2, 2)) + list(mse_all = scores, lambda_1_min = NA_real_, lambda_1_row_min = 0, + lambda_1_col_min = 3, lambda_2_min = 1, r = 2) + } else list(mse_all = matrix(c(8, 7, 6, 5), 2, 2), + lambda_1_min = 0, lambda_2_min = 1, r = 2) +} diff --git a/tests/testthat/test-cv-heatmap-style.R b/tests/testthat/test-cv-heatmap-style.R new file mode 100644 index 0000000..6631e76 --- /dev/null +++ b/tests/testthat/test-cv-heatmap-style.R @@ -0,0 +1,55 @@ +test_that('relative heatmaps preserve score alignment and distinguish high error from missing', { + path <- plot_device() + on.exit({grDevices::dev.off(); unlink(path)}, add = TRUE) + x <- list(mse_all = matrix(c(100, 110, 140, NA_real_), 2), lambda_1_min = 1, lambda_2_min = 0.1) + args <- list(x = x, lambda_1_all = c(1, 2), lambda_2_all = c(.1, 1)) + z <- do.call(plot_cv, args) + expect_s3_class(z$plot, 'ggplot') + expect_equal(z$display_data$score, c(100, 110, 140, NA_real_)) + expect_equal(z$display_data$label, c('0.0%', '10.0%', '>30%', 'NA')) + expect_equal(z$display_data$selected, c(TRUE, FALSE, FALSE, FALSE)) + expect_equal(z$above_limit_count, 1) + expect_equal(z$unavailable_count, 1) + expect_equal(z$display_limits, c(0, .1)) + expect_match(z$plot$labels$caption, 'not a divergence diagnosis', fixed = TRUE) + mse <- do.call(plot_cv, c(args, list(n_observed = 10))) + expect_equal(mse$display_data$display, z$display_data$display) + expect_equal(mse$minimum, 10) + full <- do.call(plot_cv, c(args, list(relative_limit = NULL, show_values = FALSE))) + expect_equal(full$above_limit_count, 0) + expect_equal(full$display_limits, c(0, .4)) + expect_equal(length(full$plot$layers), 2) + expect_error(do.call(plot_cv, c(args, list(relative_limit = 0))), 'relative_limit') + expect_error(do.call(plot_cv, c(args, list(show_values = NA))), 'show_values') +}) + +test_that('zero minima use absolute scores and exact ties remain first in CV order', { + path <- plot_device() + on.exit({grDevices::dev.off(); unlink(path)}, add = TRUE) + x <- list(mse_all = matrix(c(0, 0, 2, 4), 2), lambda_1_min = 1, lambda_2_min = .1) + z <- plot_cv(x, lambda_1_all = c(1, 2), lambda_2_all = c(.1, 1)) + expect_false(z$relative) + expect_equal(z$above_limit_count, 0) + expect_equal(z$display_data$display, c(0, 0, 2, 4)) + expect_equal(z$exact_minima, 2) + expect_equal(which(z$display_data$selected), 1L) + expect_match(z$plot$labels$caption, 'absolute SSE', fixed = TRUE) + x$mse_all[] <- 0 + expect_gt(diff(plot_cv(x, lambda_1_all = c(1, 2), lambda_2_all = c(.1, 1))$display_limits), 0) +}) + +test_that('separate heatmaps share scales and return printable pages', { + path <- plot_device() + on.exit({grDevices::dev.off(); unlink(path)}, add = TRUE) + x <- list(mse_all = array(7:1, c(1, 1, 7)), lambda_1_row_min = 1, + lambda_1_col_min = 2, lambda_2_min = 7) + args <- list(x = x, lambda_1_row_all = 1, lambda_1_col_all = 2, lambda_2_all = 1:7, + relative_limit = NULL) + full <- do.call(plot_cv, args) + subset <- do.call(plot_cv, c(args, list(lambda_2_index = c(7, 2)))) + expect_length(full$plots, 2) + expect_equal(full$display_limits, subset$display_limits) + expect_equal(as.character(subset$plot$data$panel), c('lambda[2] == 2', 'lambda[2] == 7')) + expect_equal(levels(subset$plot$data$panel), c('lambda[2] == 7', 'lambda[2] == 2')) + expect_equal(sum(subset$plot$data$selected), 1) +}) diff --git a/tests/testthat/test-paper-plots.R b/tests/testthat/test-paper-plots.R new file mode 100644 index 0000000..3b438f6 --- /dev/null +++ b/tests/testthat/test-paper-plots.R @@ -0,0 +1,91 @@ +test_that('CV annotations distinguish small differences from exact ties and numeric boundaries', { + path<-plot_device();on.exit({grDevices::dev.off();unlink(path)},add=TRUE) + x<-list(mse_all=matrix(c(1.06,1.051,1.07,1.06,1.052,1.07),3), + lambda_1_min=50,lambda_2_min=0.01) + d<-plot_cv(x,lambda_1_all=c(10,50,100),lambda_2_all=c(0.01,1)) + expect_equal(d$exact_minima,1) + expect_equal(d$score_range,c(1.051,1.07)) + expect_equal(d$lambda_2_slice_range,c(1.051,1.052)) + expect_identical(d$boundary,c(common=FALSE,balance=TRUE)) + x$mse_all[2,2]<-1.051 + expect_equal(plot_cv(x,lambda_1_all=c(10,50,100),lambda_2_all=c(0.01,1))$exact_minima,2) + x$mse_all<-matrix(c(3,1,2),3);x$lambda_1_min<-1;x$lambda_2_min<-1 + expect_identical(plot_cv(x,lambda_1_all=c(3,1,2),lambda_2_all=1)$boundary, + c(common=TRUE,balance=FALSE)) +}) + +test_that('CV profiles sort horizontal values without reordering stored scores', { + path<-plot_device();on.exit({grDevices::dev.off();unlink(path)},add=TRUE) + old<-graphics::par(c('mfrow','mar')) + x<-list(mse_all=matrix(6:1,3),lambda_1_min=10,lambda_2_min=1) + d<-plot_cv_profile(x,lambda_1_all=c(100,1,10),lambda_2_all=c(.1,1),log_labels=TRUE) + expect_equal(d$x_order,c(2L,3L,1L));expect_equal(d$scores,x$mse_all) + expect_equal(d$best_index,c(3L,2L)) + expect_equal(graphics::par(c('mfrow','mar')),old) + expect_error(plot_cv_profile(x,lambda_1_all=c(10,1,10),lambda_2_all=c(.1,1)), 'distinct') + d<-plot_cv_profile(cv_plot_example(),lambda_1_row_all=c(10,0), + lambda_1_col_all=c(.2,3),lambda_2_all=c(0,1),lambda_2_index=2) + expect_equal(d$shown_lambda_2_index,2) + expect_equal(d$best_index,c(2L,2L,2L)) +}) + +test_that('space plots compute full-matrix spaces before taking common submatrices', { + path<-plot_device();on.exit({grDevices::dev.off();unlink(path)},add=TRUE) + # This rank-one matrix has analytically known normalized left/right vectors. + u<-c(1,2,3);v<-c(2,1);y<-outer(u,v) + set.seed(1);before<-.Random.seed + old<-graphics::par(c('mfrow','mar')) + d<-plot_source_spaces(y,list(A=y,B=2*y),r=1,row_index=c(3,1),column_index=2:1) + expect_equal(d$row_projectors[[1]],outer(u,u)[c(3,1),c(3,1)]/sum(u^2)) + expect_equal(d$column_projectors[[1]],outer(v,v)[2:1,2:1]/sum(v^2)) + expect_equal(d$row_projectors[[2]],d$row_projectors[[3]]) + expect_identical(.Random.seed,before) + expect_equal(graphics::par(c('mfrow','mar')),old) + expect_equal(d$limits,c(row=9/14,column=4/5)) + clipped<-plot_source_spaces(y,y,r=1,limits=c(.1,.1)) + expect_gt(sum(unlist(clipped$clipped_count)),0) + expect_equal(clipped$row_projectors[[1]],outer(u,u)/sum(u^2)) + expect_error(plot_source_spaces(y,y,r=2),'zero singular') + expect_error(plot_source_spaces(y,y,r=1,row_index=c(1,1)),'distinct') + expect_error(plot_source_spaces(y,y,r=1,limits=c(0,.1)),'positive') + y[1]<-NA + expect_error(plot_source_spaces(y,y,r=1),'complete finite') +}) + +test_that('contributions use squared normalized singular vectors, not raw factors', { + path<-plot_device();on.exit({grDevices::dev.off();unlink(path)},add=TRUE) + u<-c(1,2,3);v<-c(2,1);y<-outer(u,v) + d<-plot_contributions(y,r=1,row_index=c(3,1),column_index=2) + expect_equal(drop(d$row_scores),u^2/sum(u^2)) + expect_equal(drop(d$column_scores),v^2/sum(v^2)) + expect_equal(colSums(d$row_scores),1) + expect_equal(colSums(d$column_scores),1) + expect_equal(d$row_index,c(3L,1L)) + expect_equal(plot_contributions(-y,r=1)$row_scores,d$row_scores) + expect_equal(plot_contributions(list(learner_estimate=y,r=1))$column_scores,d$column_scores) + expect_equal(plot_contributions(list(dlearner_estimate=y,r=1))$row_scores,d$row_scores) + expect_error(plot_contributions(y),'Supply r') + expect_error(plot_contributions(y,r=2),'zero singular') + expect_error(plot_contributions(y,r=1,column_index=3),'valid integer') +}) + +test_that('simulation plots summarize replicate errors and preserve graphics state', { + path<-plot_device();on.exit({grDevices::dev.off();unlink(path)},add=TRUE) + d<-data.frame(scenario='High',rank=2,variance_ratio=c(1,1,3),method='LEARNER',error=c(2,4,1)) + old<-graphics::par(c('mfrow','mar'));original<-d + for(uncertainty in c('none','sd','se')) { + out<-plot_simulation_results(d,uncertainty=uncertainty) + expect_equal(out$mean,c(3,1));expect_equal(out$n,c(2L,1L)) + expect_equal(out$sd,c(sqrt(2),NA));expect_equal(out$se,c(1,NA)) + expect_equal(graphics::par(c('mfrow','mar')),old) + } + expect_identical(d,original) + d$error[1]<-NA;expect_error(plot_simulation_results(d),'finite') + d$error[1]<- -1;expect_error(plot_simulation_results(d),'nonnegative') + d$error[1]<-1;d$variance_ratio[1]<-0;expect_error(plot_simulation_results(d),'positive') + expect_error(plot_simulation_results(data.frame()),'nonempty') + # Empty scenario/rank combinations and pages must not synthesize zero errors. + d<-data.frame(scenario=rep(c('A','B','C','D'),each=3),rank=rep(1:3,4), + variance_ratio=1,method='Other',error=1:12) + expect_equal(nrow(plot_simulation_results(d)),12) +}) diff --git a/tests/testthat/test-plotting.R b/tests/testthat/test-plotting.R new file mode 100644 index 0000000..e418bfe --- /dev/null +++ b/tests/testthat/test-plotting.R @@ -0,0 +1,144 @@ +test_that('objective plots retain the update indices and restore graphics state', { + path <- plot_device() + on.exit({grDevices::dev.off(); unlink(path)}, add = TRUE) + graphics::par(mfrow = c(2, 1), mar = c(3, 3, 2, 1)) + old <- graphics::par(c('mfrow', 'mar')) + fit <- list(objective_values = c(9, 5, 6, 5.5), convergence_criterion = 2L) + original <- fit + result <- withVisible(plot_objective(fit, last = 2)) + expect_false(result$visible) + expect_equal(result$value$iteration, 1:4) + expect_equal(result$value$last_indices, 3:4) + expect_equal(result$value$recorded_min_index, 2L) + expect_identical(fit, original) + expect_equal(graphics::par(c('mfrow', 'mar')), old) + expect_equal(plot_objective(fit, last = 100)$last_indices, 1:4) +}) + +test_that('objective plots support singleton, flat, and failed histories', { + path <- plot_device() + on.exit({grDevices::dev.off(); unlink(path)}, add = TRUE) + for (values in list(4, c(4, 4, 4), c(4, Inf, NaN))) { + fit <- list(objective_values = values, convergence_criterion = 3L) + result <- plot_objective(fit, last = 1) + expect_equal(result$recorded_min_index, 1L) + expect_equal(result$objective, values) + } + expect_error(plot_objective(list(objective_values = c(Inf, NA))), 'No finite') + expect_error(plot_objective(list()), 'objective_values') + fit <- list(objective_values = 4, convergence_criterion = 1L) + expect_error(plot_objective(fit, last = 0), 'last') + expect_error(plot_objective(fit, last = 1.5), 'last') + expect_error(plot_objective(fit, main = NA_character_), 'main') + fit$convergence_criterion <- 9L + expect_error(plot_objective(fit), 'convergence_criterion') +}) + +test_that('CV plots retain candidate order, mark the global minimum, and scale SSE', { + path <- plot_device() + on.exit({grDevices::dev.off(); unlink(path)}, add = TRUE) + x <- cv_plot_example() + original <- x + old <- graphics::par(c('mfrow','mar')) + args <- list(x = x, lambda_1_row_all = c(10, 0), + lambda_1_col_all = c(0.2, 3), lambda_2_all = c(0, 1)) + sse <- do.call(plot_cv, args) + mse <- do.call(plot_cv, c(args, list(n_observed = 40))) + expect_identical(sse$grids$row, c(10, 0)) + expect_identical(sse$best_index, c(2L, 2L, 2L)) + expect_equal(sse$scores, x$mse_all) + expect_equal(mse$scores, x$mse_all / 40) + expect_equal(sse$scale, 'Validation SSE') + expect_equal(mse$scale, 'Validation MSE') + expect_equal(mse$color_limits, range(x$mse_all) / 40) + expect_identical(x, original) + expect_equal(graphics::par(c('mfrow','mar')), old) + subset <- do.call(plot_cv, c(args, list(lambda_2_index = 1))) + expect_equal(subset$color_limits, sse$color_limits) + expect_equal(subset$shown_lambda_2_index, 1) + expect_equal(subset$best_index, sse$best_index) + common <- plot_cv(cv_plot_example(FALSE), lambda_1_all = c(10, 0), lambda_2_all = c(0, 1)) + expect_identical(common$best_index, c(2L, 2L)) +}) + +test_that('CV plots handle singleton axes, duplicate candidates, ties and unavailable scores', { + path <- plot_device() + on.exit({grDevices::dev.off(); unlink(path)}, add = TRUE) + x <- list(mse_all = array(c(5, 5, Inf, NA), c(1, 2, 2)), + lambda_1_row_min = 0, lambda_1_col_min = 2, lambda_2_min = 1) + z <- plot_cv(x, lambda_1_row_all = 0, lambda_1_col_all = c(2, 2), + lambda_2_all = c(1, 10)) + expect_identical(z$best_index, c(1L, 1L, 1L)) + expect_equal(z$unavailable_count, 2) + expect_gt(diff(z$color_limits), 0) + x <- list(mse_all = matrix(0, 1, 1), lambda_1_min = 0, lambda_2_min = 0) + z <- plot_cv(x, lambda_1_all = 0, lambda_2_all = 0) + expect_identical(z$best_index, c(1L, 1L)) + expect_gt(diff(z$color_limits), 0) + # Seven balance slices exercise pagination without inventing new candidates. + x <- list(mse_all = array(7:1, c(1, 1, 7)), lambda_1_row_min = 0, + lambda_1_col_min = 2, lambda_2_min = 7) + z <- plot_cv(x, lambda_1_row_all = 0, lambda_1_col_all = 2, lambda_2_all = 1:7) + expect_equal(z$shown_lambda_2_index, 1:7) + expect_identical(z$best_index, c(1L, 1L, 7L)) +}) + +test_that('CV plots reject missing, mismatched, and ambiguous grid information', { + x <- cv_plot_example() + args <- list(x = x, lambda_1_row_all = c(10, 0), + lambda_1_col_all = c(0.2, 3), lambda_2_all = c(0, 1)) + expect_error(plot_cv(x), 'lambda_1_row_all') + bad <- args; bad$lambda_1_row_all <- c(0,10) + expect_error(do.call(plot_cv,bad), 'selected penalties') + bad <- args; bad$lambda_1_row_all <- 0 + expect_error(do.call(plot_cv,bad), 'score-array dimension') + expect_error(do.call(plot_cv,c(args,list(lambda_1_all=1))), 'Do not supply') + expect_error(do.call(plot_cv,c(args,list(n_observed=0))), 'n_observed') + expect_error(do.call(plot_cv,c(args,list(lambda_2_index=c(1,1)))), 'distinct') + expect_error(do.call(plot_cv,c(args,list(lambda_2_index=3))), 'distinct') + expect_error(do.call(plot_cv,c(args,list(lambda_2_index=NA_real_))), 'distinct') + x$mse_all[] <- Inf + expect_error(plot_cv(x), 'No finite') + x$mse_all[] <- -1 + expect_error(plot_cv(x), 'cannot be negative') + expect_error(plot_cv(list(mse_all=1:3)), 'two- or three-dimensional') +}) + +test_that('plot functions accept actual fits and both CV modes without changing outputs', { + path <- plot_device() + on.exit({grDevices::dev.off(); unlink(path)}, add = TRUE) + set.seed(623) + s <- matrix(rnorm(48), 8, 6) + t <- matrix(rnorm(48), 8, 6); t[c(1,3)] <- NA_real_ + fit <- learner(s,t,r=2,lambda_1=1,lambda_2=1,step_size=0.001, + control=list(max_iter=6)) + expect_equal(plot_objective(fit)$objective,fit$objective_values) + for (separate in c(FALSE,TRUE)) { + args <- list(Y_source=list(s,s*0.9),Y_target=t,r=2,step_size=0.001, + lambda_2_all=c(0,1),n_folds=2,control=list(max_iter=6)) + grid <- if(separate) list(lambda_1_row_all=c(0,1),lambda_1_col_all=c(0.5,2)) else list(lambda_1_all=c(0,1)) + cv <- do.call(cv.learner,c(args,grid)) + original <- cv + result <- do.call(plot_cv,c(list(x=cv,lambda_2_all=c(0,1),n_observed=sum(!is.na(t))),grid)) + expect_equal(result$scores,cv$mse_all/46) + expect_identical(cv,original) + } +}) + + +test_that('paper-style log labels preserve scores and candidate selection', { + path <- plot_device() + on.exit({grDevices::dev.off(); unlink(path)}, add = TRUE) + x <- list(mse_all = matrix(c(4, 3, 2, 1), 2, 2), + lambda_1_min = 100, lambda_2_min = 1) + raw <- plot_cv(x, lambda_1_all = c(10, 100), lambda_2_all = c(0.01, 1)) + log <- plot_cv(x, lambda_1_all = c(10, 100), lambda_2_all = c(0.01, 1), log_labels = TRUE) + expect_equal(log$scores, raw$scores) + expect_equal(log$best_index, raw$best_index) + expect_equal(log$grids, raw$grids) + expect_true(log$log_labels) + expect_error(plot_cv(x, lambda_1_all = c(0, 100), lambda_2_all = c(0.01, 1), + log_labels = TRUE), 'strictly positive') + expect_error(plot_cv(x, log_labels = NA), 'TRUE or FALSE') + expect_error(plot_cv(x, log_labels = 'yes'), 'TRUE or FALSE') +}) From e5c7fef2f40933c76025f087d4ce8e50ddbf18d1 Mon Sep 17 00:00:00 2001 From: Jodie Li Date: Fri, 9 Oct 2026 14:02:52 -0500 Subject: [PATCH 2/6] Keep CV heatmap panels distinct when penalty labels coincide --- R/plotting.R | 9 +++++++-- tests/testthat/test-cv-heatmap-style.R | 21 +++++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/R/plotting.R b/R/plotting.R index 99b2f83..5f652ad 100644 --- a/R/plotting.R +++ b/R/plotting.R @@ -279,7 +279,12 @@ plot_cv <- function(x, lambda_1_all = NULL, lambda_2_all = NULL, for (page in split(panels, ceiling(seq_along(panels) / 6))) { data <- indices[indices$slice %in% page, , drop = FALSE] facet_labels <- paste0('lambda[2] == ', format(d$grids$balance[page], digits = 4, trim = TRUE)) - data$panel <- factor(data$slice, levels = page, labels = facet_labels) + if (anyDuplicated(facet_labels)) { + facet_labels <- paste0('lambda[2] == ', format(d$grids$balance[page], digits = 17, trim = TRUE)) + } + # Grid indices identify panels; rounded display labels must not merge them. + data$panel <- factor(data$slice, levels = page) + panel_labels <- stats::setNames(facet_labels, as.character(page)) plot <- ggplot2::ggplot(data, ggplot2::aes(x = x_index, y = y_index, fill = fill_value)) + ggplot2::geom_tile(color = 'white', linewidth = 0.35, width = 1, height = 1) + ggplot2::scale_fill_viridis_c(option = 'magma', direction = -1, @@ -306,7 +311,7 @@ plot_cv <- function(x, lambda_1_all = NULL, lambda_2_all = NULL, plot <- plot + ggplot2::geom_tile(data = data[data$selected, , drop = FALSE], fill = NA, color = 'black', linewidth = 0.9, width = 0.94, height = 0.94) if (separate) plot <- plot + ggplot2::facet_wrap(~panel, ncol = min(3L, length(page)), - labeller = ggplot2::label_parsed) + labeller = ggplot2::as_labeller(panel_labels, default = ggplot2::label_parsed)) print(plot) plots[[length(plots) + 1L]] <- plot } diff --git a/tests/testthat/test-cv-heatmap-style.R b/tests/testthat/test-cv-heatmap-style.R index 26a765b..1c9e0e8 100644 --- a/tests/testthat/test-cv-heatmap-style.R +++ b/tests/testthat/test-cv-heatmap-style.R @@ -52,7 +52,24 @@ test_that('separate heatmaps share scales and return printable pages', { subset <- do.call(plot_cv, c(args, list(lambda_2_index = c(7, 2)))) expect_length(full$plots, 2) expect_equal(full$display_limits, subset$display_limits) - expect_equal(as.character(subset$plot$data$panel), c('lambda[2] == 2', 'lambda[2] == 7')) - expect_equal(levels(subset$plot$data$panel), c('lambda[2] == 7', 'lambda[2] == 2')) + expect_equal(as.character(subset$plot$data$panel), c('2', '7')) + expect_equal(levels(subset$plot$data$panel), c('7', '2')) expect_equal(sum(subset$plot$data$selected), 1) }) + + +test_that('close or repeated balance candidates remain distinct heatmap panels', { + testthat::skip_if_not_installed("ggplot2", minimum_version = "3.4.0") + path <- plot_device() + on.exit({grDevices::dev.off(); unlink(path)}, add = TRUE) + for (balance in list(c(1, 1.00001), c(1, 1))) { + x <- list(mse_all = array(1:8, c(2, 2, 2)), lambda_1_row_min = 1, + lambda_1_col_min = 1, lambda_2_min = 1) + result <- plot_cv(x, lambda_1_row_all = c(1, 2), + lambda_1_col_all = c(1, 2), lambda_2_all = balance) + built <- ggplot2::ggplot_build(result$plot) + expect_equal(nrow(built$layout$layout), 2L) + expect_equal(as.integer(table(built$data[[1]]$PANEL)), c(4L, 4L)) + expect_equal(as.integer(built$data[[3]]$PANEL), 1L) + } +}) From 3dccf2f10af04239048d01498b1c44b6e50ae9d4 Mon Sep 17 00:00:00 2001 From: Jodie Li Date: Thu, 15 Oct 2026 20:37:09 -0500 Subject: [PATCH 3/6] Add independent multiple-source example data and clarify method documentation --- NEWS.md | 50 +--- R/data-multisource.R | 43 ++++ R/dlearner.R | 97 +++++--- R/learner.R | 325 +++++++++++++------------ data-raw/gen-data.R | 35 +++ data/dat_multisource.rda | Bin 0 -> 212592 bytes inst/examples/multiple-sources.R | 21 +- man/cv.learner.Rd | 129 +++++----- man/dat_multisource.Rd | 54 ++++ man/dlearner.Rd | 91 ++++--- man/learner.Rd | 194 +++++++-------- tests/testthat/test-multisource-data.R | 31 +++ 12 files changed, 630 insertions(+), 440 deletions(-) create mode 100644 R/data-multisource.R create mode 100644 data/dat_multisource.rda create mode 100644 man/dat_multisource.Rd create mode 100644 tests/testthat/test-multisource-data.R diff --git a/NEWS.md b/NEWS.md index d22e243..e9ac59c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,49 +1,11 @@ ### learner version 1.1.0 (unreleased) -* Add `plot_objective()` for full and recent objective histories, with stopping - codes and an explicitly labeled recorded minimum. -* Add `plot_cv()` for common and separate penalty grids. The ggplot2 heatmaps - show relative error above the minimum using reversed magma colors, percentage - labels, and a black selection box. Separate panels share one legend. -* Report absolute SSE or MSE ranges, exact ties, and boundary selections. - Finite errors above an optional relative cutoff are labeled as high error, - never optimizer divergence. Zero minima use absolute scores instead. - Return ggplot objects for customization and `ggsave()` export. -* Add `plot_cv_profile()` for penalty sensitivity curves, including separate - row and column penalties and optional base-10 horizontal coordinates. -* Add `plot_source_spaces()` for population projector submatrices and - `plot_contributions()` for squared singular-vector contribution scores. -* Add `plot_simulation_results()` for replicate-level errors, with shared - scales and optional explicitly labeled SD or SE bars. -* Include reproducible plotting examples and automated tests. These plots do - not establish convergence, assign biological labels, or reproduce the - published experiments. -* Make ggplot2 an optional dependency used only by the CV heatmap. Other - plots and estimation functions remain available without it. - -* Integrate multiple-source support and separate space penalties from the - preliminary `learnerv2` 0.3.0 implementation into the package's R and C++ - implementation. -* `learner()`, `cv.learner()`, and `dlearner()` accept a list of aligned source - matrices and optional `source_weights`. Nonnegative weights are normalized - to sum to one and combine source projection operators. The common rank - interface is retained: automatic rank selection uses the first source, - and LEARNER initialization uses the first source even when rank is supplied. -* `learner()` accepts `lambda_1_row` and `lambda_1_col` to penalize the U and V - source-space residuals separately. The original `lambda_1` interface, - including positional calls, is retained. Equal separate coefficients reproduce - the common-penalty calculation under the same settings. -* `cv.learner()` accepts separate row and column penalty grids and returns a - three-dimensional validation-score array. Common-penalty calls retain their - previous output structure and score scale (summed held-out squared errors). -* Cache source decompositions during cross-validation and use C++ data in - parallel workers. Validate sources, weights, penalties, ranks, and fold counts. -* Expand argument documentation and examples for multiple sources, separate - penalties, cross-validation, missing data, and the current stopping rules. -* Expand automated tests for compatibility, gradients, multiple-source fitting, - cross-validation, and stopping behavior. Add fixed reference outputs from - `learnerv2` 0.3.0 to detect unintended numerical changes. -* Add Jodie Li as a package author. +* Add support for multiple source populations and optional source weights in + `learner()`, `dlearner()`, and `cv.learner()`. +* Add `dat_multisource`, an example dataset with one target and two source + populations with independently generated noise. +* Add separate row- and column-space penalties in `learner()` and + cross-validation over these penalties in `cv.learner()`. ### learner version 1.0.0 (2025-03-02) diff --git a/R/data-multisource.R b/R/data-multisource.R new file mode 100644 index 0000000..d98d4d9 --- /dev/null +++ b/R/data-multisource.R @@ -0,0 +1,43 @@ +#' Simulated data set: Multiple sources with different noise levels +#' +#' Simulated matrices for one target population and two source populations. +#' The sources share the target's latent row and column spaces, but source A +#' is observed more precisely than source B. This data set illustrates fitting +#' LEARNER and D-LEARNER with multiple sources. +#' +#' @docType data +#' @format A list with four components. All matrices have 100 rows and 50 columns, +#' with corresponding rows and columns aligned across populations. +#' \describe{ +#' \item{\code{Y_source}}{A named list of observed source matrices, \code{A} +#' and \code{B}, suitable for the \code{Y_source} argument.} +#' \item{\code{Y_target}}{The observed target matrix.} +#' \item{\code{Theta_source}}{A named list of the true rank-3 source matrices, +#' \code{A} and \code{B}. The two true matrices are identical.} +#' \item{\code{Theta_target}}{The true rank-3 target matrix.} +#' } +#' @details +#' The source signal is obtained by reversing the three nonzero singular values +#' of the target signal while keeping its singular vectors. Each observed matrix +#' is generated by adding independent, mean-zero normal noise to its own signal. +#' Noise is independent across entries and populations, with variances 0.10 for +#' the target, 0.01 for source A, and 0.50 for source B. Thus, source A has less +#' noise than the target and source B has more. Source B is not generated from +#' the observed source A matrix. +#' +#' The true signal matrices are included for illustration and estimation-error +#' calculations; they are not inputs to the fitting functions. These simulated +#' data are a usage example, not a reproduction of the paper's simulation study. +#' +#' @examples +#' data(dat_multisource) +#' fit <- dlearner( +#' Y_source = dat_multisource$Y_source, +#' Y_target = dat_multisource$Y_target, +#' r = 3, source_weights = c(0.7, 0.3) +#' ) +#' dim(fit$dlearner_estimate) +#' @seealso \code{\link{dat_highsim}}, \code{\link{dat_modsim}}, +#' \code{\link{learner}}, \code{\link{dlearner}} +#' @keywords datasets +"dat_multisource" diff --git a/R/dlearner.R b/R/dlearner.R index 7ce14aa..014d9ad 100644 --- a/R/dlearner.R +++ b/R/dlearner.R @@ -1,54 +1,79 @@ #' Direct projection transfer learning #' -#' Estimate a target signal matrix by applying source-space operators directly -#' to the target data, using the D-LEARNER method (McGrath et al. 2024). -#' One source matrix or a list of source matrices can be supplied. +#' @description +#' This function applies the Direct projection LatEnt spAce-based tRaNsfer +#' lEaRning (D-LEARNER) method (McGrath et al. 2024) to leverage data from a source +#' population to improve estimation of a low-rank matrix in an underrepresented +#' target population. #' -#' @param Y_target nonempty numeric target matrix with only finite values. -#' Unlike \code{learner()}, this function does not allow missing target entries. -#' @param r optional common rank of the retained source spaces. If omitted, ScreeNOT selects it from the first source. -#' @inheritParams learner -#' -#' @return A list with the following components: -#' \item{dlearner_estimate}{matrix containing the D-LEARNER estimate of the target population knowledge graph.} -#' \item{r}{rank value used.} +#' @param Y_source Matrix containing the source population data, with the same rows +#' and columns as \code{Y_target}. For multiple sources, supply a list of +#' matrices. +#' @param Y_target Matrix containing the target population data. Missing entries +#' are not allowed. +#' @param r Rank of the knowledge graphs. If omitted, ScreeNOT (Donoho et al. 2023) +#' is applied to the source population data to select the rank. +#' @param source_weights Relative weights of the sources, in the order of +#' \code{Y_source}. Default is equal weights. #' #' @details +#' \strong{Data and notation:} +#' +#' The data consists of a matrix in the target population +#' \eqn{Y_0 \in \mathbb{R}^{p \times q}} and the source population +#' \eqn{Y_1 \in \mathbb{R}^{p \times q}}. Let +#' \eqn{\hat U_k\hat\Lambda_k\hat V_k^\top} denote the rank-\eqn{r} truncated +#' singular value decomposition (SVD) of \eqn{Y_k}, \eqn{k = 0, 1}. +#' +#' For \eqn{k = 0, 1}, one can view \eqn{Y_k} as a noisy version of +#' \eqn{\Theta_k}, referred to as the knowledge graph. The target of inference +#' is the target population knowledge graph, \eqn{\Theta_0}. +#' +#' \strong{Estimation:} +#' +#' This method estimates \eqn{\Theta_0} by +#' +#' \deqn{\hat U_1\hat U_1^\top Y_0 \hat V_1\hat V_1^\top.} #' -#' Each source has the same dimensions as the target matrix \eqn{Y_0}. -#' Let \eqn{B_{U,k}} and \eqn{B_{V,k}} contain the first \eqn{r} left and -#' right singular vectors of source \eqn{k}. The estimate is -#' \deqn{\widehat\Theta_0 = \bar P_U Y_0 \bar P_V, \qquad -#' \bar P_U=\sum_k w_k B_{U,k}B_{U,k}^\top, \qquad -#' \bar P_V=\sum_k w_k B_{V,k}B_{V,k}^\top.} -#' The nonnegative weights are normalized to sum to one and default to equal -#' values. With one source this reduces to projection onto its retained left -#' and right singular spaces. With multiple sources, the averaged operators -#' need not be idempotent, and the estimate can have rank larger than \code{r}. -#' This is not an average of the raw source matrices or of separately computed -#' single-source D-LEARNER estimates. -#' -#' D-LEARNER does not iteratively fit target factors and has no space or balance -#' penalty parameters. All inputs must have aligned rows and columns and no -#' missing values. Names are not matched. If \code{r} is omitted, it is selected -#' from the first source, including when that source has zero weight. -#' Output dimension names are taken from the first source. +#' That is, the target population data are projected onto the latent row and +#' column spaces of the source population, without iterative fitting or penalty +#' selection. +#' +#' \strong{Multiple sources:} +#' +#' Supply a list of source matrices to combine information from several +#' populations. Their projection operators are averaged using +#' \code{source_weights}, and the estimate becomes +#' +#' \deqn{\widehat\Theta_0=\bar P_U Y_0\bar P_V,\qquad +#' \bar P_U=\sum_{k=1}^K w_k\hat U_k\hat U_k^\top,\qquad +#' \bar P_V=\sum_{k=1}^K w_k\hat V_k\hat V_k^\top.} +#' +#' where \eqn{\hat U_k} and \eqn{\hat V_k} come from the truncated SVD of +#' the \eqn{k}th source and the weights \eqn{w_k} are given by +#' \code{source_weights}. If \code{r} is not specified, the rank is selected +#' using the first source. +#' +#' @return +#' A list with the following components: +#' \item{dlearner_estimate}{matrix containing the D-LEARNER estimate of the target population knowledge graph.} +#' \item{r}{rank value used.} #' #' @references #' Donoho, D., Gavish, M. and Romanov, E. (2023). \emph{ScreeNOT: Exact MSE-optimal singular value thresholding in correlated noise}. The Annals of Statistics, 51(1), pp.122-148. #' +#' @seealso +#' \code{\link{learner}} for a target fit that can adapt the source +#' spaces through penalized estimation. +#' #' @examples #' res <- dlearner(Y_source = dat_highsim$Y_source, #' Y_target = dat_highsim$Y_target) #' -#' # A second simulated source, made by perturbing the first. -#' set.seed(803) -#' source_A <- dat_highsim$Y_source[1:20, 1:10] -#' source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), -#' nrow(source_A), ncol(source_A)) +#' # Multiple sources with fixed demonstration weights. #' res_multi <- dlearner( -#' Y_source = list(A = source_A, B = source_B), -#' Y_target = dat_highsim$Y_target[1:20, 1:10], r = 2, +#' Y_source = dat_multisource$Y_source, +#' Y_target = dat_multisource$Y_target, r = 3, #' source_weights = c(0.7, 0.3)) #' dim(res_multi$dlearner_estimate) #' diff --git a/R/learner.R b/R/learner.R index c22cc60..c668e7d 100644 --- a/R/learner.R +++ b/R/learner.R @@ -1,57 +1,71 @@ #' Cross-validation for LEARNER #' -#' Select common or separate source-space penalties and a balance penalty for -#' \code{\link{learner}} by k-fold cross-validation on observed target entries. -#' One source matrix or a list of source matrices can be supplied. -#' -#' @param lambda_1_all nonempty vector of finite nonnegative candidate common penalties. Use this or both separate grids, not both modes. -#' @param lambda_2_all nonempty vector of finite nonnegative candidate balance penalties. -#' @param lambda_1_row_all nonempty vector of finite nonnegative candidate penalties on U; requires \code{lambda_1_col_all}. -#' @param lambda_1_col_all nonempty vector of finite nonnegative candidate penalties on V; requires \code{lambda_1_row_all}. -#' @inheritParams learner -#' @param step_size numeric scalar specifying the step size for the scaled gradient steps in the numerical optimization algorithm, as in \code{\link{learner}} -#' @param n_folds integer from 2 to the number of observed target entries. The default is \code{4}. -#' @param n_cores positive integer specifying the requested number of OpenMP -#' threads. Candidate penalty combinations are evaluated in parallel when the -#' package is built with OpenMP support; otherwise they run sequentially. -#' The default is \code{1}. -#' @param control a list of parameters for controlling the stopping criteria for the numerical optimization algorithm, as in \code{\link{learner}}. -#' -#' @return A list with the following elements: -#' \item{lambda_1_min}{selected common penalty; \code{NA} in separate mode.} -#' \item{lambda_1_row_min, lambda_1_col_min}{selected separate penalties, returned only in separate mode.} -#' \item{lambda_2_min}{balance penalty in the combination with the smallest held-out squared-error sum.} -#' \item{mse_all}{held-out squared-error sums, retaining the historical scale despite the name. Common mode returns a matrix indexed by \code{lambda_1_all} and \code{lambda_2_all}. Separate mode returns a three-dimensional array indexed by \code{lambda_1_row_all}, \code{lambda_1_col_all}, and \code{lambda_2_all}, in that order.} -#' \item{r}{rank value used.} +#' @description +#' Performs k-fold cross-validation to select the source-space and balance +#' penalties for \code{\link{learner}}. Returns prediction errors and the +#' penalty combination with the smallest error. +#' +#' @param Y_source Source population data, as in \code{\link{learner}}. +#' Supply a matrix for one source or a list of matrices for multiple sources. +#' @param Y_target Target population data matrix, as in \code{\link{learner}}. +#' Only observed entries are used to evaluate predictions. +#' @param r Rank used in every fold, as in \code{\link{learner}}. It is not +#' selected by cross-validation. +#' @param lambda_1_all Candidate values for the common source-space penalty +#' \eqn{\lambda_1}. All combinations with \code{lambda_2_all} are evaluated. +#' @param lambda_2_all Candidate values for the balance penalty \eqn{\lambda_2}. +#' @param step_size Step size for each fit, as in \code{\link{learner}}. +#' @param n_folds Number of cross-validation folds. Default is \code{4}. +#' @param n_cores Number of CPU cores used to evaluate the candidate penalty +#' combinations in parallel. Default is \code{1}, i.e., no parallelization. +#' @param control Optimization settings for each fit; see \code{\link{learner}}. +#' @param lambda_1_row_all,lambda_1_col_all Candidate values for separate +#' penalties on the row and column factors. Supply both instead of +#' \code{lambda_1_all}; all combinations with \code{lambda_2_all} are evaluated. +#' @param source_weights Relative source weights, as in \code{\link{learner}}. The weights +#' are not selected by cross-validation. Default is equal weights. #' #' @details -#' Each observed target entry is held out once. The same folds are used for all -#' candidates. Dividing \code{mse_all} by the number of observed target entries -#' gives the mean squared error without changing the selected candidate. Ties -#' select the first minimum in R array order. Existing missing entries are -#' excluded from validation. Rank, source spaces, source weights, and the -#' first-source initialization are fixed across folds. The function tunes -#' penalties, not rank or source weights. Call \code{set.seed()} before -#' \code{cv.learner()} to reproduce the fold allocation. -#' For independent penalties, supply both separate grids and \code{lambda_2_all}. -#' Candidates with non-finite errors are excluded with a warning; if all fail, -#' the function stops. Finite fits that reach the iteration limit are retained, -#' so selecting a candidate does not certify convergence of its fold fits. -#' Assess the optimization settings as well as the final refit. -#' -#' The return value contains scores and selected parameters, not a final fitted -#' target matrix. Refit with \code{learner()} using all observed target entries -#' and the selected penalties, while retaining the same sources, weights, and rank. -#' -#' Observed target entries are randomly partitioned into approximately equal -#' folds. Each fold is set to missing in the training matrix, and the fitted -#' matrix is used to predict the held-out entries. Errors are summed across -#' folds for each candidate pair (common penalties) or triple (separate -#' penalties). Source entries are not held out. See McGrath et al. (2024). +#' Given sets of candidate values of \eqn{\lambda_1} and \eqn{\lambda_2}, this +#' function performs k-fold cross-validation to select the pair +#' \eqn{(\lambda_1, \lambda_2)} with the smallest held out error. This function +#' randomly partitions the entries of \code{Y_target} into \eqn{k} +#' (approximately) equally sized subsamples. The training data sets are obtained +#' by removing one of the \eqn{k} subsamples and the corresponding test data +#' sets are based on the held out subsamples. The \code{\link{learner}} function is +#' applied to each training data set. The held out error is computed by comparing +#' the entries in the test data sets with those imputed from the LEARNER +#' estimates. See McGrath et al. (2024) for further details. +#' +#' With separate penalties, the procedure selects the row, column, and balance +#' penalties together. Both modes can be used with multiple sources. Call +#' \code{set.seed()} first to reproduce the random partition. +#' +#' To obtain the final estimate, apply \code{\link{learner}} with the selected +#' penalties to the full data (see Examples). Fits that reach the iteration +#' limit still contribute to the held out error, so check the settings in +#' \code{control} before interpreting the selection. +#' +#' @return +#' A list containing: +#' \item{lambda_1_min}{Common source-space penalty in the selected combination. +#' \code{NA} when separate penalties are used.} +#' \item{lambda_2_min}{Balance penalty in the selected combination.} +#' \item{lambda_1_row_min, lambda_1_col_min}{Row and column penalties in the +#' selected combination. Returned only when separate penalties are used.} +#' \item{mse_all}{Sum of held-out squared errors for each candidate combination. +#' For common penalties, rows correspond to \code{lambda_1_all} and columns to +#' \code{lambda_2_all}. For separate penalties, the three dimensions correspond +#' to the row, column, and balance grids, in that order. Divide by the number +#' of observed target entries to obtain mean squared errors.} +#' \item{r}{Rank used in the fits.} #' #' @references #' McGrath, S., Zhu, C,. Guo, M. and Duan, R. (2024). \emph{LEARNER: A transfer learning method for low-rank matrix estimation}. arXiv preprint arXiv:2412.20605. #' +#' @seealso +#' \code{\link{learner}} for fitting with the selected penalties. +#' #' @examples #' res <- cv.learner(Y_source = dat_highsim$Y_source, #' Y_target = dat_highsim$Y_target, @@ -59,19 +73,20 @@ #' lambda_2_all = c(1, 10, 100), #' step_size = 0.003) #' -#' # A small simulated example: source B is a perturbation of source A, -#' # not an independent population. Weights are fixed demonstration values. -#' set.seed(803) -#' source_A <- dat_highsim$Y_source[1:20, 1:10] -#' source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), -#' nrow(source_A), ncol(source_A)) -#' sources <- list(A = source_A, B = source_B) -#' target <- dat_highsim$Y_target[1:20, 1:10] +#' # Refit using the selected penalties and all observed target entries. +#' fit <- learner(dat_highsim$Y_source, dat_highsim$Y_target, r = res$r, +#' lambda_1 = res$lambda_1_min, lambda_2 = res$lambda_2_min, +#' step_size = 0.003) +#' fit$convergence_criterion +#' +#' # Multiple sources with fixed demonstration weights. +#' sources <- dat_multisource$Y_source +#' target <- dat_multisource$Y_target #' weights <- c(0.7, 0.3) #' #' set.seed(815) #' res_separate <- cv.learner( -#' Y_source = sources, Y_target = target, r = 2, +#' Y_source = sources, Y_target = target, r = 3, #' source_weights = weights, #' lambda_1_row_all = c(1, 10), lambda_1_col_all = c(1, 3), #' lambda_2_all = c(0.1, 1), step_size = 0.0003, @@ -154,127 +169,131 @@ cv.learner <- function(Y_source, Y_target, r, lambda_1_all = NULL, lambda_2_all, #' Latent space-based transfer learning #' -#' Estimate a low-rank target matrix using information from one or more source -#' populations with the LatEnt spAce-based tRaNsfer lEaRning (LEARNER) method -#' (McGrath et al. 2024). The two source-space penalties can share a coefficient -#' or use separate coefficients. -#' -#' @param Y_target nonempty numeric matrix containing target population data. -#' Missing entries (\code{NA} or \code{NaN}) are allowed, but at least one entry -#' must be observed. Infinite values are not allowed. -#' @param Y_source numeric source matrix or nonempty list of numeric source matrices. Every source must have the same dimensions and corresponding row/column order as \code{Y_target}, and contain no missing or infinite values. -#' @param r optional common rank for the source spaces and target factors, between 1 and the smaller matrix dimension. If omitted, ScreeNOT selects it from the first source. -#' @param source_weights optional finite nonnegative numeric vector with one -#' weight per source and at least one positive value. Weights correspond to the -#' order of matrices in \code{Y_source}; names are not used for matching. They -#' are normalized to sum to one. \code{NULL} gives equal weights. For a single -#' source, a supplied weight must be a positive scalar and is normalized to one. -#' @param lambda_1 finite nonnegative numeric scalar specifying the common space penalty. Supply either this parameter or both \code{lambda_1_row} and \code{lambda_1_col}, not both modes. -#' @param lambda_2 finite nonnegative numeric scalar for the balance penalty (see Details) -#' @param lambda_1_row optional finite nonnegative numeric scalar for the source-space penalty on \eqn{U} (the SNP side when SNPs are rows). Must be supplied together with \code{lambda_1_col}. -#' @param lambda_1_col optional finite nonnegative numeric scalar for the source-space penalty on \eqn{V} (the phenotype side when phenotypes are columns). Must be supplied together with \code{lambda_1_row}. -#' @param step_size numeric scalar specifying the step size for the scaled gradient steps in the numerical optimization algorithm -#' @param control list controlling the numerical optimization: +#' @description +#' This function applies the LatEnt spAce-based tRaNsfer lEaRning (LEARNER) +#' method (McGrath et al. 2024) to leverage data from a source population to improve +#' estimation of a low-rank matrix in an underrepresented target population. +#' +#' @param Y_source Matrix containing the source population data, with the same rows +#' and columns as \code{Y_target}. For multiple sources, supply a list of +#' matrices. +#' @param Y_target Matrix containing the target population data. Missing entries +#' are allowed. +#' @param r Rank of the knowledge graphs. If omitted, ScreeNOT (Donoho et al. 2023) +#' is applied to the source population data to select the rank. +#' @param lambda_1 Common source-space penalty. Larger values borrow more +#' information from the source population; zero does not use the source spaces. +#' Use \code{\link{cv.learner}} to select penalty values. +#' @param lambda_2 Balance penalty. Larger values more strongly encourage +#' \eqn{U} and \eqn{V} to have comparable scale. +#' @param step_size Step size in the optimization algorithm. Smaller values may +#' reduce oscillation but require more iterations. +#' @param control List of settings controlling when optimization stops: #' \describe{ -#' \item{\code{max_iter}}{Positive integer giving the maximum number of updates; -#' default \code{100}.} -#' \item{\code{threshold}}{Finite nonnegative absolute objective-change threshold; -#' default \code{0.001}. Starting with the second update, the algorithm stops -#' when \eqn{|L_t-L_{t-1}|} is strictly less than this value. This rule alone does -#' not establish factor stability or a global optimum.} -#' \item{\code{max_value}}{Finite positive multiplier; default \code{10}. -#' Starting with the second update, the algorithm stops if the current objective -#' exceeds this multiplier times the preceding objective (unless the -#' objective-change stopping rule has already been met).}} -#' -#' @return A list with the following elements: -#' \item{learner_estimate}{target signal estimate with the same dimensions as \code{Y_target}, formed from the factors with the lowest objective encountered, including the initialization.} -#' \item{objective_values}{objective values after each update, excluding the initialization. The last value need not correspond to the returned estimate.} -#' \item{convergence_criterion}{stopping code: \code{1}, the absolute objective-change threshold was met; \code{2}, the iteration limit was reached; \code{3}, excessive objective growth or non-finite objective/factors were detected.} -#' \item{r}{rank value used.} +#' \item{\code{max_iter}}{Maximum number of updates; default \code{100}.} +#' \item{\code{threshold}}{Stop when the absolute change between consecutive +#' objective values is smaller than this value; default \code{0.001}.} +#' \item{\code{max_value}}{Stop if the objective grows beyond this multiple of +#' its preceding value; default \code{10}.}} +#' @param lambda_1_row,lambda_1_col Separate source-space penalties for the row +#' factor \eqn{U} and column factor \eqn{V}, respectively. Use these when the two +#' sides should borrow different amounts of source information. Supply both +#' instead of \code{lambda_1}; equal values reproduce the common penalty. +#' @param source_weights Relative weights of the sources, in the order of +#' \code{Y_source}. Default is equal weights. #' #' @details +#' \strong{Data and notation:} +#' +#' The data consists of a matrix in the target population +#' \eqn{Y_0\in\mathbb{R}^{p\times q}} and the source population +#' \eqn{Y_1\in\mathbb{R}^{p\times q}}. Let +#' \eqn{\hat U_k\hat\Lambda_k\hat V_k^\top} denote the rank-\eqn{r} truncated +#' singular value decomposition (SVD) of \eqn{Y_k}, \eqn{k = 0, 1}. +#' +#' For \eqn{k = 0, 1}, one can view \eqn{Y_k} as a noisy version of +#' \eqn{\Theta_k}, referred to as the knowledge graph. The target of inference +#' is the target population knowledge graph, \eqn{\Theta_0}. #' -#' \strong{Data and source spaces:} -#' -#' Let \eqn{Y_0} be the \eqn{p \times q} target matrix, and let -#' \eqn{Y_1,\ldots,Y_K} be aligned source matrices of the same dimensions. -#' The target signal is estimated by \eqn{UV^\top}, with -#' \eqn{U \in \mathbb{R}^{p \times r}} and -#' \eqn{V \in \mathbb{R}^{q \times r}}. For each source, the first \eqn{r} -#' left and right singular vectors form bases \eqn{B_{U,k}} and \eqn{B_{V,k}}. -#' The source operators are -#' \deqn{\bar P_U = \sum_{k=1}^K w_k B_{U,k}B_{U,k}^\top, \qquad -#' \bar P_V = \sum_{k=1}^K w_k B_{V,k}B_{V,k}^\top,} -#' where \eqn{w_k \geq 0} and \eqn{\sum_k w_k=1}. -#' These combine source spaces, not the raw source matrices. The averaged -#' operators are generally not orthogonal projectors themselves. -#' Rows and columns must already be aligned by the caller; names are not matched. -#' -#' \strong{Objective and penalties:} -#' -#' The numerical algorithm seeks to minimize -#' \deqn{L(U,V) = \rho^{-1}\|\mathcal{P}_{\Omega}(UV^\top-Y_0)\|_F^2 -#' + \lambda_{1,\mathrm{row}}\|(I-\bar P_U)U\|_F^2 -#' + \lambda_{1,\mathrm{col}}\|(I-\bar P_V)V\|_F^2 -#' + \lambda_2\|U^\top U-V^\top V\|_F^2.} -#' Here \eqn{\Omega} contains the observed target entries, -#' \eqn{\mathcal{P}_{\Omega}} keeps residuals at those entries and sets the -#' others to zero, and \eqn{\rho=|\Omega|/(pq)} is the observed fraction. -#' With no missing entries, \eqn{\rho=1}. Source matrices must be complete. -#' -#' Supply \code{lambda_1} for a common space penalty, or supply both -#' \code{lambda_1_row} and \code{lambda_1_col} and leave \code{lambda_1=NULL}. -#' Mixing the modes or providing only one separate coefficient is an error. -#' Equal separate coefficients reproduce the common-penalty calculation under -#' the same inputs and optimization settings. The balance penalty -#' \code{lambda_2} is required in either mode. -#' The space penalties square the residual from the weighted source operator; -#' they are not weighted sums of separately squared per-source residuals. -#' Use \code{\link{cv.learner}} to select common or separate penalty values. -#' -#' \strong{Initialization and stopping:} -#' -#' If \code{r} is omitted, ScreeNOT selects it from the first source, with a -#' minimum of one. A supplied \code{r} is used directly. The first source is -#' always used to initialize the target factors, including when its weight is -#' zero. Reordering sources with their weights therefore preserves the weighted -#' operators for fixed \code{r}, but can change initialization and fitted results. -#' The initial factors are the first source's retained left and right singular -#' vectors, each multiplied by the square roots of its retained singular values. -#' -#' This function uses scaled gradient descent steps. Both gradients are -#' evaluated at the current \eqn{U} and \eqn{V}, then both matrices are updated. -#' Updates are repeated until a stopping criterion is satisfied. The returned -#' estimate uses the best objective encountered, not necessarily the final -#' iterate. Inspect \code{objective_values} and \code{convergence_criterion}; -#' code 1 only indicates that the stated objective-change rule was met. +#' \strong{Estimation:} +#' +#' This method estimates \eqn{\Theta_0} by \eqn{\tilde U\tilde V^\top}, where +#' \eqn{(\tilde U, \tilde V)} is the solution to the following optimization problem +#' +#' \deqn{\mathrm{arg\,min}_{U\in\mathbb{R}^{p\times r},\,V\in\mathbb{R}^{q\times r}} +#' \big\{\|UV^\top-Y_0\|_F^2 +#' +\lambda_1\|\mathcal{P}_\perp(\hat U_1)U\|_F^2 +#' +\lambda_1\|\mathcal{P}_\perp(\hat V_1)V\|_F^2 +#' +\lambda_2\|U^\top U-V^\top V\|_F^2\big\}} +#' +#' where \eqn{\mathcal{P}_\perp(\hat U_1) = I - \hat U_1\hat U_1^\top} and +#' \eqn{\mathcal{P}_\perp(\hat V_1) = I - \hat V_1\hat V_1^\top}. The four terms measure +#' target fit, departures from the two source spaces, and imbalance between the +#' target factors, respectively. If \code{Y_target} has missing entries, the +#' first term is computed over the observed entries. +#' +#' At each iteration, \eqn{U} and \eqn{V} are updated by a gradient +#' descent step, starting from the truncated SVD of the source population data. +#' These updates are repeated until convergence. +#' +#' \strong{Extensions:} +#' +#' Separate penalties \code{lambda_1_row} and \code{lambda_1_col} replace +#' \eqn{\lambda_1} in the second and third terms. For multiple sources, the +#' source projectors are replaced by weighted combinations: +#' +#' \deqn{\bar P_U=\sum_{k=1}^K w_k\hat U_k\hat U_k^\top,\qquad +#' \bar P_V=\sum_{k=1}^K w_k\hat V_k\hat V_k^\top.} +#' +#' where \eqn{\hat U_k} and \eqn{\hat V_k} come from the truncated SVD of +#' the \eqn{k}th source and the weights \eqn{w_k} are given by +#' \code{source_weights}. The full objective is +#' +#' \deqn{\begin{aligned} +#' L(U,V) ={}& \|UV^\top-Y_0\|_F^2 \\ +#' &+\lambda_{1,\mathrm{row}}\|(I_p-\bar P_U)U\|_F^2 \\ +#' &+\lambda_{1,\mathrm{col}}\|(I_q-\bar P_V)V\|_F^2 \\ +#' &+\lambda_2\|U^\top U-V^\top V\|_F^2. +#' \end{aligned}} +#' +#' where \eqn{I_p} and \eqn{I_q} are identity matrices. Equal space penalties +#' give the common-penalty version. With multiple sources, the first source is +#' used to initialize the algorithm and, if \code{r} is not specified, to +#' select the rank. +#' +#' @return +#' A list with the following elements: +#' \item{learner_estimate}{matrix containing the LEARNER estimate of the target population knowledge graph.} +#' \item{objective_values}{numeric vector containing the values of the objective function at each iteration.} +#' \item{convergence_criterion}{stopping code: \code{1}, the absolute objective-change threshold was met; \code{2}, the iteration limit was reached; \code{3}, excessive objective growth or non-finite objective/factors were detected.} +#' \item{r}{rank value used.} #' #' @references #' McGrath, S., Zhu, C,. Guo, M. and Duan, R. (2024). \emph{LEARNER: A transfer learning method for low-rank matrix estimation}. arXiv preprint arXiv:2412.20605. #' #' Donoho, D., Gavish, M. and Romanov, E. (2023). \emph{ScreeNOT: Exact MSE-optimal singular value thresholding in correlated noise}. The Annals of Statistics, 51(1), pp.122-148. #' +#' @seealso +#' \code{\link{cv.learner}} for penalty selection; +#' \code{\link{dlearner}} for direct projection onto source spaces. +#' #' @examples +#' # Single source with a common source-space penalty. #' res <- learner(Y_source = dat_highsim$Y_source, #' Y_target = dat_highsim$Y_target, #' lambda_1 = 1, lambda_2 = 1, #' step_size = 0.003) #' +#' # Same source, with separate penalties for the two factors. #' res_separate <- learner(Y_source = dat_highsim$Y_source, #' Y_target = dat_highsim$Y_target, #' lambda_1_row = 10, lambda_1_col = 1, #' lambda_2 = 1, step_size = 0.003) #' #' # Multiple sources with fixed demonstration weights. -#' # Source B is a perturbed copy, not an independent population. -#' set.seed(803) -#' source_A <- dat_highsim$Y_source[1:20, 1:10] -#' source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), -#' nrow(source_A), ncol(source_A)) #' res_multi <- learner( -#' Y_source = list(A = source_A, B = source_B), -#' Y_target = dat_highsim$Y_target[1:20, 1:10], r = 2, +#' Y_source = dat_multisource$Y_source, +#' Y_target = dat_multisource$Y_target, r = 3, #' source_weights = c(0.7, 0.3), #' lambda_1_row = 10, lambda_1_col = 1, lambda_2 = 1, #' step_size = 0.0003, control = list(max_iter = 100)) diff --git a/data-raw/gen-data.R b/data-raw/gen-data.R index 32d302a..53a7863 100644 --- a/data-raw/gen-data.R +++ b/data-raw/gen-data.R @@ -55,3 +55,38 @@ dat_modsim <- list(Y_source = Y_source, Theta_target = Theta_target) usethis::use_data(dat_modsim, overwrite = TRUE) + +################################################################################ +## Multiple sources with different noise levels +################################################################################ + +# This block can be run on its own without regenerating the existing datasets. +local({ + set.seed(20261014) + p <- 100; q <- 50; r <- 3 + + Theta_target <- matrix(rnorm(p * r), p, r) %*% + matrix(rnorm(r * q), r, q) + target_svd <- svd(Theta_target, nu = r, nv = r) + # Both sources share the target's latent spaces, with reversed signal strengths. + Theta_source <- target_svd$u %*% + diag(rev(target_svd$d[seq_len(r)]), nrow = r, ncol = r) %*% + t(target_svd$v) + + # Variances, not standard deviations. Each call draws independent noise. + noise_variance <- c(target = 0.10, A = 0.01, B = 0.50) + Y_target <- Theta_target + matrix( + rnorm(p * q, sd = sqrt(noise_variance['target'])), p, q) + Y_A <- Theta_source + matrix( + rnorm(p * q, sd = sqrt(noise_variance['A'])), p, q) + Y_B <- Theta_source + matrix( + rnorm(p * q, sd = sqrt(noise_variance['B'])), p, q) + + dat_multisource <- list( + Y_source = list(A = Y_A, B = Y_B), + Y_target = Y_target, + Theta_source = list(A = Theta_source, B = Theta_source), + Theta_target = Theta_target + ) + usethis::use_data(dat_multisource, overwrite = TRUE, version = 2) +}) diff --git a/data/dat_multisource.rda b/data/dat_multisource.rda new file mode 100644 index 0000000000000000000000000000000000000000..21395b444e2ef32a711d2e88a3337194eb58c8db GIT binary patch literal 212592 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%0DY@G&#{g9$tJ5*I_<6GC6^m-nQmn- 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≻bnd_p`B4 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
XA3Ke5f!fTl`lCb8PU4RHr;mwaWT_-j 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¥1m|B z512;h*s|Sby{(?D*^WpVAi#~ebjricbwa|7M=MrAgm~q;!(@x3MMF7D7D zDw@9&m|WU8)NwI%DSbS4Xgn`UT*7K#;T*oNzXaPZdI1=;m|ZNJMJui#lJ;bWrEe)m z)e^$|Ygh-=JQ4h<=&TUq9H7q;uOlIV_ndlDxPW&Y~(P(vby zD5jP@W5&JbHY_?`3|HB&S&9=isM8uM@9nm3G|SH%j#&7Hx*v%Usq{aR|5|A3am5t! zsXpR10!;|43*=7~M}#A2p=lGq#Y|#r*d0_qbaE$ebAE@ncw?Fjyt$GDo*}D!f8_Vv zi0du+D{V3fXSwR2Pfj5AiX7xI%K(PX-YbqlLg0m;nxlN8N1=4+cncH**#T18yPwLr zQbN9i*uflL=k0{5HPJDGjnshTv1cWxK{>iP(8)AF;02Xzv~r(Bb*PVz+l*e@-DP)ZpS;xXB!2?L{3)hUkz1CeEtdM9>qxEhzT6%vv6M|?_1H#{0G^xObSYdtD7C# z*{;+lhPZ{;TnaNq5V1(n6I6;@dLtN!SMid~i>_H7K0^IX zRTi(rXu%M9MXm9MC+yxd?EhHtmEV^up^WX1zT$W-_7)RYK~m2Fj4f#5r=Oj$!Z#bA zS0=C&C<``2b|p>R;xlZ#W;r)bGEO(t7s*KKn>9R4R6ZyyYE-v@3$M*yp2>fak}ccL zFhXr(7~>w2a#(l7oxCb+PkcR|Z1o1DFYN?Z5S1vYHA!f}L~|&lPAq81)`gd7^7qm) z{Vdz<2xq2-!nnwvvA*U<_}fxnc2*@R3xADl&sSjXKj)n`C+wpVqc&y^9b)Nc z@Gs!lQW-t3uXNA5(0uLFeadcGnDu+^rQqEzavX&3=%Eb~kMfNs9fF=s9Hptb z@|yGRG-#idV&#L)bd*L7ViGqI#6q-F245xr7HMQx?C9ClGNeJsVVB3?q8}qiR9fKy zTsMi4s;yW{#KU`cvAeWCyEH?ML`%U95IRw`hFHN6Ac&N;y=N@v>4tZ}>cGsaisBSI zu9^_pv=BXBjN{ zDA|e`R(U67MsD?(O(I+?SSCUc5XRG(e&{qoKeW~{djhH_nQrB@7;F6NF|1k!J&sumG-;O;^iGGxPN%yk zGdwy2JhWt4@5K=$n0XH~FL4lb0_)#c3nD*}Iye4s*^B$*;dtYk%Th_L+?bNWpiBa6 zR}`T7?eVs_@;H%S=RKoK`VqR~m!$36I=LxD#b*UmY?5%>!|0& zbuuHq=-=p#D**&ds(v>L<*d;?u=%G2k0tJ=)(t;IIBCmoV*O(ommWI<0bADPjT-W* zC#pB}YmGw^czx_4CWBpB|C_bTLv=(t>4Vxv{*Y&<2+2TdP^F9f2_8p7(MYt}TSIw% zUrX=B-THDjMLcCW3#vzV#_|MCHqI>sNetCt5{t0M803j(>t}M!4Lt0fGPFJ`U2Y%0Li8 z-e(#597Q(IXONP#R6FTCV5F4GF@|D06DMoAg^*V;4!KEBrlLRoNpx>t5+6OPW*D9K z9;rHb;|&T^dIbqaf_^Bhi>4^(McGz>R}xq>!*Z7yy~>ZcN1ffJC8CIiuHmvZPLBW7 zbuuK+2O?>fLmkCGcy0KEhHCkUksLhJ^&#O?q_iy)c7sjKp{{pPvt6{WG4AINk|c6| zol?b^VeFAa$oe&C&Yjo^N-pd_nE}>rHP^{n|LLgK9ktc%&yMhHnxz9#R4lJFK|2bk zO;kY%$FGT1rScx>qziY$CEdJio_(k(kLikKN>-`ir3EC2$1}*NYPzeeLx|~st7vLc zhvd(bu{$GFM-d3J|166m4)BLpi8Ez&=N3gEGL{y)A_@AV=EGYlG@kFgq@Tn^j;Tjj zMojQX$s)!M=H$q`a7qz~S9s+ zNjd3Z*fB!oQRs5|r61437;0Zsa`97Ao{+G^&dt^!vP^ibAfum@S12#im^B0-qJkX@ z;|yC|_?~I!U-SJD+#NC-u$T!%tYMD#sb)ATMkW$fp$YxjdU*25ONC2;a44sKYSGE` zvo~LaJ8KnfNGfQO9G3ifB$hAd%0i*s{+4Fx5Qz~SmL^Y0urzTWI1k7c;g5&de5s;Zjz4Q%ji7It2?gSA`;Bn22q>ADrD7r* z;S87iQ~qkjH0%B}kkJ*mf3$r=+CBe<3v@Y(!4SJ>*Vc!qbolq0&bpaad;CJ+U95i% zkH-{rmDaUmE2&u~+H!e5v;MX?j4|?t84&!|uG-myUoAH*Gk`-Cn2m!q8r6PKYFWdw zyO5ZQ;hm-{{v0(1Jl9Y*2ze^<2Wz57O8Cn3 zWrH0?pjal=O}qo#ddmg_{Or8vcPe%Nzgcsh{K;+qM4?_aQm^Hv!sjuov~fh6i7J{e zB*zLpBUqH~bsY}h)!&*HGyOL#wCPq4pT5Ryi0r6qP{^m&A}&)*+78Hi22K`S@mppU z$4YjkYj!At!3zfUqg?SD8g`hoYqI|h7RalT$t-QFURa)g&79}H%r%&LVgEeji;~yIW7V)0)?p(oPpHj_f5Bb{4xO4V9F0q;KlIcygUOr$C|e7Igj@ zEHQkn$^TxtUqWUgh=>Ve=xD|r2Xm-Y+UIXeC)hki(`aT?fP!&~SFmZ2jVqFj5dn^G z)ym2+L_&><7xr#WSLgowcp?M;(x{Vrik30BQi*nVMA2{0N=f-A=sn#M?%^}Sq{K~S^ z=a(X@M9qJN`ZhaIwa(6AbI@KJ>>Ay+WFImFo#06``y2UA3FLxBUjuP0-d=%kfuP^NB~}n0!GXdj9=iDqFiR zQqYp+j~w)A_aU5ADLp1I#3`KnbX{nk-fpjvdU}99=UpjC+#)NM=(8C|`K^r_Suzs} zi*&CN(hZZRmzg~POF^z#`W3GKHgVLwxe4;!%wWudoGe<;?Shgg*FB9G=gKTBQbY*j z?qL;Zg}IJJ#@&Q*mjne)`q8?!;{xzV)7Xw(vshM6Be3TxHzs8;eNn*#P7;f6*25op zThdbUv(gKSPR<7Lm*nRp#RuHQslkZ2x<*9EreXV!@$Pi(J2gRwl;s6*vi5WO7V@5O zjwEFXA-Up2wFDqIf;lM`teC6S*@VsWnLN#KkCXGXec=_&%Iz6Aus5P0(GqyN^69Gb zk9owZi64Mqch|$~%<*sI>m!zHRB4EU$yt00?Xlvgj10FikACd&4WtG!cm1pmF`%7 zj^C;oPYEaHKy&-5!P_)}U{bS;kPIhqJ!v#UYc|NtMxF$5&9MnZQd??{jCb`o3rK3@ zkk=zT3sQ${Z?3u0z&>)*(UyAS6GCC8iCe|m(G2EZY*ulNfj<>cr6H!nW}R`j{W}9_ z)iN8!VO@}nx5E@m>4c?^K}R*nDmgzRj2TT{3QZdHjlQucE~tX3EKCArdM{K2X^Bgy z#Voq*e!y$Gedx6@8kqcB8K#sMrnbPGl-Q$xVyn;Hx5o0OjU(|B-}j>LL;F$+V%1zn z3OK$-om?w;4-brUX|-1zpBCYF2<3j|S0mGhY|!H5%)=SrwkwQF&M?)?uwIehM6i%v zA&D{OTk;X8Whsb7b`e~oVOgQ`y3b{kn8!YORhHX75MzQ+ka+_oqFdxy)^WsJM&$^J z0qbCXBOj{fzePH6cIh6n(q!pnV^mJr?KncU)}rg9dw=M=|J7_H{h}hBDXQZk9XTzA z=VoLHV3jr!<`Sa-(&~m~9Z2juBSTiZ**qqH*(Ti2_E3McIi>`Vy4aoxo1n{@I+JQ1 zp77T*A})t%}dGCdh>6Z%&spQ6=nP)J9XHx0X(&DiFCDP=u!Cr6VV zaFioR?15wFBZe{I0#aKibNjp1`Ofg|7GwE`e-?tJO&(o_$6)MO-;U#At-#p^8gKFl(4gLdX%~=!$odOl1Xw zEz0HtVQh7`KiFjMNf0o>;z%C6TG|R?)A*1>S=U|(a6P-3bS^SeiT@wg*==1(R~2O0 zkL-wRk&lWBeFapl**ooWGV04l`pA7Vve(p2$~mPW_GIDbWHKyEDDJK)1ERyug9IZY zZ@<`Ck z>ObVuf9C&#xu$!BW()b=Y;7iG_wj-+>vHqAi{#bNpbqL%MTsizmJ{Ulo?_@l+h^xq z<3H=Ttg@6;auOhxF$Fbu6W~%?IJHY48Q()r@hWx$N$Qov(YtukYgjr*Rbn@yjKd=L z|9qDAl=IjRrPtNvdXE3)4nGcZ!oY-vy9Lc+P>^)Wn#}_nja88RCj1#t@0QfC3VNy? zr8n$c*qicpw4?l2GJkP(iCToE=OD9|oV4Q zF=A*;VJSm0kQ%tc%*`#%X!a=Ql6F5gS12BBmR+}l081H1$B|UK$#${DKteaN7*#~$ z-IeX*3nqn?6 z#P?c-BM4NH$L%Mzj6R9eW~@dQlZGoKV(rVqjVNUxRyc%0)chOve9`2Ur6O`RU6vh? z;;PeWVFH9;iTKO9LdX1UJ^8nV^X{7XDoq!5!CXy(tVboCPWqo;!21MDNybz1B@2k~ z!4f!0mCF-cURhAi=s0wn1P=N+SP~s0F!o7mxmf+47=I%EO?zZVs%bMHao?Srzp-qj zdr+AUtOvjH9|SHsI39!=M{0(wVOr%nT6?e_G?W7v!w8bP+LWB69OFW!$0jDD*gDkd zA*V=Y#eZW@8&Tjk@#6{iyplQdCo1&ddJ>NHm-ah>D(klc%75no>i5E;1 zI;~>z52}e~YAh$8yQI%dW9RHS@ATx=#S>k<@>309jnBdp%4CY8; zr%4sTFZ?Y0sjg$bibqr3M-~>jXEVaiOxp1@Lcd*)#q;C(0~Ls6l2`j1IoZGBRsA}M z&Blcg<`KTL7)I(1#Xhb{CLWiz?-V=YxgaizfX-|;n%+}KOhSJco-RU78_AEv)(N8=XH{nG;Gn`>cow-Q zF1PP>w4(V{;Jc@>DbhP2@yB)!?{QGVKIP)$G&Jq}!!EVtW?a#ge5vISq(A zN38y2`z_b({-+*UDh-LWbW52OlBO*es|4fz*-GhrNr+`jOX=>0{+(kA4p zi&e6Gp&ZjPa71Vj&`tAqLK#%eq_}*y;abE*Nq~xDv3piZM_0|2Kggn6nvXVf-|O1V z%~*liE&I$r{<>bKMIN2f#%K?<--qSH_(_bSP;(GeNwBhv@U*^mJ%jp8y6USQ!oj71 zOFtO~dm##Sl=?IJjWwP1R0_I0$0gO640RksRGrp#Gt1V#Jj#kGiJ9`NIYP+(bVIME zh1P5{4gR=pbZ!5`$Moy`WD8yAm@*XIMYri~4JyBixy*q^9vsn(1vnxKCTUz$oUSfD z^9meU^M&$?<-U8T8rC`1Al!2}LXO&F;-OGdy;GhV2b_1kLb;3$$8OHS^TGcxiam!; zNvF2KA4D?hv?&bHqiL$BB6hh-%A%cq-%8#+ViIfAwLxhYp{>{CvjjHScnmjUf{5Zc zhA-`1VXwI@S3*ExB(W`{zUvV7`WB+J6SX6vnv#tOneBg;UrTWljH@nIRIlX)D74!- z8*J2$Qzx3IY&(H&Usz^LQav2WFmf{%*t0~0!wP#YGskCzf*F(JZZVt>RgJC66*e+H zY!%O<9HG|~6Re`4`MomYQB-&9Ns_U~xFBAwJBfo&gyPUx|G68^dOk*~wAk!c`WTL0 zo7oVL$JdVdu#J|Hp0Gz+^m93cb&x7vV0ESY1(AhTZuQ) zOb>|g1&==>V3iMekra%BfLOa^3%&y(G92}Kuyopr0q0>*kwA??xRTh^nHoU>Z~Kd-43%J-UCD@wi+M-7O~N3bdSel5{Z@Js;5KBV!v&-8_0ebX^!RwX4& zKJ8j-h|18@`ga1!ZHJo6gUXSSWw%utc^QnK)O*_GOvbNW3b)PZOzuvj5@ zP5rz6m6uvz^aK}V$-WI-9dn$jhzTr51qzv07+7JpwY~GZST{;DS1nrewfFAC(Ohf9 zyl0e&j$;OoxRd?n;k;Rby^WEWb~#tZAG0g*zv?bp;m5W`(m+&O4)DE&Q4dObi*&<{ zwRSBcn?kgu0JXybe#_SYZ+5XUd zlLw6B@rDX)sq<>w$=a?0WdDx=6CUD!e*Pu%=exX_98VP&(a3lh9+(!cXnwbPEWH-B z&NsN+-vxa8`hfoLM-1Kd5Sl^r&|@QL#cIz;;yh~78OhNl7JOdabLP@kAYodrM$}Za zac@-Ortz+Qb3GzQQ=?=L=^2!4Xkvj z*0i+d6KBb6M(~OyoHwW58>DjW1HE>Q(@i1fhw_j+6}InWmS>4a{?{|){2D0i^te-Qqc6<6tZ|+C&m-?m%U$|wvL?3*$b<-Jen8n z$>UC9OcGj-N%;nbkJHUzE;<~?`Ma2#`6jqwceQnLWb-0Wh&{AoZ2tLc*4CpWtl1QT zhuGRFO7XigLAkgE2`dUt>4-4|%F6%oNfho>cf51?Eq^%s{Gp#_S*_6Z_v+D|Q)`wea5D zXI+qLcPACDSyKpLbVQZ?1KwDgLIwl*UY_ytzp0OpR1h6BnRS?#QDt76N7`UJ3(|_7 zT32a}K|X?;X_I?J7h7&@)PXt~41*lj;9YA()d_f;iZvX1x7KCoPI?-V4)LJD>yL~* znf2$dB>T08VR)Xukj?~)8pr~0MHE69S-UAZQD|sS(3ETV^ez01+!C?}@`KT1zLNc# zyZAuf7ZGxCmzYMyBFD0iSG(5zrh_RAT49D~ef#f(ANq9)*_=`woGs&bU3L%J(?ONE7rk^rq5cQZ<*+ctM`{|%fMF6r-Nnep2jNl>5a>je zqAXE7zfdb5Z|PbIfnSXdm5x#!z{lva_Vi-YI$Ky@1v!=}zgEzA>(Pm19Ow8p=6^lo zyWn-mA8nwcIjVO(P2?3KqSa)e#y&?KO@zHjCrQv#5@49a3DC zW+})_LnOj}wOUF29W1mL$aUVtxAy8f#3_p6_C%B7AaSxIrEEwVaxg4{ENS)a@TSci zgHlyGo|YC4w7Zwm&68+OKsurwhEaG@VbI8Ir_8jZ*8K^_CTPxNi4g9OH2VP%!U(B) zud6Q<#$3}LCX#gf6I(gxZQC@Qb*&^HJczx(Jjm#dUghj!WEWEz)U!$`KLY3HyXL@gz!`WV+LN}_Ro9l?O!AAs2~ z?v>*XKc?1f7~|p@l;8TNWM$`di=DX^frYeqJB+gR>3MSQnbW}MpAc*t%imrXZfH7}=qB>ar9ll?9n$Q}5u4=P137OZ@Wd`}XE za`+cN+JV<3oE^m=uKH>>n#^}=y%zg5I7oTgy%jB1TMOsy$z%!U%L!P9bW9IKVKO@& zPj3r5IVqpk*M$w%i&^s$O1s0bAaxiGbEZ>;{f?&IO)HN)bD;`axNP+_{!_3a!bwK9 z6b8=rqzOw*epaS4RgTg_MkEUH%y?rYLUgoh)G)d;Tcjy2>A!?n5rvi3ySGM@q>9Z! zyzAwW=Dy{jT*o9sP8%`W`b)QGvNnrHA`|-sYXuNAsl8yOF-6%VVn)+#sRz^!|{v_iMj-ni2($N{WXnoE=vI~@oBKV}cKREn=}ZB*P1EYowc#m(@BtA!3{h>>I62Cr@2( zj><7=Ur(^qvLnoKhspgl=ZMYEeW~gAo`32>zhv%lPtIT} zVo`3!Ig)_}pYWo&hnUX?T0%~9^y@0?^?8wl&(En-4B3~=J$*51OAv<5S?cX=#SR}S zq&hbXDlj#-e7G&=sDfxbh12+l;FUrjaFBLlagl--u^d?@#8ycuqbX^|(Y(Y%=#W^u z)$`@AO|wQO)_v#t8tqRa9 z(UO@iTV%C+3d}tf(+=yDRMfcN4%zq()3(6<3OJrA@3mX6{dn%^k08pg)AnUCP?Y>X zlr*i43s{BRjg(BiTz6;3Q))f9mY;WQ$Xrg%(^&>=L#@gI&aaK3_AVKoOar1na!v|# zVwJ()=85-dC)L~G#@2;h!HAmn_fKA^kPr2$deE-1cbC}_;~MLoAws%jh5Lx;p0ti` zC&Wj^@WB0e*`9?ksofwsqD_5ti`tv}77xTr+gH7bK&4T5#u#+s$rsZo@Z<=WQ1y$X zEQ>r(=tm(A|3qb_zAQsm+y4np#`|b1(enZ28}#z)Vcf6&dj@{m?_0l~W1M0& z5)k8DR6HW8IEY(_%R4RBF=G+=CLz0Ix1c_F6=c~Az{?Z3KAA#LJ)LMySJ?(~sL}ajygsAgOexQ$lJ_~0fk^C@rXw_5 zRVE1A=z@u^TB;AVT-qg)Le{sNjqSUf9p{_U?WK5kbJ1f9*AUl69Uwic^RJABimgDv z(YvrD#y-kFXNt(1J@grAHL<}C$Iq1h2mM!zg5is-mZz2Tov)qnUAB zE$l5Q#52qJWW%bXcv;iHU?!WocIUs(g>0-*Vr^;ZDo@nP{I_F_=+-+AKyl6*L*;}e z6Rt}g(Ip6FBP2AwP8YQDr&TIuh_AZlXLj*^KSz4d|FHEI2X{2r3JuQ=Lr-VkT+9b> z*5PO)R*t@Lk#sJ7z2O{v*BnB(LPft;%l(@V2gA@)sdzbzKFh%_C2HMod$LvE zCEDC?yJRW7AsERPYm(HylW^BRbS`ilUkmCW9i?eTo4 z6^NK)x!G1$&{02uPnV6jq9QSrx6R((Yqsn%N%yhIbCyfI_Gue}W7SYZPf`nSPv6IX UKXKQv5C8FZBvXY61A&#^VEk8;4*&oF literal 0 HcmV?d00001 diff --git a/inst/examples/multiple-sources.R b/inst/examples/multiple-sources.R index a3a39c7..7881e4e 100644 --- a/inst/examples/multiple-sources.R +++ b/inst/examples/multiple-sources.R @@ -1,11 +1,8 @@ -# Demonstration only: these are simulated matrices, not actual population data. -# The second source is constructed here to illustrate the multiple-source API. -set.seed(803) -source_A <- learner::dat_highsim$Y_source -source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), - nrow(source_A), ncol(source_A)) -target <- learner::dat_highsim$Y_target -sources <- list(source_A, source_B) +# Demonstration using the built-in simulated multiple-source data. +# Each population has independently generated noise; source A is more precise. +sources <- learner::dat_multisource$Y_source +target <- learner::dat_multisource$Y_target +# Illustrative weights, not weights estimated or tuned from these data. weights <- c(0.7, 0.3) # Tune the U-space, V-space, and balance penalties separately. @@ -16,8 +13,8 @@ cv_result <- learner::cv.learner( lambda_1_row_all = c(1, 10), lambda_1_col_all = c(1, 3), lambda_2_all = c(0.1, 1), - step_size = 0.003, n_folds = 3, - control = list(max_iter = 100) + step_size = 0.0003, n_folds = 3, + control = list(max_iter = 2000) ) print(c(row = cv_result$lambda_1_row_min, col = cv_result$lambda_1_col_min, @@ -31,12 +28,12 @@ fit <- learner::learner( lambda_1_row = cv_result$lambda_1_row_min, lambda_1_col = cv_result$lambda_1_col_min, lambda_2 = cv_result$lambda_2_min, - step_size = 0.003, control = list(max_iter = 100) + step_size = 0.0003, control = list(max_iter = 5000) ) print(fit$learner_estimate[1:5, 1:5]) print(fit$convergence_criterion) # 1: objective-change threshold; 2: iteration limit; 3: excessive growth/breakdown. -# This short example tests the workflow, not scientific performance or convergence. +# This example demonstrates the workflow, not scientific performance or convergence. # D-LEARNER also accepts multiple sources with the same weights. direct_fit <- learner::dlearner(sources, target, r = 3, source_weights = weights) diff --git a/man/cv.learner.Rd b/man/cv.learner.Rd index 8cea0ab..609ca27 100644 --- a/man/cv.learner.Rd +++ b/man/cv.learner.Rd @@ -20,76 +20,75 @@ cv.learner( ) } \arguments{ -\item{Y_source}{numeric source matrix or nonempty list of numeric source matrices. Every source must have the same dimensions and corresponding row/column order as \code{Y_target}, and contain no missing or infinite values.} +\item{Y_source}{Source population data, as in \code{\link{learner}}. +Supply a matrix for one source or a list of matrices for multiple sources.} -\item{Y_target}{nonempty numeric matrix containing target population data. -Missing entries (\code{NA} or \code{NaN}) are allowed, but at least one entry -must be observed. Infinite values are not allowed.} +\item{Y_target}{Target population data matrix, as in \code{\link{learner}}. +Only observed entries are used to evaluate predictions.} -\item{r}{optional common rank for the source spaces and target factors, between 1 and the smaller matrix dimension. If omitted, ScreeNOT selects it from the first source.} +\item{r}{Rank used in every fold, as in \code{\link{learner}}. It is not +selected by cross-validation.} -\item{lambda_1_all}{nonempty vector of finite nonnegative candidate common penalties. Use this or both separate grids, not both modes.} +\item{lambda_1_all}{Candidate values for the common source-space penalty +\eqn{\lambda_1}. All combinations with \code{lambda_2_all} are evaluated.} -\item{lambda_2_all}{nonempty vector of finite nonnegative candidate balance penalties.} +\item{lambda_2_all}{Candidate values for the balance penalty \eqn{\lambda_2}.} -\item{step_size}{numeric scalar specifying the step size for the scaled gradient steps in the numerical optimization algorithm, as in \code{\link{learner}}} +\item{step_size}{Step size for each fit, as in \code{\link{learner}}.} -\item{n_folds}{integer from 2 to the number of observed target entries. The default is \code{4}.} +\item{n_folds}{Number of cross-validation folds. Default is \code{4}.} -\item{n_cores}{positive integer specifying the requested number of OpenMP -threads. Candidate penalty combinations are evaluated in parallel when the -package is built with OpenMP support; otherwise they run sequentially. -The default is \code{1}.} +\item{n_cores}{Number of CPU cores used to evaluate the candidate penalty +combinations in parallel. Default is \code{1}, i.e., no parallelization.} -\item{control}{a list of parameters for controlling the stopping criteria for the numerical optimization algorithm, as in \code{\link{learner}}.} +\item{control}{Optimization settings for each fit; see \code{\link{learner}}.} -\item{lambda_1_row_all}{nonempty vector of finite nonnegative candidate penalties on U; requires \code{lambda_1_col_all}.} +\item{lambda_1_row_all, lambda_1_col_all}{Candidate values for separate +penalties on the row and column factors. Supply both instead of +\code{lambda_1_all}; all combinations with \code{lambda_2_all} are evaluated.} -\item{lambda_1_col_all}{nonempty vector of finite nonnegative candidate penalties on V; requires \code{lambda_1_row_all}.} - -\item{source_weights}{optional finite nonnegative numeric vector with one -weight per source and at least one positive value. Weights correspond to the -order of matrices in \code{Y_source}; names are not used for matching. They -are normalized to sum to one. \code{NULL} gives equal weights. For a single -source, a supplied weight must be a positive scalar and is normalized to one.} +\item{source_weights}{Relative source weights, as in \code{\link{learner}}. The weights +are not selected by cross-validation. Default is equal weights.} } \value{ -A list with the following elements: -\item{lambda_1_min}{selected common penalty; \code{NA} in separate mode.} -\item{lambda_1_row_min, lambda_1_col_min}{selected separate penalties, returned only in separate mode.} -\item{lambda_2_min}{balance penalty in the combination with the smallest held-out squared-error sum.} -\item{mse_all}{held-out squared-error sums, retaining the historical scale despite the name. Common mode returns a matrix indexed by \code{lambda_1_all} and \code{lambda_2_all}. Separate mode returns a three-dimensional array indexed by \code{lambda_1_row_all}, \code{lambda_1_col_all}, and \code{lambda_2_all}, in that order.} -\item{r}{rank value used.} +A list containing: +\item{lambda_1_min}{Common source-space penalty in the selected combination. +\code{NA} when separate penalties are used.} +\item{lambda_2_min}{Balance penalty in the selected combination.} +\item{lambda_1_row_min, lambda_1_col_min}{Row and column penalties in the +selected combination. Returned only when separate penalties are used.} +\item{mse_all}{Sum of held-out squared errors for each candidate combination. +For common penalties, rows correspond to \code{lambda_1_all} and columns to +\code{lambda_2_all}. For separate penalties, the three dimensions correspond +to the row, column, and balance grids, in that order. Divide by the number +of observed target entries to obtain mean squared errors.} +\item{r}{Rank used in the fits.} } \description{ -Select common or separate source-space penalties and a balance penalty for -\code{\link{learner}} by k-fold cross-validation on observed target entries. -One source matrix or a list of source matrices can be supplied. +Performs k-fold cross-validation to select the source-space and balance +penalties for \code{\link{learner}}. Returns prediction errors and the +penalty combination with the smallest error. } \details{ -Each observed target entry is held out once. The same folds are used for all -candidates. Dividing \code{mse_all} by the number of observed target entries -gives the mean squared error without changing the selected candidate. Ties -select the first minimum in R array order. Existing missing entries are -excluded from validation. Rank, source spaces, source weights, and the -first-source initialization are fixed across folds. The function tunes -penalties, not rank or source weights. Call \code{set.seed()} before -\code{cv.learner()} to reproduce the fold allocation. -For independent penalties, supply both separate grids and \code{lambda_2_all}. -Candidates with non-finite errors are excluded with a warning; if all fail, -the function stops. Finite fits that reach the iteration limit are retained, -so selecting a candidate does not certify convergence of its fold fits. -Assess the optimization settings as well as the final refit. - -The return value contains scores and selected parameters, not a final fitted -target matrix. Refit with \code{learner()} using all observed target entries -and the selected penalties, while retaining the same sources, weights, and rank. - -Observed target entries are randomly partitioned into approximately equal -folds. Each fold is set to missing in the training matrix, and the fitted -matrix is used to predict the held-out entries. Errors are summed across -folds for each candidate pair (common penalties) or triple (separate -penalties). Source entries are not held out. See McGrath et al. (2024). +Given sets of candidate values of \eqn{\lambda_1} and \eqn{\lambda_2}, this +function performs k-fold cross-validation to select the pair +\eqn{(\lambda_1, \lambda_2)} with the smallest held out error. This function +randomly partitions the entries of \code{Y_target} into \eqn{k} +(approximately) equally sized subsamples. The training data sets are obtained +by removing one of the \eqn{k} subsamples and the corresponding test data +sets are based on the held out subsamples. The \code{\link{learner}} function is +applied to each training data set. The held out error is computed by comparing +the entries in the test data sets with those imputed from the LEARNER +estimates. See McGrath et al. (2024) for further details. + +With separate penalties, the procedure selects the row, column, and balance +penalties together. Both modes can be used with multiple sources. Call +\code{set.seed()} first to reproduce the random partition. + +To obtain the final estimate, apply \code{\link{learner}} with the selected +penalties to the full data (see Examples). Fits that reach the iteration +limit still contribute to the held out error, so check the settings in +\code{control} before interpreting the selection. } \examples{ res <- cv.learner(Y_source = dat_highsim$Y_source, @@ -98,19 +97,20 @@ res <- cv.learner(Y_source = dat_highsim$Y_source, lambda_2_all = c(1, 10, 100), step_size = 0.003) -# A small simulated example: source B is a perturbation of source A, -# not an independent population. Weights are fixed demonstration values. -set.seed(803) -source_A <- dat_highsim$Y_source[1:20, 1:10] -source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), - nrow(source_A), ncol(source_A)) -sources <- list(A = source_A, B = source_B) -target <- dat_highsim$Y_target[1:20, 1:10] +# Refit using the selected penalties and all observed target entries. +fit <- learner(dat_highsim$Y_source, dat_highsim$Y_target, r = res$r, + lambda_1 = res$lambda_1_min, lambda_2 = res$lambda_2_min, + step_size = 0.003) +fit$convergence_criterion + +# Multiple sources with fixed demonstration weights. +sources <- dat_multisource$Y_source +target <- dat_multisource$Y_target weights <- c(0.7, 0.3) set.seed(815) res_separate <- cv.learner( - Y_source = sources, Y_target = target, r = 2, + Y_source = sources, Y_target = target, r = 3, source_weights = weights, lambda_1_row_all = c(1, 10), lambda_1_col_all = c(1, 3), lambda_2_all = c(0.1, 1), step_size = 0.0003, @@ -132,3 +132,6 @@ fit$convergence_criterion \references{ McGrath, S., Zhu, C,. Guo, M. and Duan, R. (2024). \emph{LEARNER: A transfer learning method for low-rank matrix estimation}. arXiv preprint arXiv:2412.20605. } +\seealso{ +\code{\link{learner}} for fitting with the selected penalties. +} diff --git a/man/dat_multisource.Rd b/man/dat_multisource.Rd new file mode 100644 index 0000000..32bc0f1 --- /dev/null +++ b/man/dat_multisource.Rd @@ -0,0 +1,54 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data-multisource.R +\docType{data} +\name{dat_multisource} +\alias{dat_multisource} +\title{Simulated data set: Multiple sources with different noise levels} +\format{ +A list with four components. All matrices have 100 rows and 50 columns, + with corresponding rows and columns aligned across populations. +\describe{ + \item{\code{Y_source}}{A named list of observed source matrices, \code{A} + and \code{B}, suitable for the \code{Y_source} argument.} + \item{\code{Y_target}}{The observed target matrix.} + \item{\code{Theta_source}}{A named list of the true rank-3 source matrices, + \code{A} and \code{B}. The two true matrices are identical.} + \item{\code{Theta_target}}{The true rank-3 target matrix.} +} +} +\usage{ +dat_multisource +} +\description{ +Simulated matrices for one target population and two source populations. +The sources share the target's latent row and column spaces, but source A +is observed more precisely than source B. This data set illustrates fitting +LEARNER and D-LEARNER with multiple sources. +} +\details{ +The source signal is obtained by reversing the three nonzero singular values +of the target signal while keeping its singular vectors. Each observed matrix +is generated by adding independent, mean-zero normal noise to its own signal. +Noise is independent across entries and populations, with variances 0.10 for +the target, 0.01 for source A, and 0.50 for source B. Thus, source A has less +noise than the target and source B has more. Source B is not generated from +the observed source A matrix. + +The true signal matrices are included for illustration and estimation-error +calculations; they are not inputs to the fitting functions. These simulated +data are a usage example, not a reproduction of the paper's simulation study. +} +\examples{ +data(dat_multisource) +fit <- dlearner( + Y_source = dat_multisource$Y_source, + Y_target = dat_multisource$Y_target, + r = 3, source_weights = c(0.7, 0.3) +) +dim(fit$dlearner_estimate) +} +\seealso{ +\code{\link{dat_highsim}}, \code{\link{dat_modsim}}, + \code{\link{learner}}, \code{\link{dlearner}} +} +\keyword{datasets} diff --git a/man/dlearner.Rd b/man/dlearner.Rd index a0be094..4d9bbbb 100644 --- a/man/dlearner.Rd +++ b/man/dlearner.Rd @@ -7,18 +7,18 @@ dlearner(Y_source, Y_target, r, source_weights = NULL) } \arguments{ -\item{Y_source}{numeric source matrix or nonempty list of numeric source matrices. Every source must have the same dimensions and corresponding row/column order as \code{Y_target}, and contain no missing or infinite values.} +\item{Y_source}{Matrix containing the source population data, with the same rows +and columns as \code{Y_target}. For multiple sources, supply a list of +matrices.} -\item{Y_target}{nonempty numeric target matrix with only finite values. -Unlike \code{learner()}, this function does not allow missing target entries.} +\item{Y_target}{Matrix containing the target population data. Missing entries +are not allowed.} -\item{r}{optional common rank of the retained source spaces. If omitted, ScreeNOT selects it from the first source.} +\item{r}{Rank of the knowledge graphs. If omitted, ScreeNOT (Donoho et al. 2023) +is applied to the source population data to select the rank.} -\item{source_weights}{optional finite nonnegative numeric vector with one -weight per source and at least one positive value. Weights correspond to the -order of matrices in \code{Y_source}; names are not used for matching. They -are normalized to sum to one. \code{NULL} gives equal weights. For a single -source, a supplied weight must be a positive scalar and is normalized to one.} +\item{source_weights}{Relative weights of the sources, in the order of +\code{Y_source}. Default is equal weights.} } \value{ A list with the following components: @@ -26,42 +26,57 @@ A list with the following components: \item{r}{rank value used.} } \description{ -Estimate a target signal matrix by applying source-space operators directly -to the target data, using the D-LEARNER method (McGrath et al. 2024). -One source matrix or a list of source matrices can be supplied. +This function applies the Direct projection LatEnt spAce-based tRaNsfer +lEaRning (D-LEARNER) method (McGrath et al. 2024) to leverage data from a source +population to improve estimation of a low-rank matrix in an underrepresented +target population. } \details{ -Each source has the same dimensions as the target matrix \eqn{Y_0}. -Let \eqn{B_{U,k}} and \eqn{B_{V,k}} contain the first \eqn{r} left and -right singular vectors of source \eqn{k}. The estimate is -\deqn{\widehat\Theta_0 = \bar P_U Y_0 \bar P_V, \qquad -\bar P_U=\sum_k w_k B_{U,k}B_{U,k}^\top, \qquad -\bar P_V=\sum_k w_k B_{V,k}B_{V,k}^\top.} -The nonnegative weights are normalized to sum to one and default to equal -values. With one source this reduces to projection onto its retained left -and right singular spaces. With multiple sources, the averaged operators -need not be idempotent, and the estimate can have rank larger than \code{r}. -This is not an average of the raw source matrices or of separately computed -single-source D-LEARNER estimates. +\strong{Data and notation:} -D-LEARNER does not iteratively fit target factors and has no space or balance -penalty parameters. All inputs must have aligned rows and columns and no -missing values. Names are not matched. If \code{r} is omitted, it is selected -from the first source, including when that source has zero weight. -Output dimension names are taken from the first source. +The data consists of a matrix in the target population +\eqn{Y_0 \in \mathbb{R}^{p \times q}} and the source population +\eqn{Y_1 \in \mathbb{R}^{p \times q}}. Let +\eqn{\hat U_k\hat\Lambda_k\hat V_k^\top} denote the rank-\eqn{r} truncated +singular value decomposition (SVD) of \eqn{Y_k}, \eqn{k = 0, 1}. + +For \eqn{k = 0, 1}, one can view \eqn{Y_k} as a noisy version of +\eqn{\Theta_k}, referred to as the knowledge graph. The target of inference +is the target population knowledge graph, \eqn{\Theta_0}. + +\strong{Estimation:} + +This method estimates \eqn{\Theta_0} by + +\deqn{\hat U_1\hat U_1^\top Y_0 \hat V_1\hat V_1^\top.} + +That is, the target population data are projected onto the latent row and +column spaces of the source population, without iterative fitting or penalty +selection. + +\strong{Multiple sources:} + +Supply a list of source matrices to combine information from several +populations. Their projection operators are averaged using +\code{source_weights}, and the estimate becomes + +\deqn{\widehat\Theta_0=\bar P_U Y_0\bar P_V,\qquad +\bar P_U=\sum_{k=1}^K w_k\hat U_k\hat U_k^\top,\qquad +\bar P_V=\sum_{k=1}^K w_k\hat V_k\hat V_k^\top.} + +where \eqn{\hat U_k} and \eqn{\hat V_k} come from the truncated SVD of +the \eqn{k}th source and the weights \eqn{w_k} are given by +\code{source_weights}. If \code{r} is not specified, the rank is selected +using the first source. } \examples{ res <- dlearner(Y_source = dat_highsim$Y_source, Y_target = dat_highsim$Y_target) -# A second simulated source, made by perturbing the first. -set.seed(803) -source_A <- dat_highsim$Y_source[1:20, 1:10] -source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), - nrow(source_A), ncol(source_A)) +# Multiple sources with fixed demonstration weights. res_multi <- dlearner( - Y_source = list(A = source_A, B = source_B), - Y_target = dat_highsim$Y_target[1:20, 1:10], r = 2, + Y_source = dat_multisource$Y_source, + Y_target = dat_multisource$Y_target, r = 3, source_weights = c(0.7, 0.3)) dim(res_multi$dlearner_estimate) @@ -69,3 +84,7 @@ dim(res_multi$dlearner_estimate) \references{ Donoho, D., Gavish, M. and Romanov, E. (2023). \emph{ScreeNOT: Exact MSE-optimal singular value thresholding in correlated noise}. The Annals of Statistics, 51(1), pp.122-148. } +\seealso{ +\code{\link{learner}} for a target fit that can adapt the source +spaces through penalized estimation. +} diff --git a/man/learner.Rd b/man/learner.Rd index fc8e9be..96de9bb 100644 --- a/man/learner.Rd +++ b/man/learner.Rd @@ -18,132 +18,130 @@ learner( ) } \arguments{ -\item{Y_source}{numeric source matrix or nonempty list of numeric source matrices. Every source must have the same dimensions and corresponding row/column order as \code{Y_target}, and contain no missing or infinite values.} +\item{Y_source}{Matrix containing the source population data, with the same rows +and columns as \code{Y_target}. For multiple sources, supply a list of +matrices.} -\item{Y_target}{nonempty numeric matrix containing target population data. -Missing entries (\code{NA} or \code{NaN}) are allowed, but at least one entry -must be observed. Infinite values are not allowed.} +\item{Y_target}{Matrix containing the target population data. Missing entries +are allowed.} -\item{r}{optional common rank for the source spaces and target factors, between 1 and the smaller matrix dimension. If omitted, ScreeNOT selects it from the first source.} +\item{r}{Rank of the knowledge graphs. If omitted, ScreeNOT (Donoho et al. 2023) +is applied to the source population data to select the rank.} -\item{lambda_1}{finite nonnegative numeric scalar specifying the common space penalty. Supply either this parameter or both \code{lambda_1_row} and \code{lambda_1_col}, not both modes.} +\item{lambda_1}{Common source-space penalty. Larger values borrow more +information from the source population; zero does not use the source spaces. +Use \code{\link{cv.learner}} to select penalty values.} -\item{lambda_2}{finite nonnegative numeric scalar for the balance penalty (see Details)} +\item{lambda_2}{Balance penalty. Larger values more strongly encourage +\eqn{U} and \eqn{V} to have comparable scale.} -\item{step_size}{numeric scalar specifying the step size for the scaled gradient steps in the numerical optimization algorithm} +\item{step_size}{Step size in the optimization algorithm. Smaller values may +reduce oscillation but require more iterations.} -\item{control}{list controlling the numerical optimization: +\item{control}{List of settings controlling when optimization stops: \describe{ -\item{\code{max_iter}}{Positive integer giving the maximum number of updates; -default \code{100}.} -\item{\code{threshold}}{Finite nonnegative absolute objective-change threshold; -default \code{0.001}. Starting with the second update, the algorithm stops -when \eqn{|L_t-L_{t-1}|} is strictly less than this value. This rule alone does -not establish factor stability or a global optimum.} -\item{\code{max_value}}{Finite positive multiplier; default \code{10}. -Starting with the second update, the algorithm stops if the current objective -exceeds this multiplier times the preceding objective (unless the -objective-change stopping rule has already been met).}}} - -\item{lambda_1_row}{optional finite nonnegative numeric scalar for the source-space penalty on \eqn{U} (the SNP side when SNPs are rows). Must be supplied together with \code{lambda_1_col}.} - -\item{lambda_1_col}{optional finite nonnegative numeric scalar for the source-space penalty on \eqn{V} (the phenotype side when phenotypes are columns). Must be supplied together with \code{lambda_1_row}.} - -\item{source_weights}{optional finite nonnegative numeric vector with one -weight per source and at least one positive value. Weights correspond to the -order of matrices in \code{Y_source}; names are not used for matching. They -are normalized to sum to one. \code{NULL} gives equal weights. For a single -source, a supplied weight must be a positive scalar and is normalized to one.} +\item{\code{max_iter}}{Maximum number of updates; default \code{100}.} +\item{\code{threshold}}{Stop when the absolute change between consecutive +objective values is smaller than this value; default \code{0.001}.} +\item{\code{max_value}}{Stop if the objective grows beyond this multiple of +its preceding value; default \code{10}.}}} + +\item{lambda_1_row, lambda_1_col}{Separate source-space penalties for the row +factor \eqn{U} and column factor \eqn{V}, respectively. Use these when the two +sides should borrow different amounts of source information. Supply both +instead of \code{lambda_1}; equal values reproduce the common penalty.} + +\item{source_weights}{Relative weights of the sources, in the order of +\code{Y_source}. Default is equal weights.} } \value{ A list with the following elements: -\item{learner_estimate}{target signal estimate with the same dimensions as \code{Y_target}, formed from the factors with the lowest objective encountered, including the initialization.} -\item{objective_values}{objective values after each update, excluding the initialization. The last value need not correspond to the returned estimate.} +\item{learner_estimate}{matrix containing the LEARNER estimate of the target population knowledge graph.} +\item{objective_values}{numeric vector containing the values of the objective function at each iteration.} \item{convergence_criterion}{stopping code: \code{1}, the absolute objective-change threshold was met; \code{2}, the iteration limit was reached; \code{3}, excessive objective growth or non-finite objective/factors were detected.} \item{r}{rank value used.} } \description{ -Estimate a low-rank target matrix using information from one or more source -populations with the LatEnt spAce-based tRaNsfer lEaRning (LEARNER) method -(McGrath et al. 2024). The two source-space penalties can share a coefficient -or use separate coefficients. +This function applies the LatEnt spAce-based tRaNsfer lEaRning (LEARNER) +method (McGrath et al. 2024) to leverage data from a source population to improve +estimation of a low-rank matrix in an underrepresented target population. } \details{ -\strong{Data and source spaces:} - -Let \eqn{Y_0} be the \eqn{p \times q} target matrix, and let -\eqn{Y_1,\ldots,Y_K} be aligned source matrices of the same dimensions. -The target signal is estimated by \eqn{UV^\top}, with -\eqn{U \in \mathbb{R}^{p \times r}} and -\eqn{V \in \mathbb{R}^{q \times r}}. For each source, the first \eqn{r} -left and right singular vectors form bases \eqn{B_{U,k}} and \eqn{B_{V,k}}. -The source operators are -\deqn{\bar P_U = \sum_{k=1}^K w_k B_{U,k}B_{U,k}^\top, \qquad - \bar P_V = \sum_{k=1}^K w_k B_{V,k}B_{V,k}^\top,} -where \eqn{w_k \geq 0} and \eqn{\sum_k w_k=1}. -These combine source spaces, not the raw source matrices. The averaged -operators are generally not orthogonal projectors themselves. -Rows and columns must already be aligned by the caller; names are not matched. - -\strong{Objective and penalties:} - -The numerical algorithm seeks to minimize -\deqn{L(U,V) = \rho^{-1}\|\mathcal{P}_{\Omega}(UV^\top-Y_0)\|_F^2 - + \lambda_{1,\mathrm{row}}\|(I-\bar P_U)U\|_F^2 - + \lambda_{1,\mathrm{col}}\|(I-\bar P_V)V\|_F^2 - + \lambda_2\|U^\top U-V^\top V\|_F^2.} -Here \eqn{\Omega} contains the observed target entries, -\eqn{\mathcal{P}_{\Omega}} keeps residuals at those entries and sets the -others to zero, and \eqn{\rho=|\Omega|/(pq)} is the observed fraction. -With no missing entries, \eqn{\rho=1}. Source matrices must be complete. - -Supply \code{lambda_1} for a common space penalty, or supply both -\code{lambda_1_row} and \code{lambda_1_col} and leave \code{lambda_1=NULL}. -Mixing the modes or providing only one separate coefficient is an error. -Equal separate coefficients reproduce the common-penalty calculation under -the same inputs and optimization settings. The balance penalty -\code{lambda_2} is required in either mode. -The space penalties square the residual from the weighted source operator; -they are not weighted sums of separately squared per-source residuals. -Use \code{\link{cv.learner}} to select common or separate penalty values. - -\strong{Initialization and stopping:} - -If \code{r} is omitted, ScreeNOT selects it from the first source, with a -minimum of one. A supplied \code{r} is used directly. The first source is -always used to initialize the target factors, including when its weight is -zero. Reordering sources with their weights therefore preserves the weighted -operators for fixed \code{r}, but can change initialization and fitted results. -The initial factors are the first source's retained left and right singular -vectors, each multiplied by the square roots of its retained singular values. - -This function uses scaled gradient descent steps. Both gradients are -evaluated at the current \eqn{U} and \eqn{V}, then both matrices are updated. -Updates are repeated until a stopping criterion is satisfied. The returned -estimate uses the best objective encountered, not necessarily the final -iterate. Inspect \code{objective_values} and \code{convergence_criterion}; -code 1 only indicates that the stated objective-change rule was met. +\strong{Data and notation:} + +The data consists of a matrix in the target population +\eqn{Y_0\in\mathbb{R}^{p\times q}} and the source population +\eqn{Y_1\in\mathbb{R}^{p\times q}}. Let +\eqn{\hat U_k\hat\Lambda_k\hat V_k^\top} denote the rank-\eqn{r} truncated +singular value decomposition (SVD) of \eqn{Y_k}, \eqn{k = 0, 1}. + +For \eqn{k = 0, 1}, one can view \eqn{Y_k} as a noisy version of +\eqn{\Theta_k}, referred to as the knowledge graph. The target of inference +is the target population knowledge graph, \eqn{\Theta_0}. + +\strong{Estimation:} + +This method estimates \eqn{\Theta_0} by \eqn{\tilde U\tilde V^\top}, where +\eqn{(\tilde U, \tilde V)} is the solution to the following optimization problem + +\deqn{\mathrm{arg\,min}_{U\in\mathbb{R}^{p\times r},\,V\in\mathbb{R}^{q\times r}} + \big\{\|UV^\top-Y_0\|_F^2 + +\lambda_1\|\mathcal{P}_\perp(\hat U_1)U\|_F^2 + +\lambda_1\|\mathcal{P}_\perp(\hat V_1)V\|_F^2 + +\lambda_2\|U^\top U-V^\top V\|_F^2\big\}} + +where \eqn{\mathcal{P}_\perp(\hat U_1) = I - \hat U_1\hat U_1^\top} and +\eqn{\mathcal{P}_\perp(\hat V_1) = I - \hat V_1\hat V_1^\top}. The four terms measure +target fit, departures from the two source spaces, and imbalance between the +target factors, respectively. If \code{Y_target} has missing entries, the +first term is computed over the observed entries. + +At each iteration, \eqn{U} and \eqn{V} are updated by a gradient +descent step, starting from the truncated SVD of the source population data. +These updates are repeated until convergence. + +\strong{Extensions:} + +Separate penalties \code{lambda_1_row} and \code{lambda_1_col} replace +\eqn{\lambda_1} in the second and third terms. For multiple sources, the +source projectors are replaced by weighted combinations: + +\deqn{\bar P_U=\sum_{k=1}^K w_k\hat U_k\hat U_k^\top,\qquad + \bar P_V=\sum_{k=1}^K w_k\hat V_k\hat V_k^\top.} + +where \eqn{\hat U_k} and \eqn{\hat V_k} come from the truncated SVD of +the \eqn{k}th source and the weights \eqn{w_k} are given by +\code{source_weights}. The full objective is + +\deqn{\begin{aligned} +L(U,V) ={}& \|UV^\top-Y_0\|_F^2 \\ +&+\lambda_{1,\mathrm{row}}\|(I_p-\bar P_U)U\|_F^2 \\ +&+\lambda_{1,\mathrm{col}}\|(I_q-\bar P_V)V\|_F^2 \\ +&+\lambda_2\|U^\top U-V^\top V\|_F^2. +\end{aligned}} + +where \eqn{I_p} and \eqn{I_q} are identity matrices. Equal space penalties +give the common-penalty version. With multiple sources, the first source is +used to initialize the algorithm and, if \code{r} is not specified, to +select the rank. } \examples{ +# Single source with a common source-space penalty. res <- learner(Y_source = dat_highsim$Y_source, Y_target = dat_highsim$Y_target, lambda_1 = 1, lambda_2 = 1, step_size = 0.003) +# Same source, with separate penalties for the two factors. res_separate <- learner(Y_source = dat_highsim$Y_source, Y_target = dat_highsim$Y_target, lambda_1_row = 10, lambda_1_col = 1, lambda_2 = 1, step_size = 0.003) # Multiple sources with fixed demonstration weights. -# Source B is a perturbed copy, not an independent population. -set.seed(803) -source_A <- dat_highsim$Y_source[1:20, 1:10] -source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), - nrow(source_A), ncol(source_A)) res_multi <- learner( - Y_source = list(A = source_A, B = source_B), - Y_target = dat_highsim$Y_target[1:20, 1:10], r = 2, + Y_source = dat_multisource$Y_source, + Y_target = dat_multisource$Y_target, r = 3, source_weights = c(0.7, 0.3), lambda_1_row = 10, lambda_1_col = 1, lambda_2 = 1, step_size = 0.0003, control = list(max_iter = 100)) @@ -157,3 +155,7 @@ McGrath, S., Zhu, C,. Guo, M. and Duan, R. (2024). \emph{LEARNER: A transfer lea Donoho, D., Gavish, M. and Romanov, E. (2023). \emph{ScreeNOT: Exact MSE-optimal singular value thresholding in correlated noise}. The Annals of Statistics, 51(1), pp.122-148. } +\seealso{ +\code{\link{cv.learner}} for penalty selection; +\code{\link{dlearner}} for direct projection onto source spaces. +} diff --git a/tests/testthat/test-multisource-data.R b/tests/testthat/test-multisource-data.R new file mode 100644 index 0000000..0cecc33 --- /dev/null +++ b/tests/testthat/test-multisource-data.R @@ -0,0 +1,31 @@ +test_that('multiple-source example contains aligned observations and rank-three signals', { + d <- dat_multisource + expect_named(d, c('Y_source', 'Y_target', 'Theta_source', 'Theta_target')) + expect_named(d$Y_source, c('A', 'B')) + expect_named(d$Theta_source, c('A', 'B')) + matrices <- c(d$Y_source, list(d$Y_target), d$Theta_source, list(d$Theta_target)) + for (m in matrices) { + expect_identical(dim(m), c(100L, 50L)) + expect_true(all(is.finite(m))) + } + for (m in c(d$Theta_source, list(d$Theta_target))) { + expect_equal(sum(svd(m, nu = 0, nv = 0)$d > 1e-8), 3) + } + expect_equal(d$Theta_source$A, d$Theta_source$B) + target <- svd(d$Theta_target, nu = 3, nv = 3) + source <- svd(d$Theta_source$A, nu = 3, nv = 3) + expect_equal(tcrossprod(target$u), tcrossprod(source$u), tolerance = 1e-10) + expect_equal(tcrossprod(target$v), tcrossprod(source$v), tolerance = 1e-10) +}) + +test_that('saved example has the intended noise scales without shared source noise', { + d <- dat_multisource + noise <- cbind(target = as.vector(d$Y_target - d$Theta_target), + A = as.vector(d$Y_source$A - d$Theta_source$A), + B = as.vector(d$Y_source$B - d$Theta_source$B)) + # Fixed saved data: broad checks catch variance/SD mistakes and reused noise. + ratios <- apply(noise, 2, var) / c(target = 0.10, A = 0.01, B = 0.50) + expect_true(all(ratios > 0.8 & ratios < 1.2)) + correlations <- cor(noise) + expect_true(all(abs(correlations[upper.tri(correlations)]) < 0.05)) +}) From 106929bef456950b924e131859a4271032420b1f Mon Sep 17 00:00:00 2001 From: Jodie Li Date: Thu, 15 Oct 2026 20:38:00 -0500 Subject: [PATCH 4/6] Refine objective plots and remove CV profile and simulation tools --- NAMESPACE | 2 - NEWS.md | 2 + R/plot-cv-profile.R | 81 ------- R/plot-simulation.R | 120 ---------- R/plot-spaces.R | 13 +- R/plotting.R | 181 ++++++++------- README.Rmd | 170 ++++++++++---- README.md | 209 +++++++++++------- README_files/figure-gfm/cv-heatmap-1.png | Bin 0 -> 35276 bytes .../figure-gfm/objective-history-1.png | Bin 0 -> 34864 bytes README_files/figure-gfm/unnamed-chunk-6-1.png | Bin 26109 -> 0 bytes README_files/figure-gfm/unnamed-chunk-7-1.png | Bin 26109 -> 0 bytes inst/examples/plot-diagnostics.R | 15 +- inst/examples/plot-paper-displays.R | 64 +----- inst/examples/plotting-guide.md | 50 ++--- man/plot_contributions.Rd | 6 +- man/plot_cv.Rd | 2 +- man/plot_cv_profile.Rd | 76 ------- man/plot_objective.Rd | 52 ++--- man/plot_simulation_results.Rd | 61 ----- man/plot_source_spaces.Rd | 7 +- tests/testthat/test-paper-plots.R | 36 --- tests/testthat/test-plotting.R | 102 +++++++-- 23 files changed, 504 insertions(+), 745 deletions(-) delete mode 100644 R/plot-cv-profile.R delete mode 100644 R/plot-simulation.R create mode 100644 README_files/figure-gfm/cv-heatmap-1.png create mode 100644 README_files/figure-gfm/objective-history-1.png delete mode 100644 README_files/figure-gfm/unnamed-chunk-6-1.png delete mode 100644 README_files/figure-gfm/unnamed-chunk-7-1.png delete mode 100644 man/plot_cv_profile.Rd delete mode 100644 man/plot_simulation_results.Rd diff --git a/NAMESPACE b/NAMESPACE index 2238f12..02814b0 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,9 +5,7 @@ export(dlearner) export(learner) export(plot_contributions) export(plot_cv) -export(plot_cv_profile) export(plot_objective) -export(plot_simulation_results) export(plot_source_spaces) import(RcppEigen) importFrom(Rcpp,sourceCpp) diff --git a/NEWS.md b/NEWS.md index e9ac59c..3631f30 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,8 @@ populations with independently generated noise. * Add separate row- and column-space penalties in `learner()` and cross-validation over these penalties in `cv.learner()`. +* Add plotting functions for objective function values, cross-validation + results, population latent spaces, and variable contributions. ### learner version 1.0.0 (2025-03-02) diff --git a/R/plot-cv-profile.R b/R/plot-cv-profile.R deleted file mode 100644 index cf2260e..0000000 --- a/R/plot-cv-profile.R +++ /dev/null @@ -1,81 +0,0 @@ -#' Plot cross-validation sensitivity curves -#' -#' Compare validation scores across the common space penalty, or across the -#' row-space penalty at each fixed column-space penalty. Each line corresponds -#' to one balance penalty, making nearly overlapping curves visible. -#' -#' @inheritParams plot_cv -#' @return Invisibly, a list of absolute-score diagnostics: \code{scores}, -#' \code{scale}, \code{grids}, \code{best_index}, \code{shown_lambda_2_index}, -#' \code{color_limits}, \code{unavailable_count}, \code{log_labels}, -#' \code{score_range}, \code{minimum}, \code{exact_minima}, \code{boundary}, -#' \code{lambda_2_slice_range}, and \code{x_order} (horizontal drawing order). -#' This base-graphics function does not return ggplot objects or the relative -#' display fields returned by \code{plot_cv()}. -#' @details -#' Supply the exact original grids. The horizontal candidates are sorted only -#' for drawing lines; arrays and selected indices retain CV order. Duplicate -#' horizontal candidates are rejected. In separate mode there is one panel per -#' column-space penalty; \code{lambda_2_index} selects the balance-penalty lines. -#' At most four panels are drawn per page. With \code{log_labels=TRUE}, the -#' horizontal coordinate is the actual base-10 logarithm, not a rounded label. -#' All vertical limits use the complete score array. Non-finite values break -#' lines. The graph does not quantify uncertainty or establish that a parameter -#' has no effect; exact ties and boundary selections are reported separately. -#' @examples -#' set.seed(42) -#' cv <- cv.learner(dat_highsim$Y_source[1:12, 1:8], -#' dat_highsim$Y_target[1:12, 1:8], r = 2, -#' lambda_1_all = c(0.1, 1, 10), lambda_2_all = c(0.1, 1), -#' step_size = 0.0003, n_folds = 2, control = list(max_iter = 20)) -#' plot_cv_profile(cv, lambda_1_all = c(0.1, 1, 10), -#' lambda_2_all = c(0.1, 1), n_observed = 96, log_labels = TRUE) -#' @export -plot_cv_profile <- function(x, lambda_1_all = NULL, lambda_2_all = NULL, - lambda_1_row_all = NULL, lambda_1_col_all = NULL, - n_observed = NULL, lambda_2_index = NULL, log_labels = FALSE) { - d <- prepare_cv_plot(x, lambda_1_all, lambda_2_all, lambda_1_row_all, - lambda_1_col_all, n_observed, lambda_2_index, log_labels) - separate <- length(dim(d$scores)) == 3L - horizontal <- if (separate) d$grids$row else d$grids$common - if (anyDuplicated(horizontal)) stop('Profile horizontal candidates must be distinct.') - order_x <- order(horizontal) - horizontal <- horizontal[order_x] - if(log_labels) horizontal <- log10(horizontal) - balance <- if(separate) d$shown_lambda_2_index else seq_along(d$grids$balance) - panels <- if(separate) seq_along(d$grids$col) else 1L - colors <- grDevices::colorRampPalette(c('#2166AC','#1B9E77','#D95F02','#984EA3'))(length(d$grids$balance)) - limits <- d$color_limits - old <- graphics::par(no.readonly=TRUE);on.exit(graphics::par(old),add=TRUE) - for(page in split(panels,ceiling(seq_along(panels)/4))) { - nc <- min(2,length(page)) - graphics::par(mfrow=c(ceiling(length(page)/nc),nc),mar=c(3.8,4.5,2.5,7), - oma=c(4.2,0,0,0),mgp=c(2.5,0.6,0),cex.axis=0.9,cex.lab=0.95) - for(j in page) { - label <- if(separate) expression(lambda[1*','*plain(row)]) else expression(lambda[1]) - if(log_labels)label<-bquote(log[10](.(label[[1]]))) - title <- if(separate) bquote('CV profile at '~lambda[1*','*plain(col)] == .(d$grids$col[j])) else 'Cross-validation sensitivity' - graphics::plot(horizontal,rep(NA_real_,length(horizontal)),type='n',ylim=limits, - xlab=label,ylab=sub('Validation','Held-out',d$scale),main=title,cex.main=1.1) - graphics::grid(col='gray90',lty=1) - for(k in balance) { - y <- if(separate) d$scores[order_x,j,k] else d$scores[order_x,k] - y[!is.finite(y)]<-NA_real_ - graphics::lines(horizontal,y,type='b',pch=16,cex=0.55,col=colors[k],lty=1+(k-1)%%6,lwd=1.5) - } - if(!separate || (j==d$best_index[2] && d$best_index[3] %in% balance)) { - selected_x<-if(separate)d$grids$row[d$best_index[1]] else d$grids$common[d$best_index[1]] - if(log_labels)selected_x<-log10(selected_x) - graphics::points(selected_x,d$minimum,pch=4,cex=1.3,lwd=2) - } - usr <- graphics::par('usr') - graphics::legend(usr[2] + 0.06 * diff(usr[1:2]), usr[4], xjust=0, yjust=1, - xpd=NA,bty='n',title=expression(lambda[2]), - legend=format(d$grids$balance[balance],digits=3,trim=TRUE), - col=colors[balance],lty=1+(balance-1)%%6,pch=16,cex=0.8) - } - plot_cv_caption(d) - } - d$x_order<-order_x - invisible(d) -} diff --git a/R/plot-simulation.R b/R/plot-simulation.R deleted file mode 100644 index 352e790..0000000 --- a/R/plot-simulation.R +++ /dev/null @@ -1,120 +0,0 @@ -#' Compare methods across simulation settings -#' -#' Plot replicate means by noise-variance ratio, with panels for each scenario -#' and rank. The default method colors and line types follow the simulation -#' figures in the LEARNER paper. This function summarizes supplied results; -#' it does not run simulations or reproduce the paper's experiments. -#' -#' @param data Data frame with columns \code{scenario}, \code{rank}, -#' \code{variance_ratio}, \code{method}, and \code{error}. Each row must be one -#' replicate result, not an already aggregated mean. Scenario and method are -#' nonempty character strings or factors; rank is a positive integer; the -#' variance ratio is positive; error is finite and nonnegative. Use the same -#' error definition across all methods and scenarios. For the paper's metric, -#' supply the Frobenius norm of estimated minus true signal. -#' @param uncertainty One of \code{"none"}, \code{"sd"}, or \code{"se"}. -#' Bars show one sample standard deviation or one standard error of the mean, -#' not a confidence interval. Bars are omitted for groups with one replicate. -#' @param main Overall title. -#' @param ylab Vertical label naming the supplied error metric. -#' @param xlab Horizontal label; defaults to the target/source noise variance ratio. -#' @return Invisibly, a data frame with grouping columns and \code{mean}, -#' \code{sd}, \code{se}, and \code{n}. Missing groups are not filled with zero. -#' @details -#' Scenarios and methods follow their first appearance in the data; ranks and -#' ratios are sorted numerically. At most two ranks and three scenarios are -#' drawn per page. All pages share vertical limits and method styles. Missing -#' method-by-ratio groups break curves. Canonical method names are -#' \code{"Target-only SVD"}, \code{"D-LEARNER"}, and \code{"LEARNER"}. -#' Other names are permitted and receive distinct styles. Replicate identifiers -#' are not inferred: callers must avoid duplicate records or mixing experiments. -#' @examples -#' results <- expand.grid(scenario = c("High similarity", "Low similarity"), -#' rank = 2, variance_ratio = c(1, 3, 5), -#' method = c("Target-only SVD", "D-LEARNER", "LEARNER"), -#' replicate = 1:2, stringsAsFactors = FALSE) -#' # Illustrative values only; replace with measured errors from simulations. -#' results$error <- 3 + 1 / results$variance_ratio + results$replicate / 10 -#' plot_simulation_results(results, main = "Illustrative input format") -#' @export -plot_simulation_results <- function(data, uncertainty=c('none','sd','se'), - main='Simulation comparison', ylab='Estimation error', - xlab=expression(sigma[0]^2/sigma[1]^2)) { - uncertainty<-match.arg(uncertainty) - required<-c('scenario','rank','variance_ratio','method','error') - if(!is.data.frame(data)||!all(required %in% names(data))||nrow(data)==0L) - stop('data must be a nonempty data frame with scenario, rank, variance_ratio, method, and error.') - data<-data[,required,drop=FALSE] - for(name in c('scenario','method')) { - if(!(is.character(data[[name]])||is.factor(data[[name]]))||anyNA(data[[name]])|| - any(!nzchar(trimws(as.character(data[[name]])))))stop(name,' must contain nonempty labels.') - data[[name]]<-as.character(data[[name]]) - } - for(name in c('rank','variance_ratio','error')) { - z<-data[[name]] - if(!is.numeric(z)||is.complex(z)||any(!is.finite(z)))stop(name,' must be finite and numeric.') - } - if(any(data$rank<1|data$rank!=floor(data$rank)))stop('rank must contain positive integers.') - if(any(data$variance_ratio<=0))stop('variance_ratio must be positive.') - if(any(data$error<0))stop('error must be nonnegative.') - # Integer codes avoid collisions when labels contain punctuation. - keys<-lapply(data[,required[1:4]],function(z)match(z,unique(z))) - groups<-split(seq_len(nrow(data)),interaction(keys,drop=TRUE)) - summary<-do.call(rbind,lapply(groups,function(i) { - first<-data[i[1],required[1:4],drop=FALSE] - n<-length(i);s<-if(n>1)stats::sd(data$error[i])else NA_real_ - cbind(first,mean=mean(data$error[i]),sd=s,se=s/sqrt(n),n=n) - })) - rownames(summary)<-NULL - scenarios<-unique(data$scenario);ranks<-sort(unique(data$rank));methods<-unique(data$method) - summary<-summary[order(match(summary$scenario,scenarios),summary$rank, - match(summary$method,methods),summary$variance_ratio),] - rownames(summary)<-NULL - canonical<-c('Target-only SVD','D-LEARNER','LEARNER') - colors<-stats::setNames(grDevices::colorRampPalette(c('#7B3294', '#008837', '#E66101', '#2166AC'))(length(methods)),methods) - types<-stats::setNames(1+(seq_along(methods)-1)%%6,methods) - for(j in seq_along(canonical))if(canonical[j] %in% methods) { - colors[canonical[j]]<-c('#E41A1C','#4DAF4A','#377EB8')[j] - types[canonical[j]]<-c(1,2,6)[j] - } - bar<-if(uncertainty=='none')rep(0,nrow(summary))else summary[[uncertainty]] - bar[is.na(bar)]<-0 - ylim<-c(min(0,summary$mean-bar),max(summary$mean+bar)) - if(diff(ylim)==0)ylim<-c(0,1) - ylim[2]<-ylim[2]+0.05*diff(ylim) - old<-graphics::par(no.readonly=TRUE);on.exit(graphics::par(old),add=TRUE) - for(sp in split(scenarios,ceiling(seq_along(scenarios)/3))) { - for(rp in split(ranks,ceiling(seq_along(ranks)/2))) { - graphics::par(mfrow=c(length(sp),length(rp)),mar=c(3.5,4,2.8,1), - oma=c(4.5,0,2.5,0),mgp=c(2.3,0.6,0),cex.axis=0.9,cex.lab=0.95) - for(sc in sp)for(rk in rp) { - panel<-summary[summary$scenario==sc & summary$rank==rk,,drop=FALSE] - ratios<-sort(unique(data$variance_ratio)) - graphics::plot(range(ratios),ylim,type='n',ylim=ylim,xlab=xlab,ylab=ylab, - main=paste0(sc,' (Rank = ',rk,')'),cex.main=1.05) - graphics::grid(col='gray85',lty=1) - for(m in methods) { - z<-panel[panel$method==m,,drop=FALSE] - pos<-match(ratios,z$variance_ratio) - graphics::lines(ratios,z$mean[pos],type='b',pch=16,col=colors[m],lty=types[m],lwd=2) - if(uncertainty!='none') { - valid<-is.finite(z[[uncertainty]]) & z[[uncertainty]]>0 - if(any(valid))graphics::arrows(z$variance_ratio[valid],z$mean[valid]-z[[uncertainty]][valid], - z$variance_ratio[valid],z$mean[valid]+z[[uncertainty]][valid], - angle=90,code=3,length=0.035,col=colors[m]) - } - } - } - # A full-device overlay places one shared legend beneath the panel grid. - graphics::par(fig=c(0,1,0,1),new=TRUE,mar=rep(0,4),oma=rep(0,4),cex=1) - graphics::plot.new();graphics::plot.window(c(0,1),c(0,1),xaxs='i',yaxs='i') - graphics::legend('bottom',inset=0.022,legend=methods,col=colors,lty=types,pch=16, - ncol=min(3,length(methods)),bty='n',cex=0.9,xpd=NA) - label<-if(uncertainty=='none') 'Replicate means; no uncertainty bars.' else - paste0('Replicate means; bars: +/- 1 ',toupper(uncertainty),'. No bars for n = 1.') - graphics::text(0.5,0.012,label,cex=0.75,xpd=NA) - graphics::text(0.5,0.98,main,font=2,cex=1.05,xpd=NA) - } - } - invisible(summary) -} diff --git a/R/plot-spaces.R b/R/plot-spaces.R index 3d280f9..d4287bc 100644 --- a/R/plot-spaces.R +++ b/R/plot-spaces.R @@ -52,7 +52,7 @@ draw_matrix_panel <- function(values, x_index, y_index, limits, colors, title, x #' Compare target and source latent spaces #' #' Plot column-space and row-space projection submatrices for the target and -#' each source, following the layout of Figure 4 in the LEARNER paper. +#' each source, to compare their retained latent spaces. #' #' @param Y_target Complete finite target matrix, or a completed estimate. #' Missing values are rejected; the function does not impute them. @@ -87,9 +87,8 @@ draw_matrix_panel <- function(values, x_index, y_index, limits, colors, title, x #' singular values at the truncation boundary the retained space is not unique. #' The input order must already encode any desired phenotype or genomic ordering. #' @examples -#' a <- dat_highsim$Y_source[1:20,1:10] -#' y <- dat_highsim$Y_target[1:20,1:10] -#' plot_source_spaces(y, list(A=a, B=a*1.1), r=2, max_display=20) +#' plot_source_spaces(dat_multisource$Y_target, dat_multisource$Y_source, +#' r = 3, max_display = 20) #' @export plot_source_spaces <- function(Y_target, Y_source, r, row_index=NULL, column_index=NULL, max_display=200L, limits=NULL, row_label='Row index', @@ -130,8 +129,8 @@ plot_source_spaces <- function(Y_target, Y_source, r, row_index=NULL, column_ind #' Plot variable contributions to latent components #' -#' Show squared singular-vector entries for a fitted target matrix, following -#' Figure 5 of the LEARNER paper. Higher contribution scores are darker red. +#' Show which rows and columns contribute to each latent component of a fitted +#' target matrix, using squared singular-vector entries. Higher scores are darker red. #' #' @param x A complete estimated matrix, a \code{learner()} result, or a #' \code{dlearner()} result. No rotation of the singular vectors is performed. @@ -156,7 +155,7 @@ plot_source_spaces <- function(Y_target, Y_source, r, row_index=NULL, column_ind #' subspaces can change component-specific scores. Components from different #' populations are not automatically aligned or given biological labels. #' @examples -#' fit <- dlearner(dat_highsim$Y_source,dat_highsim$Y_target,r=3) +#' fit <- dlearner(dat_multisource$Y_source, dat_multisource$Y_target, r = 3) #' plot_contributions(fit,max_display=50) #' @export plot_contributions <- function(x, r=NULL, row_index=NULL, column_index=NULL, diff --git a/R/plotting.R b/R/plotting.R index 5f652ad..ee62a14 100644 --- a/R/plotting.R +++ b/R/plotting.R @@ -1,99 +1,116 @@ -#' Plot the recorded LEARNER objective history +#' Plot the objective function values of a LEARNER fit #' -#' Inspect the objective values returned by \code{learner()}. Optionally show -#' the last updates beside the full history to reveal small oscillations. +#' Plots the values of the objective function at each iteration of the +#' numerical optimization algorithm in \code{learner}. #' -#' @param x A result returned by \code{learner()}. -#' @param last Optional positive integer specifying the number of final updates -#' to show in a second panel. \code{NULL} draws only the full history. -#' @param main Character scalar used as the title of the full-history panel. +#' @param x A result returned by \code{learner}. +#' @param iterations Iterations to show: \code{"all"} (the default), +#' \code{"near_optimum"}, or a vector of iteration numbers such as +#' \code{1:1000}. \code{"near_optimum"} shows the iterations from the one +#' with the smallest objective value through the last iteration. If the +#' smallest value occurs within the last 10\% of the iterations (and at least +#' the last 10), the last 10\% of the iterations are shown instead. The +#' horizontal axis is the same as for \code{"all"}, except in that case. +#' @param main Title of the plot. By default, \code{"Objective function"}, +#' with \code{"(near optimum)"} added when \code{iterations = "near_optimum"}. #' -#' @return Invisibly, a list containing \code{iteration}, \code{objective}, -#' \code{last_indices}, \code{recorded_min_index}, and -#' \code{convergence_criterion}. Plotting does not modify \code{x}. +#' @return Invisibly, a data frame of the plotted iterations and objective +#' values. #' -#' @details -#' Update numbers start at one. The initialization objective is not stored in -#' \code{x$objective_values}. The marked minimum is therefore the lowest -#' \emph{recorded} objective, not necessarily the objective of the returned -#' estimate, which can retain the initialization. Panels use their own vertical -#' scales. A stopping code of one indicates the objective-change rule was met; -#' it does not establish a global optimum or stability of the factors. -#' -#' Non-finite recorded values are shown as red triangles at the top of a panel, -#' with a legend; their vertical positions do not represent objective values. -#' At least one recorded value must be finite. Graphics parameters are restored -#' on exit; this function sets its own panel arrangement. +#' @details The red point marks the smallest objective value, which is also +#' given above the plot. The objective value at the initialization is not +#' included. With \code{iterations = "near_optimum"}, the plot shows a shaded +#' band spanning each pair of consecutive objective values, so that values +#' that alternate between iterations remain visible, and a line at their +#' average. #' #' @examples #' fit <- learner(dat_highsim$Y_source, dat_highsim$Y_target, -#' r = 3, lambda_1 = 1, lambda_2 = 1, step_size = 0.0003, -#' control = list(max_iter = 100)) -#' plot_objective(fit, last = 30) +#' r = 3, lambda_1 = 1, lambda_2 = 1, step_size = 0.003, +#' control = list(max_iter = 1000)) +#' plot_objective(fit) +#' plot_objective(fit, iterations = "near_optimum") +#' plot_objective(fit, iterations = 1:100) #' #' @export -plot_objective <- function(x, last = NULL, main = "Recorded objective history") { - if (!is.list(x) || !is.numeric(x$objective_values) || - is.complex(x$objective_values) || length(x$objective_values) == 0L || - !is.null(dim(x$objective_values))) { - stop('x must contain a nonempty numeric objective_values vector.') - } +plot_objective <- function(x, iterations = "all", main = NULL) { objective <- x$objective_values + if (!is.numeric(objective) || length(objective) == 0L || + !any(is.finite(objective))) { + stop('x must be a result returned by learner().') + } + n <- length(objective) finite <- is.finite(objective) - if (!any(finite)) stop('No finite objective values are available to plot.') - if (!is.null(last)) validate_count(last, 'last') - if (!is.character(main) || length(main) != 1L || is.na(main)) { - stop('main must be a nonmissing character scalar.') + minimum <- which.min(replace(objective, !finite, Inf)) + near <- identical(iterations, 'near_optimum') + tail_view <- FALSE + if (identical(iterations, 'all')) { + shown <- seq_len(n) + } else if (near) { + # If the minimum is the end of the history, show its final stretch instead. + tail_length <- max(10L, ceiling(0.1 * n)) + start <- min(minimum, max(1L, n - tail_length + 1L)) + tail_view <- start != minimum + shown <- seq.int(start, n) + } else if (is.numeric(iterations) && length(iterations) > 0L && + all(iterations %in% seq_len(n))) { + shown <- sort(unique(as.integer(iterations))) + } else { + stop("iterations must be 'all', 'near_optimum', or iteration numbers between 1 and ", + n, '.') + } + if (!any(finite[shown])) { + stop('The selected iterations contain no finite objective values; choose a different range.') } - code <- x$convergence_criterion - if (!is.numeric(code) || length(code) != 1L || is.na(code) || - !(code %in% 1:3)) { - stop('x must contain a convergence_criterion of 1, 2, or 3.') + if (is.null(main)) { + main <- if (near) 'Objective function (near optimum)' else 'Objective function' } - status <- c('Objective-change rule met', 'Iteration limit reached', - 'Excessive growth or numerical breakdown')[code] - indices <- seq_along(objective) - last_indices <- if (is.null(last)) integer() else - seq.int(max(1L, length(objective) - last + 1L), length(objective)) - safe <- objective - safe[!finite] <- Inf - minimum <- which.min(safe) - old <- graphics::par(no.readonly = TRUE) + values <- replace(objective[shown], !finite[shown], NA_real_) + + old <- graphics::par(mar = c(5, 4.5, 4, 1.5)) on.exit(graphics::par(old), add = TRUE) - graphics::par(mfrow = c(1, if (is.null(last)) 1 else 2), - mar = c(4.5, 4.5, 3.5, 1), oma = c(2.5, 0, 1, 0)) - draw <- function(index, title) { - y <- objective[index] - available <- is.finite(y) - limits <- if (any(available)) range(y[available]) else range(objective[finite]) - if (diff(limits) == 0) { - pad <- max(abs(limits[1]) * 0.01, 1e-8) - limits <- limits + c(-pad, pad) - } - y[!available] <- NA_real_ - graphics::plot(index, y, type = if (sum(available) == 1) 'b' else 'l', - col = '#2166AC', lwd = 1.5, xlab = 'Iteration', - ylab = 'Objective value', main = title, ylim = limits) - if (minimum %in% index) { - graphics::points(minimum, objective[minimum], pch = 4, lwd = 2, - col = '#202020', cex = 1.2) - } - if (any(!available)) { - graphics::points(index[!available], rep(limits[2], sum(!available)), - pch = 17, col = '#B2182B') - graphics::legend('topright', 'Non-finite update (position only)', - pch = 17, col = '#B2182B', bty = 'n', cex = 0.7) - } + # Near the optimum, keep the horizontal axis of the full history so that the + # two plots line up; only the vertical axis is zoomed. + xlim <- if (near && !tail_view) c(1, n) else range(shown) + graphics::plot(shown, values, type = 'n', xlim = xlim, xlab = 'Iteration', + ylab = 'Objective value', bty = 'l') + graphics::title(main, line = 2) + graphics::mtext(paste0('Minimum: ', format(objective[minimum], digits = 6), + ' at iteration ', minimum), + side = 3, line = 0.6, cex = 0.8, col = 'gray30') + if (near && length(shown) > 1L) { + # The band spans each pair of consecutive values, so alternating values + # appear as a band rather than as dense vertical lines. + nxt <- c(values[-1], values[length(values)]) # the last value pairs with itself + keep <- !is.na(values) & !is.na(nxt) + graphics::polygon(c(shown[keep], rev(shown[keep])), + c(pmin(values, nxt)[keep], rev(pmax(values, nxt)[keep])), + col = grDevices::adjustcolor('#2166AC', 0.4), border = NA) + graphics::lines(shown[keep], ((values + nxt) / 2)[keep], col = '#2166AC', lwd = 1.5) + } else { + graphics::lines(shown, values, col = '#2166AC', type = if (length(shown) == 1L) 'p' else 'l') + } + if (minimum %in% shown) { + graphics::points(minimum, objective[minimum], pch = 16, cex = 0.9, col = '#B2182B') + } + # Symbol keys distinguish the band, mean line, and minimum without colored text. + band_color <- grDevices::adjustcolor('#2166AC', 0.4) + labels <- if (near && length(shown) > 1L) + c('Consecutive-iteration range', 'Pairwise average') else 'Objective value' + colors <- if (near && length(shown) > 1L) c(band_color, '#2166AC') else '#2166AC' + symbols <- if (near && length(shown) > 1L) c(22, NA) else NA + line_types <- if (near && length(shown) > 1L) c(0, 1) else 1 + if (minimum %in% shown) { + labels <- c(labels, 'Minimum recorded objective') + colors <- c(colors, '#B2182B') + symbols <- c(symbols, 16) + line_types <- c(line_types, 0) } - draw(indices, main) - if (!is.null(last)) draw(last_indices, paste('Last', length(last_indices), 'updates')) - graphics::mtext(paste0('Stop code ', code, ': ', status), outer = TRUE, - side = 3, line = -0.5, cex = 0.85) - graphics::mtext('X: minimum recorded objective; initialization is not included.', - outer = TRUE, side = 1, line = 0.7, cex = 0.8) - invisible(list(iteration = indices, objective = objective, - last_indices = last_indices, recorded_min_index = minimum, - convergence_criterion = code)) + graphics::legend('topright', inset = 0.02, legend = labels, + col = colors, text.col = 'gray30', pch = symbols, + pt.bg = band_color, pt.cex = 1.1, lty = line_types, + lwd = 1.5, cex = 0.8, y.intersp = 1.3, bty = 'n') + invisible(data.frame(iteration = shown, objective = objective[shown])) } #' Plot the LEARNER cross-validation scores @@ -116,7 +133,7 @@ plot_objective <- function(x, last = NULL, main = "Recorded objective history") #' \code{NULL} draws all panels. Not used in common-penalty mode. #' #' @param log_labels Logical scalar. If TRUE, display base-10 logarithms of -#' the candidates on the heatmap axes, as in the paper's plotting script. +#' the candidates on the heatmap axes. #' All candidates on those axes must be strictly positive. Log labels are #' rounded to two decimal places. Defaults to FALSE. #' diff --git a/README.Rmd b/README.Rmd index 595e3ff..08332b6 100644 --- a/README.Rmd +++ b/README.Rmd @@ -4,12 +4,10 @@ output: github_document -```{r, include = FALSE} -knitr::opts_chunk$set( - collapse = TRUE, - comment = "#>", - out.width = "100%" -) +```{r setup, include=FALSE} +knitr::opts_chunk$set(collapse = TRUE, comment = "#>", out.width = "100%", + fig.width = 8, fig.height = 5, + fig.path = "README_files/figure-gfm/") ``` @@ -20,72 +18,148 @@ knitr::opts_chunk$set( # learner -The `learner` package implements transfer learning methods for low-rank matrix estimation. These methods leverage similarity in the latent row and column spaces between the source and target populations to improve estimation in the target population. The methods include the LatEnt spAce-based tRaNsfer lEaRning (LEARNER) method and the direct projection LEARNER (D-LEARNER) method described by [McGrath et al. (2024)](https://doi.org/10.48550/arXiv.2412.20605). +The `learner` package implements transfer learning methods for low-rank matrix +estimation. These methods use similarity in the latent row and column spaces +of source and target populations to improve estimation in the target population. +The methods include LatEnt spAce-based tRaNsfer lEaRning (LEARNER) and direct +projection LEARNER (D-LEARNER), described by +[McGrath et al. (2024)](https://doi.org/10.48550/arXiv.2412.20605). ## Installation -You can install the released version of `learner` from [CRAN](https://CRAN.R-project.org/package=learner) with: -``` r +Install the released version from CRAN: + +```r install.packages("learner") ``` -You can install the development version of `learner` from [GitHub](https://github.com/stmcg/learner) with: +The examples below use the multiple-source and plotting extensions on the +`plot-diagnostics` development branch: -``` r -# install.packages("devtools") -devtools::install_github("stmcg/learner") +```r +# install.packages("remotes") +remotes::install_github("stmcg/learner", ref = "plot-diagnostics") +# Optional: required for plot_cv(), but not for fitting or the other plots. +install.packages("ggplot2") # version 3.4.0 or later ``` -## Example +## One source population + +`dat_highsim` contains a simulated source and target with similar latent spaces. +LEARNER fits the target while penalizing departures from the source spaces. +Here `lambda_1` controls both space penalties, and `lambda_2` controls the +balance between the two target factors. -We illustrate an example of how `learner` can be used. We first load the package. -```{r, results="hide", message=FALSE} +```{r single-source, message=FALSE} library(learner) +fit_single <- learner( + Y_source = dat_highsim$Y_source, Y_target = dat_highsim$Y_target, + r = 3, lambda_1 = 10, lambda_2 = 1, step_size = 0.003, + control = list(max_iter = 2000) +) +dim(fit_single$learner_estimate) ``` -In this illustration, we will use one of the toy data sets in the package (`dat_highsim`) that has a high degree of similarity between the latent spaces of the source and target populations. The object `dat_highsim` is a list which contains the observed source population data matrix `Y_source` and the target population data matrix `Y_target`. Since the data was simulated, the true values of the matrices are included in `dat_highsim` as `Theta_source` and `Theta_target`. +D-LEARNER projects the target data directly onto the source spaces, without +iterative optimization or penalty selection: -#### LEARNER Method +```{r single-direct} +direct_single <- dlearner(dat_highsim$Y_source, dat_highsim$Y_target, r = 3) +``` -We can apply the LEARNER method via the `learner` function. +## Multiple sources and separate penalties + +`dat_multisource` contains one target and two sources, each with 100 rows and +50 columns. The true signals have rank 3 and share their latent spaces. +Independent normal noise is added to each population: the noise variances are +0.10 for the target, 0.01 for source A, and 0.50 for source B. The true signal +matrices are also included, but are not needed for fitting. + +Pass the sources as a list. The weights below are illustrative choices; they +are held fixed during cross-validation. Larger row or column penalties encourage +the corresponding target factors to follow the combined source spaces more closely. + +```{r multiple-data} +sources <- dat_multisource$Y_source +target <- dat_multisource$Y_target +weights <- c(0.7, 0.3) +row_grid <- c(0, 1, 10) +col_grid <- c(0, 1, 3) +balance_grid <- c(0.1, 1) +``` -This method allows for flexible patterns of heterogeneity across the source and target populations. It consequently requires specifying the tuning parameters `lambda_1` and `lambda_2` which control the degree of transfer learning between the populations. These values can be selected based on cross-validation via the `cv.learner` function. For example, we can specify candidate values of 1, 10, and 100 for `lambda_1` and `lambda_2` and select the optimal values based on cross-validation as follows: -```{r} -res_cv.learner <- cv.learner(Y_source = dat_highsim$Y_source, - Y_target = dat_highsim$Y_target, - lambda_1_all = c(1, 10, 100), - lambda_2_all = c(1, 10, 100), - step_size = 0.003) -res_cv.learner$lambda_1_min -res_cv.learner$lambda_2_min +Select penalties by cross-validation. This small grid and iteration budget keep +the example quick; use suitable grids and optimization settings for an analysis. + +```{r multiple-cv} +set.seed(815) +cv <- cv.learner( + Y_source = sources, Y_target = target, r = 3, source_weights = weights, + lambda_1_row_all = row_grid, lambda_1_col_all = col_grid, + lambda_2_all = balance_grid, n_folds = 3, step_size = 0.0003, + control = list(max_iter = 2000) +) +c(row = cv$lambda_1_row_min, column = cv$lambda_1_col_min, + balance = cv$lambda_2_min) ``` -Next, we apply the `learner` function with these values of `lambda_1` and `lambda_2`: -```{r} -res_learner <- learner(Y_source = dat_highsim$Y_source, - Y_target = dat_highsim$Y_target, - lambda_1 = 100, lambda_2 = 1, - step_size = 0.003) +For a common space penalty, supply `lambda_1_all` instead of the separate row +and column grids. Cross-validation selects penalties, not the rank or source weights. + +### Cross-validation results + +The heatmap shows how much the CV error increases above its minimum, with a +black box marking the selected combination. Supplying `n_observed` converts +the stored sums of squared errors to mean squared errors (MSE). + +```{r cv-heatmap, fig.height=5.5, fig.cap="Cross-validation error across the candidate penalties."} +plot_cv( + cv, lambda_1_row_all = row_grid, lambda_1_col_all = col_grid, + lambda_2_all = balance_grid, n_observed = sum(!is.na(target)) +) ``` -The LEARNER estimate is given by the `learner_estimate` component in the output of the `learner` function, e.g., -```{r} -res_learner$learner_estimate[1:5, 1:5] + +A selected value on a grid boundary can be a reason to expand that grid. +CV scores can also come from fits that reached the iteration limit, so inspect +the optimization settings before interpreting the selection. + +### Refit and inspect optimization + +Refit using the selected penalties and all observed target entries: + +```{r multiple-refit} +fit <- learner( + Y_source = sources, Y_target = target, r = cv$r, source_weights = weights, + lambda_1_row = cv$lambda_1_row_min, + lambda_1_col = cv$lambda_1_col_min, lambda_2 = cv$lambda_2_min, + step_size = 0.0003, control = list(max_iter = 5000) +) +fit$convergence_criterion ``` -We can check the convergence of the numerical optimization algorithm by plotting the values of the objective function at each iteration: -```{r, out.width="75%"} -plot(res_learner$objective_values, - xlab = 'Iteration', ylab = 'Objective function', - type = 'l', col = 'blue', lwd = 2) + +```{r objective-history, fig.cap="Objective function values near the optimum for the refitted target model."} +plot_objective(fit, iterations = "near_optimum") ``` +Use `iterations = "all"` (the default) for the full history, or a vector such +as `1:500` for a specific range. -#### D-LEARNER Method +D-LEARNER accepts the same source list and weights: -We can apply the D-LEARNER method via the `dlearner` function. This method makes stronger assumptions on the heterogeneity across the source and target populations. It consequently does not rely on choosing tuning parameters. The `dlearner` function can be applied as follows: -```{r} -res_dlearner <- dlearner(Y_source = dat_highsim$Y_source, - Y_target = dat_highsim$Y_target) -res_dlearner$dlearner_estimate[1:5, 1:5] +```{r multiple-direct} +direct <- dlearner(sources, target, r = 3, source_weights = weights) +dim(direct$dlearner_estimate) ``` +### Latent spaces and contributions + +Compare the target and source spaces, or inspect which variables contribute +to the components of a fitted target matrix: + +```{r additional-plots, eval=FALSE} +plot_source_spaces(target, sources, r = 3) +plot_contributions(fit) +``` +See `?learner`, `?cv.learner`, `?dlearner`, and `?dat_multisource` for details. +More runnable examples are included in `inst/examples/` in the source repository. diff --git a/README.md b/README.md index a059c2c..69a98ac 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - @@ -12,121 +11,177 @@ coverage](https://codecov.io/gh/stmcg/learner/graph/badge.svg)](https://app.code # learner The `learner` package implements transfer learning methods for low-rank -matrix estimation. These methods leverage similarity in the latent row -and column spaces between the source and target populations to improve -estimation in the target population. The methods include the LatEnt -spAce-based tRaNsfer lEaRning (LEARNER) method and the direct projection -LEARNER (D-LEARNER) method described by [McGrath et -al. (2024)](https://doi.org/10.48550/arXiv.2412.20605). +matrix estimation. These methods use similarity in the latent row and +column spaces of source and target populations to improve estimation in +the target population. The methods include LatEnt spAce-based tRaNsfer +lEaRning (LEARNER) and direct projection LEARNER (D-LEARNER), described +by [McGrath et al. (2024)](https://doi.org/10.48550/arXiv.2412.20605). ## Installation -You can install the released version of `learner` from -[CRAN](https://CRAN.R-project.org/package=learner) with: +Install the released version from CRAN: ``` r install.packages("learner") ``` -You can install the development version of `learner` from -[GitHub](https://github.com/stmcg/learner) with: +The examples below use the multiple-source and plotting extensions on +the `plot-diagnostics` development branch: ``` r -# install.packages("devtools") -devtools::install_github("stmcg/learner") +# install.packages("remotes") +remotes::install_github("stmcg/learner", ref = "plot-diagnostics") +# Optional: required for plot_cv(), but not for fitting or the other plots. +install.packages("ggplot2") # version 3.4.0 or later ``` -## Example +## One source population -We illustrate an example of how `learner` can be used. We first load the -package. +`dat_highsim` contains a simulated source and target with similar latent +spaces. LEARNER fits the target while penalizing departures from the +source spaces. Here `lambda_1` controls both space penalties, and +`lambda_2` controls the balance between the two target factors. ``` r library(learner) +fit_single <- learner( + Y_source = dat_highsim$Y_source, Y_target = dat_highsim$Y_target, + r = 3, lambda_1 = 10, lambda_2 = 1, step_size = 0.003, + control = list(max_iter = 2000) +) +dim(fit_single$learner_estimate) +#> [1] 100 50 ``` -In this illustration, we will use one of the toy data sets in the -package (`dat_highsim`) that has a high degree of similarity between the -latent spaces of the source and target populations. The object -`dat_highsim` is a list which contains the observed source population -data matrix `Y_source` and the target population data matrix `Y_target`. -Since the data was simulated, the true values of the matrices are -included in `dat_highsim` as `Theta_source` and `Theta_target`. +D-LEARNER projects the target data directly onto the source spaces, +without iterative optimization or penalty selection: + +``` r +direct_single <- dlearner(dat_highsim$Y_source, dat_highsim$Y_target, r = 3) +``` -#### LEARNER Method +## Multiple sources and separate penalties -We can apply the LEARNER method via the `learner` function. +`dat_multisource` contains one target and two sources, each with 100 +rows and 50 columns. The true signals have rank 3 and share their latent +spaces. Independent normal noise is added to each population: the noise +variances are 0.10 for the target, 0.01 for source A, and 0.50 for +source B. The true signal matrices are also included, but are not needed +for fitting. -This method allows for flexible patterns of heterogeneity across the -source and target populations. It consequently requires specifying the -tuning parameters `lambda_1` and `lambda_2` which control the degree of -transfer learning between the populations. These values can be selected -based on cross-validation via the `cv.learner` function. For example, we -can specify candidate values of 1, 10, and 100 for `lambda_1` and -`lambda_2` and select the optimal values based on cross-validation as -follows: +Pass the sources as a list. The weights below are illustrative choices; +they are held fixed during cross-validation. Larger row or column +penalties encourage the corresponding target factors to follow the +combined source spaces more closely. ``` r -res_cv.learner <- cv.learner(Y_source = dat_highsim$Y_source, - Y_target = dat_highsim$Y_target, - lambda_1_all = c(1, 10, 100), - lambda_2_all = c(1, 10, 100), - step_size = 0.003) -res_cv.learner$lambda_1_min -#> [1] 100 -res_cv.learner$lambda_2_min -#> [1] 1 +sources <- dat_multisource$Y_source +target <- dat_multisource$Y_target +weights <- c(0.7, 0.3) +row_grid <- c(0, 1, 10) +col_grid <- c(0, 1, 3) +balance_grid <- c(0.1, 1) ``` -Next, we apply the `learner` function with these values of `lambda_1` -and `lambda_2`: +Select penalties by cross-validation. This small grid and iteration +budget keep the example quick; use suitable grids and optimization +settings for an analysis. ``` r -res_learner <- learner(Y_source = dat_highsim$Y_source, - Y_target = dat_highsim$Y_target, - lambda_1 = 100, lambda_2 = 1, - step_size = 0.003) +set.seed(815) +cv <- cv.learner( + Y_source = sources, Y_target = target, r = 3, source_weights = weights, + lambda_1_row_all = row_grid, lambda_1_col_all = col_grid, + lambda_2_all = balance_grid, n_folds = 3, step_size = 0.0003, + control = list(max_iter = 2000) +) +c(row = cv$lambda_1_row_min, column = cv$lambda_1_col_min, + balance = cv$lambda_2_min) +#> row column balance +#> 10 3 1 ``` -The LEARNER estimate is given by the `learner_estimate` component in the -output of the `learner` function, e.g., +For a common space penalty, supply `lambda_1_all` instead of the +separate row and column grids. Cross-validation selects penalties, not +the rank or source weights. + +### Cross-validation results + +The heatmap shows how much the CV error increases above its minimum, +with a black box marking the selected combination. Supplying +`n_observed` converts the stored sums of squared errors to mean squared +errors (MSE). ``` r -res_learner$learner_estimate[1:5, 1:5] -#> [,1] [,2] [,3] [,4] [,5] -#> [1,] 0.1468889 -1.63414688 1.1857308 0.008093962 0.2196070 -#> [2,] 0.1087766 -0.24392770 -1.0061737 -0.058119721 -0.4012974 -#> [3,] -0.6213028 1.04206753 -0.6907776 0.905709331 -0.5324437 -#> [4,] 1.5811588 -3.00001490 0.3978080 -2.124285120 0.6909386 -#> [5,] 0.5046133 0.01845138 -1.4798209 -0.765601712 -0.2230227 +plot_cv( + cv, lambda_1_row_all = row_grid, lambda_1_col_all = col_grid, + lambda_2_all = balance_grid, n_observed = sum(!is.na(target)) +) ``` -We can check the convergence of the numerical optimization algorithm by -plotting the values of the objective function at each iteration: +
+ +Cross-validation error across the candidate penalties. +

+Cross-validation error across the candidate penalties. +

+ +
+ +A selected value on a grid boundary can be a reason to expand that grid. +CV scores can also come from fits that reached the iteration limit, so +inspect the optimization settings before interpreting the selection. + +### Refit and inspect optimization + +Refit using the selected penalties and all observed target entries: ``` r -plot(res_learner$objective_values, - xlab = 'Iteration', ylab = 'Objective function', - type = 'l', col = 'blue', lwd = 2) +fit <- learner( + Y_source = sources, Y_target = target, r = cv$r, source_weights = weights, + lambda_1_row = cv$lambda_1_row_min, + lambda_1_col = cv$lambda_1_col_min, lambda_2 = cv$lambda_2_min, + step_size = 0.0003, control = list(max_iter = 5000) +) +fit$convergence_criterion +#> [1] 1 +``` + +``` r +plot_objective(fit, iterations = "near_optimum") ``` - +
+ +Objective function values near the optimum for the refitted target model. +

+Objective function values near the optimum for the refitted target +model. +

+ +
-#### D-LEARNER Method +Use `iterations = "all"` (the default) for the full history, or a vector +such as `1:500` for a specific range. -We can apply the D-LEARNER method via the `dlearner` function. This -method makes stronger assumptions on the heterogeneity across the source -and target populations. It consequently does not rely on choosing tuning -parameters. The `dlearner` function can be applied as follows: +D-LEARNER accepts the same source list and weights: ``` r -res_dlearner <- dlearner(Y_source = dat_highsim$Y_source, - Y_target = dat_highsim$Y_target) -res_dlearner$dlearner_estimate[1:5, 1:5] -#> [,1] [,2] [,3] [,4] [,5] -#> [1,] 0.0959171 -1.72143637 1.15500149 0.005478273 0.3258085 -#> [2,] 0.1077137 -0.32243480 -1.03557177 -0.108195636 -0.4093049 -#> [3,] -0.7237275 0.86634922 -0.43528206 1.105802194 -0.5961351 -#> [4,] 1.6347696 -2.91985925 0.04876288 -2.357504089 0.8127403 -#> [5,] 0.4757450 0.05665543 -1.50373470 -0.744076401 -0.2876033 +direct <- dlearner(sources, target, r = 3, source_weights = weights) +dim(direct$dlearner_estimate) +#> [1] 100 50 ``` + +### Latent spaces and contributions + +Compare the target and source spaces, or inspect which variables +contribute to the components of a fitted target matrix: + +``` r +plot_source_spaces(target, sources, r = 3) +plot_contributions(fit) +``` + +See `?learner`, `?cv.learner`, `?dlearner`, and `?dat_multisource` for +details. More runnable examples are included in `inst/examples/` in the +source repository. diff --git a/README_files/figure-gfm/cv-heatmap-1.png b/README_files/figure-gfm/cv-heatmap-1.png new file mode 100644 index 0000000000000000000000000000000000000000..13c0365454bc180b6c33ab3e5a9fb7a36a97bc41 GIT binary patch literal 35276 zcmeFZRZtyWv^5F@f(Ew)cZUGM-QC@TLvVL@cXxujy9M3226wkWaEHJ1*|}BszTKDq z;Z!xLUERB7uhnbLF~%G-Ve+zKAK*U0fq{X2kPsJE1OtQM2Ll6-gn19_VN%C40t16Y zG#3(*w-6E&vaz(WSF+VNG!`?qHnul6R1^~g17nYhQqeKPRYB*?XsxFqod}pKDvC#Z zWl3zXRGbc6K72KB9X?&lGAC!=c`h)g;zbug{!y}-u=%F-gM#Q=`L77N z{08}a$Nkqw2-;6obbGVg z^+Wsp{hW?Jyrw>QP0`UrCs8MngX8SwSDqd2y%|f z?y6&GHNAQL0P>r0t2yii37AfMN02?-1#iEhibn_Tp<`(o+jI|2@m!iI@E z!876KMn=6%I45t#OoV+Te6Y=JYPuTtI}-dF8FT8;(I_=D)}hPL`<8u769}&}I#YAb zg(;3glW+mrvxuEb6xCwheznr<6u}ogx@Po2GM-@FOaw>JXQ$_zM`yLHG??vp4lMXj zkz@?2jTeF-AGJn`0%W}=N1vGORgla|!U%Xych09h`2>Q5U)sF=7^zXi{67NSPBA`z zMH7xCL^Ai(`ue>X?$e=J-eI9x-u=(b(~VxLhgYy>|k`&Iz8tG zPNJB@7SR!L=pr!qYHF0a*)TaEnZt&>IJr1!F_N@kt;^|UtrpQPs+!Y2?{MTuYWnFL zog-*@F3W!Mtyk}0p+@h3O%vf2+WVc)$%<+-%gL89RXDFU8?An^D;`-R-a^6KsI-B(NdiHvD z1P(c3-{#!TzgV1Af3JnxH&0Pm%@NdGq~oSE3gYSQ>ot;+JH!YMgwwK5j&GDV$!+*&o%7B5p^TXEijYABXo1@9r4DRWngz zBJB0#Ql7LOw&PA0AvUI%?Sa*JFtPXANW8vat+*8$JLJ4)f2QEN(`@2E+N^jt^<*~6 z>T9~wc@9>=TjYAH=t|LVumQpTGobH>n%t+!a)Of^Emv!@b+ARxPTjYs7k96#cXJzH zExB*+-pE|x2Mp&&fdIj7tS(_9BLhYae1-u74>1RW1U`WSf1iLqAeQ7p{JRH&Klk0g zpTQ&l91OW$a|Q$B2a^yMP<91B&4Tts7r`0K`GSi2?XdGsx8QD_3u2}a3km~;_Nzd6 zC`2zb3^9zqI0_UMDtg7;heeJ=3t|Nqm~MZtoCgSUTg^|jdTh;~?!mKutS!$dAg z^jdS3w-lu8X$UD#TvWJSv7-`#|JiKSAgmdDtMs~~Z-1?j*lqWNl4!8v$)ti&$t1t- z3`U)eT&IoR%t$d13HU>*H-1Z+AUSQSkDM)(Dvq&hn$%c*eR*Q^es*LWwUy(k7gAbzo}GcH6sp$V>ld8w1Y_IPu$^HWXlXDy>wbSfpq)!)xd=(k9|Iggpkw&95; z7LEI3Do?1^^}?Xz<#r|Yi#aN}Ty~^D!22HycVXU7uTSR+Ew=ie4|^$;iUlzC2NO|- zL(%5WIi8oWk}33+bYe*~YR)ydJnq*pFON4ugOMNOxZUjMDz&hHr(sMZ;BM&03<)Q8hk{JT(*=%cG0CYC z0&5OpTR^E2Zjp~L?G5I$-$qwE+^%-zi??P9rOcM<$ovBWoK_-nIc84x(cp7-7n*JK zt`Dc*l`0j_SF}0*D%&aKnJJJ6P_5KF>R{0A2(?~o6Id2vcRXADNSqjjJZ(4V?Va%N z+na0MWsL)eNo%GQ)L^yJL^s3qdhYW!dxT0ZZ3YhmQ&T9NHdADM%}R#p*nuj^`qQgu z;pO?+GK0-#{()+%V5dy2+WKU+I7Vh|%&N|3GMmTUCjyhsGTs!JW=r6Ljc#skOUic{ z6Dt~igHbreS2*mpxvlMH)A^4r-^F53Lp= zbiO>DcXO@1UeC(icvBfRe*0<3%gf7Yqfbgy_dJ=&VW=c1M?ip%Uf>2|)SrC#VzS|N zmo*{eB(i+DKW>5MZ13&k%}%_RE-iQT_>+WKTOuy<=&{f7CXG& z@WFzfPa_q4&PRr)HkWhjcBhkM8I2m?2aAw*LqNeT;`4fzTsb&r3i-7N{IhPudlI;c zGxA{&5a!t0XN%>;GzEpg!fIkO3Rabq3VS;IsA=MPy%%frIc2SvQ6n?n=1P^UT+Y{j z3QB3DTaFm$D@6}zA{dV(4mX+4l4q=fu;?_6v^w1z-Rgg)Eqa0Mh-DL<1c&h1ZTWMm z4FT~4gNuiA9pB(JlMpv8g3dx}`nYBfn0tEpmjTEXNN?{%Auf6D5z z+ZlXxeW>9xRZjf~L1{XX+4OXGe&UIsQ0M*P&a7uBGD5wFrcW%uP9d2HN~Tt!rX&3T zPHLX$G+ab0$hnTpG?M?lISFHo)oxojE9DsR=YzOb3XPiV5LJ2Z#Kw}HJ2c2(NBLhy7TTOF#s!u&VcD}-E0j83>`Y9wiT;Y7gnmA-EsbC!aU>!1 z`|F(^c%P&_y_Ory-x){Qg<@?PLKx9&fw?v4iH;z>qhrdnpG%|=({T?F6A!_~nJ@d+wI9NCa0FR}Wr39SHR z;yp!}TtX<9Tf2<^G3M+>oELWWkd&sIlC(bzn4wOS&ahr|3>>org@DFEc14L?AJ%AO zjTXuTYG6Vg@)4Of{1Ou-gI(1%VmA~sQ*H>;v;a|>3QTmzJOmV%|e+aBG0rMb;8AHKMJ0$uj@P? z?C~g~=gK^w>rKY}kB|M2t^L*6NF(vMFb#$k)&x^|Jes_-^v*+jLoQdQ5qn+ER;PD} zhKYRMUYaK+*`LiHaFQ_sLhLdx@Gwk7HTS9wD?}eF+X}9|?S!#@P>2Y&hxbYZIPvd4N*_OE* zOrc|-QD?+hNhd0`ypg!Kui544wq`}V^L{u=eLXgw&?Z3w872Q&vc~zBrd1&dO%PR@yMOT&ct~?dThx+Vh~A}CjA}`t_A`;L@m;be zLYnG`xr?PD4O;*~J+{`Pzl_wl8A)zbk5&{_cu~S-PK?0I$fpI9RL*eUmLHu}xFNJr zYJK}@GXSXpJLx=~#p1Z>OI;`k&SayAm|QICQ(-l%+m%#>^9X~Ki{9<&(kw{QXNBOP zCIIIjh(ZW?`s}xAuac>^_kp&LNORptk&P>Z9UJF|EqO9q>S+M`@JNRC<$Qv&%0t{D zX(dV`%TQ5&piHNG zxrg^09gO4?lal>MUXqcEiF}dXG}7zw@n6qlPc5}Zem0mZd3CUBm7j53RZ>d4H9YT) zv+qRFa_DMw_GVp~k0DT)xmirW^vWO8$c|0;vx`N{HWVl%e|&kLv9)N;E-;E?eEY-J zWWCWM#jC+tJcaEf@$1aPa2u+lNqT5BGn#GCKL8CKoFOmQTH2bRpF^Xq)MzaG@cqXp zQCj-#QM5Q?{t7;(qNsYpE{bhK;^-DelmgoAcGR?UX`_QUJHLK#jkbZzAkbyXzUsCR zmxI{63_6C%!~~v3rn?Njq_n8|87TTgyIh5xy-dGyM&NkP%HIQgk>P6$DgSK!em?qN zm`F$uL=7}YZU4>I|AB@f05m*kV6l+=7aIO%0EZ*gh5HXl;*0r0fOgU|G06XpJ>aOh zVB0MJVpGyhJ4i}j?y6x8{eM<4A-EI_;l~@b!9smo(ywFnd)T9uE#$~a7FVmS4t6>f z2j09}#NBHLc*Nfb)O`4u5R*iL6+G5udae?DwNPy>z#D8fc+u&#)#Wk>nWMWOkIUUr zXG6uEe=TElzuamGw`UDbW^rLbBjG!?F<2HLgr@TOyw*%l8fz6@-R~yIjXAslshd)% z2m#2n(1w`q4hPItAZ4QSt|#k;m}HI-qcZq3)xK0cx_oIPIQX=F7V0gHWj5-Ew)lM@ zUB+5mng2*dX3KYEn$%vjumF&m`=;#2dqwiIB#vETI3}$0m)$y)!iX@}v@x~)q}8qX-EWQ`pYAVqM43Pjg^{UF{K|KJwpKliQ7{l#<_(R3QidLc;G5y5nasPTe zS85U`k{TwDLR1UnXr7ghv4oor|J7%5ne>7K{R9bv7+j9aj&RiS0~IBJ8dEsT4hSPl z1xe>x1O~xl9iy}xdpzG?Jc`G`G8lUq7gSdD6XT&^(sOUq_bDUo-&9ZnH} zgnv-TW=?gl$??4?%lTRq{935a!Gs}3HF~%lWr)&IcujWuE&L1$1Q9LciB1ZVnTY#Z8yX3c?+K%Uqwk;*=g?Sj9xgKg z!omaFa${5ii$*?|Ukqe-ck*i>pMs-O2+*=I{V3uNWBE1*(l=0Mlqq*>s&P5jeJLp0 zznhtdNxRW`&D@1m9+wFMfU5r0{^Ey3LdB9 z)YG210=Z=ppCasKAkCiHVohGt2&rtQx?C^c^wN5*0zdgyUrg$t)_*lF(fl==q z@H}Y>iil}nmB;-GDT0#l41UBr3>SPy1yH7c7MBYrZn4Yr6H0ObchVbx-?v7rd?IV{ zlbFH_1&fYiae`LSiPP))Kvx+yT|WRI?&zLYqgtVE)Ac|;=V^m{B37l{`Wu*OoMER< z5ypgh7^Amr&9w=;?Ax+3OVuM{#e@gVuCoTLw7|r z844cLkIHmC895hX)i{7Qn3tDm4;g&a?sR8CB;c*^f$o)aeF3nAyZO5%PmMeh#_>!deUx;ho8Vn(4LCeAk?n}se%qS8|@kFAMwX7!)*0#K{F(s*7m zRI)bn?GKG7_NNyR(ysIQ(CXLcIiN(a$wIb^t)|+B0 zCBcEB>`<%YVKq6+Z#ijmB9}FK1<@1!(j(N|q$L?O*@W}r6bS>=`o@;QPl%%e4?u^m80!iKb6OOIm_cBD40}>e%GlXaYq{cB1N~VlorYQf2*# z7{UE{h!+m1t!`rR+*v*4l7|i?+!hB#c4?)^khZGQi>2q8H_EV(kuEeo-|k|Qq3y%; z(JBm;%<)s}ZRCl9Q_fN%`^U;NYZRo-CuZL54O=--g%cm>K<2J?BViL6GBwVtl zz+F%c-4(y4xvIj-^T1Z4=CW!;IxFN$Hh0#+Ev0`kH&F7|Ca7$)vSP}!JDMpR7xBi? z7gB$`{z2OOD#I}np_!jjwU2Y z!X=}8y{D;eA0Mq?gpZaZ#6|?;PC#m6Bi(GmBpi^`V(j-Pj*$Lc`l_892)_%fk-dx1 z4R;|HTTD*^O31v&4PDaom*%9NhjGhi9lMsy#2&tB=2qYwRmw&<`B`2(t6Zu$aAZ_w zAj3q9&!k}iJi4R1n*mlt7&n;|n{TF8+fBcA_3P&NB@PtIiITcfR{>UpoDq>kJSpNl zTrFzJ$FnSa;-_AFWY&ohI=M!DA>~m@<79d%GHXH|SV`gF@sU+bS=4?PgWsD!(hD1! z)%D}aW#DRcVHik21&ujgw;$&|=xFVo-i0wlK}wLIElOfV+!GJ85J#ws_Y3Y-R1VCT zLDo{k(2#^enVl}y4w_VWjBhPs=2WYqqF1b<_Od%KFt%ww^rz(WQoo!`7_pT` z6fMD0F@9=;h{o&pf)!nF3j_dZoJsUpLB)e0(i@am06_cx#EiPe{-+I*TUm-5^FBRfP+m!Vso5m1PX-Hr-VK3lK0 zh*uMEysp@Evjq8DdUy;)wPz;U^oOE6YKE8m>YG;>F(m+&*7hzyF-hf6Xin`L96#pG6B+%T+691ZD6`cAN1uVy%Dic-)%3aiJs=fBEGm>P&kV2L{Vc z01@pHaiA0oGfC}JLaex3MD4R{P>AeF-RZ9bBVkyFck5@NyZ+&vSzE|^P4 zYPQcu%~(Eo4-N^}mko5$iQ8zzyq_m7(NV~~N#ct`!;9BE`xykXBlqOwUmHYV#O2qZ z9*gLG!VM%I4|046c?E6-F1_G_tv)` zk1!@<^;f^?&!=7pzb|dsW~ci_Dy4owWYr3YCoifQp?ViXs5B9F^eDN(>hYZ6EyxG@ zBp?=}0+#Jw?+7S0+TQIIG9->mj}*dW5;q5B?`DM$#6<{vTy~b2jHGW=_T%D9AEZju zLB#HTih;S$^LxvQA=L`sMF-N%qQCK^0l>W{&Nrovin}(1Yq0%i@^c<2$6zrdJ`|z# zLgc!DRyNx{0O9GH-Y50eZCGaBRydk+vSx_;Lu~5ao&*zp0JpxB!za1XZC_f|nowO4 z7uDia>gGhD%jWL5u-~Y8`;ie|IM`za&%|Fdq0A|$EYwksXe;|a&O>|neN@mAOAMj8 zFbLCZcdn$-=s&EDtBo2)qk1Ew^EYIYd+Ny4>AQW}apx)W=CjkW&W?fPj`JmhjHO_e zmY&mCk41%#?1Tvm^UPsEx)@??`;^UsRE~dO4FM{{)v*3U6+)>X(D@lf;vZu9Ng?~s zgR4cwAVS;!5EH^^Sb4AkM%(QFn9Jq^T4;uvR<;k12E>0f1NAfTwKU2W!GD>{Cer+UTV?#?zdMD&IrqlTUX6SM6B&sCZe6N3b3)q!Vg?S{kg zWU`9fT8Z3is#V$_sg#O@%alvyfoFTZU9s6>(rUKGbH6*QybD`mPI!J|HT(ukcy{NSzUo zhMy>_0BXlg0uGmJ3Tb-N2ZXMopb3C9*IF(ybzIL#wMi1I2O|+wwGSpU=tTpB_{i19 z#-l= z+4a01j6pawfZ~lutz7bTUM7>y<#Z9nI*vk(M7c~^g3B%Z=5Qj5t1aReP=A?f9~kLZ zTmPW=eES9Bk}HkX>XZBJDeQyHGr$L1TrY_GL&W+gfg(-A00n^M$kPFRKlrJ0iSKJF zY%8#;e@jD}fxDAi&hs9gF4e2I+7~`ZS_5dGP%i6oejX6?rhy_v$L+EOD4nHTL!lPo z11|g2s&fnVTT_+U|1B+P<8LH_46~S`E2t{O)45RJ}I8 zDqmf80O_o{`_;dLh}CS05Hy7jFv~9}pp106r}4C~KaYZJ1)+O9ypH+nyDNVO3E}+5 z3!d1j&ld`yEUxC;H#f`!Nw+eLA4VHInCHH=f~1?o;1A=&@5D~#D=&UP6PWoW@_M;L zzJK4A%8Wz-gg!W6j!Wb(W4{>Kr&KYLfM(0o9saC$gAm`y1mic=RCSqw%RWG3L4E?> zX@eZb$^X$?Odt;-f0=yf-TtQ*2nO5F3tVo|bd34GJuM#r4TsGF88hTR?dSH-Qs*_y z{-2`ZI84hgcK#H4-GSdOT|EI$nOlpm>E$gIc{he%CKU6C9uh0jy4nKElL!_*fg*h(3yk+_DT_MhpA=F9<`*0#^?etbvlS{>UKMiQSI1#4{EF31pdvsT?Qnla{0WoUT+KW?)-Mlz0a^78O@;bdA7ef$&;)uzpE{} zs`SL{>|PJA>bsg)lk$Q*lg?g@hj)}qu_J4Fx9O}xg+?8oOq=o`pH__FjogYtr3NhS zq8+Mfv@X-Eh7%bOu}Xe+hSR@R&#LZRmX@2SgfGwq=CiSz_}!f1Jqd^2hJ|MkF@TV1f~i%NtYO z{V4Q8K_U3IfYUQHgahC8hT~Js=Sw~=nP*V=eD5}_db;8B{uh_3=<%Ec)biIoe zGg4J7zdD4SHMnNCH;?s3SJYCy>35I2Gr(}5xX3gx?7U4XU^dn9KDdkVJ(VJvQ0b+} z_?2q47C2kgT5k)A-c}UR+W0;Aw@%wIPxp4W6?bed&riEtPRohn*x1Mn{V~c_eVgx7 zzqs-umg30vRV9r*j!MeU1Rip9a4_vkSMGR3nfa9pc~3!<=%+|hs z_*P?zW%#U|#D=J_4SLo>(0%Im#mY$TJ+vFeD`PYmQ$#q`Lo!#Q_yIv?srdykNNw2o zrL3s*?+nFU94b&w=(YGE9K{pNI!a%~mCiA3u=) z78pD&Wz3(H?&R)xgX!~_oEsgtaIw|a;cL{TjVj!z5L(iN+S)mZ%HMMsg1;a^PVdmb z>Xz1tN>WnY8gRi$(?%ti2(=%V+CE|1Cz2==2C4C;tgERGsCbqlc%t14&OjE+6^SU_ z8l))*z3ewtuUhTv?_r>PIhvsu>iHq_`$7yJuZ+q5o*2QkoDr}%ga^U1ZsvFu*LvJZ zAmFUOpY3|ayF26QUv7NM_3lGeZ}o(puUHSsY@OlQ1iqdF4b>=)MwRLkw6niDfBu4BQd|vw!fxDP={q;pX$IRxK zlx^tMPB(&LL>>Mvth%jxK2wDXdwj@JzT=9AnVrRTX6hLE5blaLyZuv^-N_7m5qQ(V zVI{S(Z=pSYDS#K}gRlzOH|G*Q6CbB~IE3q=!;4YI)6RYIcy=Osz1AQ)u4obwTjZ*Z zj*!TY{^Q)+J0lr%Clym7SHKT^k+X0&!Wzg8~sILSyKJ~qC}N#+Im zKHv6hCYCF+o@zAe6~}*(P$UyQn_05Ey0n6%l8Wtw8Xk#tF53_#yLpmGV+u1Nd>cm% zj7xd$N_1-c*61JW*zTOg9~AEPGoY9c4t*@pa0G&;yV+Q(nKh<=7ey&7gafDs{ zmYop!079I8*Vh*eu)Cl-988D+9+2(abd;R@jm;Fh*HaP8jpv*sV!7AT7RN2PXk6~- z)rUF~n;)4Y*XfX2Y^U>l!oya__Z;f2Ml7n8fE`KACfB?E`0&kH%?dXB-g16Psnpz# z&Iq&InwDhFQt1=d0)Bte72B7Pp*&Vg1-A0M`gWlTzRLpdn`ap-l*iMgrIK=M?GTr2 z!3CuBT-To+qO8d;-q_oe@1w%G5nzKBx^ZBiJrf(nV2LrBfC<*-m@(5QUFk~_Im`n8 z@Vjru%1phOl2>KhERJbq=s>>F95zwj-!-q!8C#%DOziFrlfXFxaVbY~ZI`U!SV_nw zY4|Z%@6O;tt(#NR(H92gHeMJV64;TM1RsW!N_`)M*5<61ABYc-#r zKtq@MRzEVpe(<{Fa;HR+f1B5Vv=4pkTvISdD{+GLK4zn^J1>Eby2UHsOs^vjzyHX0 zYbu>~y{j)nu?r0wNCZq3i2aT0yd$7cD#ZX$<(IKQp)sT9Y$3P(H|1<9Y734bv&m)h z(y*DW_vWwFu~l^yOU%TE$=BF7-OUOuuDp9sN}|QxuCGkX&gS8*dT*irB3EBmiqcK$2e%bv_~4-JQK7l|}!mO3|}{g!I~I zo5|^bk(M|h)N4TIsL94)RV0NS%O64VP`~~(X|AjwSEJR0$9y-`j8VIpSfv1V-juU# zi$^AvhTU~sWb9$tH^96QtL{`D&cbN5kKXn)x@r5c`r(Vht1)pPH5+wN|1?8Mv1fP% zeqCu*dGh&}{)6K?r;`z@kmJzm$zSCzHMaeeFmu`+(+{)rASHT=^iCDI;tvX{EChSS zlz{yJ-n4gOmIg$OQ+L%R|M>~w6D9{(|=1I{h*;ez{WJOhibeht! zQyACx3iGs|P0h{Cb@*h1ZH-THst+k-s;Y+}$1ZNK&rTA9{196({jF%%fUIEqdsCnT zDDo~hq|s_H?M>b83L>essYg<3)`2ZMT1Q5mG<}aeK0}2zIfCQz+OE*`ahe)!@j5|d zty+u5;Cm^>;^Whc%();^eQOg)p%pu$W%qc5I9qQm_VG-zNc=HI1#eJ0SDVv#f8W7a z%hOc(2u(14Qc1@R-JHXpy{*A`enlat1U0(PUrX&~ciH1c7f4-_p=7`J#8h-nTkp%F znKGzue0-<0ohuPj)>2^YsEVY+rP{lBhqW1OAADAv`DGx_ZSH_?XNQPxs>E6W0r!$; zBD+1S?!RnxIO+r?OI^Trap%les|l>j}-l?wwS?dy14^l%qOSD_o8pRuZo-D; zt_X$4Qp?md5lVb=)08Nv5HU~)AO8$kJ=GcAnCI!%K4uh$sjhn;k5|V+zd0cd&vvLs z(C4^a?j{lIC~7OnUDbYEE-l{4KF11F163_@M`8ZDjEHNN?|ol;8p^?2XRE0Zim*X& z0UO^un7N0cfj_-`u!jUW@1Nq5+XpDVz(9$@qlMtvwGd5A#XW>Tk-^F*6WOe`Ijf~e zu5%}zEi59;j3+BMo)eQvu5uKNp<6z_hRVgN$M@%BY5{8m7rQQ>WF2oA>$@mzPXWVI zu0`{rTc2bFtLR;_m)CB3(wor}GHa!JD3gg~Uc#y(66K{Yk+&ct9`Ey4dR})Q`azjP zzb-G)UYucrNAxiUL>P8m89UovNLbXbKo!0WXRFFy)8SbuI$&1p1U)W{VEAw*=uKBI zsoB#&>sh$`w_WO@?lZ`IfDMGqSBJNy;Uz%c>r%Vr8PPRXCNV0OE?r*PozZAq!41rh>9ENHDct!3CTmnkN`^-38cHh-K2h zxW528>pkE_0OUVdy6|Lrop82w*FJQ$4ezH)%&%KaQ9~%`{1+{D8Z3WHGj|lf0pe^? z9zonk^6@S zB6fE5VyRd{@IUPA=49c!bQ+VUZ5RtE;az(Vck7LY%U?I~JCOZ)Fp&jHVaO5U04yVG zis0bj$QDz8SEk@js#!G#p9gp56HXKk+q^3N66T2lARR{Oa58Y*UQP0e0hPq53JvDa z0T4`lF${?zhwY|sEzP>O64<~>k?{baXaQRVKcQNcHoNm_s*u8D4qps#tz-7@IH$cq z@GYJXOyQVx$FcK^$mOvxrGQ1+oQSk{7=}VFJ22A}unawF19d>*PWPMNK%~wx8cC!A z{1^ePz(fqOfBApih`+5%xB)~gunuvuP)z_R(S^vI5{P>q`x7Z=mX_(zKM%@7~WdG<1R`LkzcDk%O69e@K&Jel?* zpoj+mwsw^ixqjKGPb^c77Hah!fOlpPaNiOpXMzNOEjLcr8j6kO@NE#KImQ4s7Kv^u z@->S~lSNP*2?{oL$!w$5is}>gM`#xF8R2ubrkx!&D0xpymHDtgV`UJ>Z+2*C%n{mI z`^}*CkTL$oP~|Q4Pg_8rp3e)^M`&?c=JoYxyf4JQMq!i{x}D3bPJ1I54qktJ_-*RJ zMsY7xiUpr{_I`bqLqO-xUOcW~{__J`_;YQlC1Qh!_F>sQ+G&a=4H&!)IBjV5#K4UT z=M6L@fSKClKow79Cz>gcxy6;*)GbCtxOSf&(u2(REu_h3~wFmM3iIMa5pox?1a7Ze~zQpA?|VB zz>B!U9o;QVb?qBnu$%hgcC|Ax{saJqc?a5zr!3CTaeVzFb}zj$cQmUGv~e*xNQx$s z1<#7FIBfeIm_wDabT0BXHa8n+@B&2zf4U9&R)$Hwb5ZB8fovRRz!^(Il?L9ZbM-aSASis-xgDCNxM zCoiDvWOx5tA4GG_M3JT*`=>C&>-Bi(HfW@%hN2YT9BHKt7`}?}i)qlM2+;X!A(NN> zX#?U{h6%kl`&pKhoh?XCm&E9S08YB|2)f2#vMZHN{^nX4N*TAXWNhN{kzb*xirwS3 z0u!595)O+VA&ptOs5@2wB?J*K9C+JR;6HwsCiWCA5bqcxB8M>l(@x<(cQjjUI9H+& z%P|0WC~i3Hccd^wz$xl1ax*0)TxvTR{A zg(2oo?XFI)s3w-Mx;{cgBrpUaopLB5NPG}JK1@zT8#{nTQcV@86JFch5{QJOPiNGf z|EV;cFn!(u`ejZNj&4ShXijXXs$sAVkwwB1!&K17Bnx(!OeZWS1Q}E*NwBwN+9o2A zlXfB-7+-*An-xwY_y@HJ2lS*e8eY$#1BT0iWI8QJwgg;yL4nL%sTBGsr;~Z}EfRmh zwrrq_SxZvxba(Pntvw9IMj;?Rf&jjW=MB_BNr611U3&WB($O&|g%%eW z>gl%|Tf9e-&^E$tqL>VnWPw^C>Z7zA7k&J8OGg9*$T)E9uP6=#r|c}>&@`aSNJ-8+ zolpQGD^6OGdLH*E{PA}mQlo7wJspb3XfmBp!21u5&2vE2w{$>kyFcuPwAA2;F)-TE ze0Wv=_4RexfP}f9ubObpgsC$`J)|GiNIf81&26{C6aLg~#wXa}ith8l)IDaZ1O2Af zR}^16{PE_VOouJ4uM@4}meQS2OwNyJMIyNaTCN3BnQ{dhFVNqaFT(9Uetvtt(h)s2Qd8`EiA`LZYU)-y1fFXvMcWGM|lgi(&flfeURW~E}@$qUEEaSG)k&mfp- zRU~wNRU3iioWBQR2J#czw<28%55*v$qhV`iKuWGM#>cn&(*Z?Hn)O@>%D3HvPy0=^ z#L+`c$?_SjR><=ZiKG&!AmZfg1znF1eZe1Q1E|BnP)8?$jHpm3i2=G77Ky+Nk`7<4 z*5l*B2yqM=cIAbFL;oym+-d_yILh)7{_p-kMXxD+ZCoFQA+`0ad6f?}9XiXGExKr$QQ&{VLz+&2v(YH#A*!&#u6`&J0pdzM`)^Zfue6k`ed#&0h2FYRW!TFAQveXLo3So@A(O3gY@+B+&V^`cI7vy<#5=KG$E^A zTIuFxSku|YrduIwZCnr)N2>~=Lx-*TZq zWpqRKf47+R!$Wkf^CDvAnwBQIK+dX%*2E2q7nV!)M@q*qS}lJ8ja_XYkX!|MT@F*Y z$DLJ3B#>_bT`j~3%5Dqq@JWm?L=0uR3@>E^FJXcn&I2$iRW2u^=(gbTDEXSUP&12LqZ`(r9b zK|fV7`4pky2=o4H3!Mz}WmCpV1p>#YYt$*n&mZB%gG{4o*l@ln0H3Nv9>?QbPS~B{;6;>iuZ)@@-vDvA8ROpiDY3Y{hQw+ z(SCbZyccw|BQ;~t^vz$RN9Ugw9enraUVm=44|5;b_KKPMDT8AlZkLEd0nANYs6PGg zY!4U;pKAH&_$TwsDSflwIcCTI^CSom>&GmvYS4naT3_Ffm-x(@U^=NvOZ@vRvOQ|! zQ2WQIabTz$`9Md}SHL&{7&zPmjo$!MO5d;g7@%8@rARI(IyM$L%lc=T3cJs%XNzG4 z`c@rS8(?X;JzK-Eu0@4RPWtDD*;wFwd)+(0>#hA8@RWOm0mcKgNI|mMtP3ZQ%T$a6{1-;q$);+A z+#Kj<-)TkfEM_yy@%ZE$!7AiQ*fD2ycS{$e@S@YI%Zp9q@DTuRBE)U6w)%DNCoAa; z)(?Li)km2FVK*g!d1tY~JfR8w1NLVyz{0V>!v};Q7$3!m{uWx_NOzze7z>ExGjKq8 zW}MuH2DJ(0dE5WBxd6OV@?@Z@X8{jgyYQt&am7|!t-S& z%9sF10q<&&b=sX}v_cA?hcq(n#(>_*TEij4KRw7X1bm`zuP=YvFbtdbx(xdvc27)O4ri6$`}uAYtcL$^STF|K8$If2&{;U_twf3fuUFIXHa%N(l6Gd>{RNRGeb~ zxQAMN-gpZRih!mOPTkDRfl#6!8kzland~TDkJr<$!rRDni1REK^NBji2#mzO1^WXL zSYp5h?4IxKNG0O?<3FpL{o_suwSe&Et%A=DbstB>Wk;^!H249FQ$Ryw34^l$L@%@FY{+^nR~R;q(L&Qf z;`g~0v_w3a;C6ukIlKgk9jd37S1g&>dxxB#YE`QpAr5MNG2e-9IGrxYtv8+~?|x0Z zAZNDN_ElW|)Vi~KoS}1g>M1SXclXqhNFWk{Tu}JrgvX;1c|z&6uZ*~$F!7wWm9jbf zCVrVHft*Qg-ti+5Tprr%-~S7M-3>Xu!1j9RGj$51%cuLt2kea4hOl_3*JeE2z`3-5 z@n5tLvl%C#=oNviqgbP$u3fVv^J-PlEKH7l}n*+n& z9`9;hCVA&dR6gtsQdZyV>ioXVI3I9N+u4SE$9ERI!gfxm_F8ej?-Nn$wZrSR%nena zXR-t5-YGCzcBuSDmsq6@IqaW6=;BwAdNDw(-RX(P;A8`M$IuMrW8|ju4e=~>x+&rANIg}-zq;$G}`ag$#RZi+e#qur0A6$@Mr?wdJLVhZ^t-UwEl;A9q9!li9Ngm1Mz z3`>4=GhnygLUOS_$Dj{L<=;Z|ULNRUM57XQmDC2bST~ZiLro$c3YpY$P)j00a z?+%+s`8|3j-Hli~VdqwvR9 zBFLIJk%34A-?TZEW+8b``XjU|>Gx++^nl{ZA6{hXGtd-*O^y;tP)CTE)jWZDOt)g( zh#pP&gW*9I3Jub_$~NkQ==>{h>qr_S3zWeg$DEvPZ(qU@Cj-{UgNbb7EX`)BIIFbV zqFHE~@`W~nLGjEy^qK8tvxCsuE=gpzm%eV|$eSTVR~~5);!WLej!=kJTz-NW4i@kV z`Zt~SgPc0QM(89B7SaP+93QDCaJxd$KgQ>kx!<^rWNZvU)280_VIp|Td{|E5!dt$3 z9?6r9NVXQzyvg9SI}VS2bNJ;wSFQn5ncn3qKtLO)9_;DasgNfvYq{Zsy5f=}I5aOO z+$1Dw-A1&gM-QLNehA^9OtAH(cjmt5%lV8P(kbCpI^IY_q1{9hVdr z+btOEcgL*HnlHmcb9@6|4{ua)XtS@hTFS1mr`{)sO^W1`3V@OrXxtGk1N71KgpM7l zY7A$+jX;_2J(>vwXPX*7+dNK!)YwDL7!Xt928i(~8Fz}?u2D-w=6?l*<}7vp8i+YT z$`ikx17$H{FWB($4=Ic$%8!H-Iys|5rtp>rXe?FX}aVy+M$Ei8TCCNrd3qt9MMU5^Z_eo$!SxCL67Y2r;HkZM_m41tG z^vqNNvbh#haEwtquiDe`Og?wZ3wAGAzA@R}H07I2hX2r|6o-7qTmGA?b^e$aDKaUY zBdwiOnu>?Yh@RbklcUd+t=5#i*M$fT0uoWHo>EHmR_qbq+}=7=jt)Q=#50W1(v?O$ z-0qsG$sz?mE(H|p&QXRFA2VxP{DN^g<~xjvR5rGXQ>~%w=yyekh9rgwUq|r76xej} zPL$q~lg@Yr2ceTLU5zFY@Y}G6q1|D9m>wB4zGdp{ZQYj|r&5|)`JrBkTC(2de<#et zwPMhYW?j2IIX^Ju>?%9kG(Ae=J>aU;>fb&c)4q;blJ(jbmr7%)R_UHx@k8QJjT!&R zJ2{*lMSSu1qgoa2zvSK1YWkPV5hcH1q>&6uK&~T%kmARJ;^;!_>h2mE^G{B!sP7 z{~g)QjR*7Qhy9|v&fOm)bx*lbEyEj(0>8>;qYoYWQB&>Qv)GZ~Mcm@ze#YyRS&dZI z{s2F78;!qPTD;;KWp?ehr5?4Ao5$tp=qaa1hx4r__Cwh)Nu1`Tgi?M2{;lgsQ)G`* zn(b^l0na5z_>)GV53kQfG=utDlPf&x>teCX%)@Hut!G0jYqy+T2&GhI};TOve1-wB804J^NZ(UT*5^w zwGf-_HLY*2Q(>BEqbW5C!aqxGN{SY(#rL`1e%*?Mc%79vhQ?VHF9*pc?X;o4>mSLK zgnLBCd{TL~|I$g_{+Uz93I-(2NU{p&=Vus-NTFgaF0Iha(&+@;sB#{SrZ@wMc)r-JnnOX-(SR5I{Y$Vhs$5*_FH&&D9g|zCsqjCtYe*XQ zM&rfrd|A_xYY25O`L3&{yB!2oa6~V^9O%(HE<%By?R~m2VU9EwP?#%;MTLTt2=&+Y|p>mhEu)fi>8ulb`rv zo&1dEt0A0iOv-VerU&dyktg;Mes2()R{@)&#*s!IWa;D!m9_DRr83f^uvKL(Egy-D zIpk;?n|<5adLzZX(+DcL%>HQRXT~zFEWI^#q%PYXmS1=rMo~)t`_c#`<}`=dnbYWosZh;h}v7-}|^)ru$o_#1Q(5;UY&&9EJp>#=NPpudqa0!(_Zi^BMq`(UMVfd1~1639DYa z!1oej)yz{k&XF%lR7orvG-h<_`8r;*-ZcJ&k-nP4Hu*ACb&bfY$59$8H(dDrQhfE? zP9q^=&4x4bqdxuitl1QD8zH0^%E&7rs$+L((;Fzh1bFzLBNEGWF2$Xe99^h2P-F zqmn3qH=C`yD?#p@&E(AO8!hw|dEd_zTX4iAgz27N zgu5kUUvOF2`TgDXNsybtVL4BZXy_J9Qflt?hp5XhE9DUAJs10?UGKVynYN5eq8#}5 zkM$G1BL@sdjrOMW&m+X>F$*Azs0I;+%X_%?=AkX-VV{F0Qq^G=UiLv*Mz>x9h zNl}B+s7P)x$3Upp^PA>8r5zS0|=? z0qwv{!xuO?nNcVL)2dhIW2k^l)p2!t=p)@h}KLJp7+v|$s zkS{8Estrz-TDj+fKUlpLo5FaHlf^^|Fy#PQHP63wPUqD^t1O5-yIT1Z` z%M<&74;%Om(}!xaK&v(ok3pSWI1qJefuf#&>4*i0l;9pc(gU?AGm8T8soiIm>gLky z<|7g21syzB2YpyzuNTRWL&K zW?*=l@vzMk;KAh&_Pt-tMspU=!Zx~+IGBJeHxy`HsDQ-`_Xkz~w(m}W<*AO*#p%Ve zQB40(Yg(e_pE;%yz>C2CpAXXa5g0z{(xvwa0os8hB-bgdg>@rRs?KhE=6)ei#vc6E z745RpvIlY4!g;79+#Gh2xzZ#7N1pAcK0ZFq%O5z347;!t^HedLo11CW%V^jwr~Cn~ zp+8&WK%>*Zd$w18hPX0R0TuA*r{%wc#)R6&Zdx#?vI&cGoF#(V`2vcgg7oEbonob; zz2C{)13W?Iae@i1Cx}t2Ce5fPjynn97U`f;yzyY;@VZ2{#UBZKFkgeierp5?U^av4 z5_#gvsPfGg?XLfzj!$)wU>dqX>|Rxa7orE6>`elX%? z+~#~w3uu#t!=4|KZOu1A3}4Xa!mE4dO}(e*_yda8kw-Q0z;{50!iN9|%(Qd8!Q(m| zcxi0_+b=wJpz!>~FBs3LHOwza8M-1$O(p$>Q~rCbU}QUmE3mNnp;nqzJpCSEwsGbx zc)v9t66E}t!AQ_6r!hVl_)OXsl-pC~xz(Ver^}aCY#-!uYcBQ~ptC@k76dNz^QK`Y zP-6ZBa`}kpuQmYo$RNoh!BTtY2lG?tLwovPegp)jLA|buJ?bmn9euyCEh3$#e+`AF z;q3JE^zf4SmH_lqoCjUc!)t*HGZ+PB&@Sh*Mrs_)&-|pI0IJ`z51@|2qM?2lzYY_& zAEp1*6IA)2V%7dZK^2iMuBsTlQ&t&@e#9{~WMx2w7Bcj>{^8N6`g#{yOCyqxy9d)l zzX(Km4UN+(kVx`RJuArD35E?bFzl+GF1@QMT>%4KN(y>K53PUe&)KW+p{*fGr1+08 zG+oeo>uuW909v|)$|fuF7k^qrx@hx%BtiI*ARV~C(74tmeEuiC#dyo2s(Mp?M&lPl zc{0EPzLp7E_tqbvST#&Q+TQ1op=hI$>yg|^;vwQc5ri3L4oOk@1Ui&CMZPZ>h9>7* z!apKUeIM^i9n`uYh?Pr(c4X4g2_l#y zyo6jS5f}-$p6{uiIqk9lqAYUQR*s$qnWc30~j9qdV6Ju8uO8+@`rZfze$?LEP#V+ zsT6o^R~e5*+us6o)Mcw;XPJ_*$YAoYM28e*%V)rYTM-E0Rz1L~aMgKWqSUmt2`QGn zled^ELjv}Mt$=p%P-NSgt;K&R7#){RAwP;5RS#eNBU}nrffG8v$<+3{-{*5?vJLfA zcXr4BT05SQeG?kTJie;}fHM!<*Q&pa->KuUvm0 zAju4C8GK?dc?n$VET0_#I?JrtgM_s2GaD68TmE@FoMUAHIg0q-Nwg>3G!20{#rhy5 zz5xClQ(=0l1uJ>%kVO9^Aq=5?=uO zHnTl)#0N%#=H=h=P17;u0#oJE1g^k9uGyZuHZr-B6-nHm!Ylb5wgd*OogPgXs_+S7 zRX!A!f%YV_(ZRx<-^M6S%J=@y3@F2f58-yl8>_R(Nw5O~uhMv4nN zLIr_7|GQWKdWDdHlaFW#arSeru;|+dlS>JS;5U_^x(}4tbG#j)bf6Gx!4{YJdnF>D!@xj^ z%0;2H@x>cr0TKVd0``OTr7dt?8CqHcEW)7DDiEmOeZ=`wU;FO}t)=^4CoQ@}m>PYi zm=CF#j<6Ul*rxZbZW1Y?S1&`P)NTtc1;ILoQA^#V29$Olf;E`>!hj_?2smkm;QMQ8 z0@hj-jXY(&W}_zwrRUT9QCJW#HKJA@B%?se_zIe=@W^r1iye9;z~9m3rt-zC8u+ts zf8(-K3b%t~=W7vAPqMu{GU5zFdHh&xC|$G|7*CvRkE6rFqY7M~?G^)TD;yfRrrrUw zhnw!>?0J6i-i>8!jAD=lJdKvLcci*2wvFit!`K zrftrfsd_ctR69GzBh%Z%7NQ5l^_$Yh7gtwt3qH@fItpg>5-#rUe(2J<9ngHrZ;%!4 z?Dms<#9U6CY`o|-tYKf=Gfh(EaEWMI0uQmWL8&QvTH>6+JQF3<|F{m>+0$;Fk+zk+>{)Cw&oDAC!rbOTQqyW&b7JdWeYOf z;eL;~?|F*f!7!8V1u3!X2%RP1T$Rn6w-fuWb#;yzerJWvs9l>|Nqt+T<#p@_M@b2! zR__2O63{^r)&^1|DTD)c0Y4BkmO*(L`#GurNUaBJopgdR9_+1M8XR}ETfp|5`Q6~O zX4t}kkHOD4)>1rw z%7nWs1HZ(eSd|X?dA~I#L(!hMYRs;aDK<)k_dWbK9vA5m_m%@{Pi20z=_bQm7*1Nv z)SY9j7t9L>uJ}wiY`rkfhxl$?{tD=!;ukI3p1|8jbw~dyi8@xG@$0KutC&ad9KZ!& zcs|yA=q_jBne6CW=?)LvVH`UO{GTxy_xO2u9tp9ft4cD57V>D2{mQvg{Rm)Gl)Aj(60b#%6t|Gzuub9)m^?hhRWBj?E`%8bS+SW&CJqMg5XVO0R*=` z!COz9Kcz^n%3gKg;-A^;TMg9bOkO!`EA%!0Vzq5q(G{3}Clya$I)yM&!d1GiHjwL@ zIV_#>sxN}GCeg?!)XhhhXLQ}6xsA!B%|7zj(<<}Sa%a(sEO$1S2BrQN zMoRZpQiU4zU$BK`Z{5<_roN}gqO=XBGC^3?mEl>ac0!lSQzkJ~d*M;P`goJ#4O*(d(H(y~XMdG*t=0>F3E+lA;A z<^|PbEG#T}Zs*&(fCK2LGiW*W#SoqEW$@&?{=K1=@muG z^7O0239YRPddae_XPiSSv8O9a#8YZI&+^MFjbeTlCI0d%M{6F5tbBBguh!@WKVivt zrCN*frj)Gn)`HKfemH`6?PcoI;zDWjcEVqJ_ZNh@OEdG9kpifr*~rj6rvGRO2?`T6t<9W192Q^nSTn#$zU zM!m42o}V;U=niBS=Q@|4VtadwsHTW_K37m2*h`~c^)7aD*zc1e@`^;xpTw1OHCCf} zna60&ZKImoHDkVRi^}is@f(mgCWk2w$7;7Qc&S8OkvE)JaZYNqd!4#oJDcP?Fv+h} zYO$A&Tk-zXwYI+X<5+Q@V1?o36CR3#2xcwzgZe&&;l=l53@|B8kMRYZ6|1I?J9Oo< zdLtUK0l@Hntft-k7rF@~_JckRlE4FXlzhxYZee$GFRLYC?uAZ69S@+NqvmS5 z9M|1`;j+t@itpJ?G=ZO_2zpg+-9TvCNixo@^yV$c8*8r;5|0^7$3CMg0Y-4$;}# zX16Fo@)IZw5+P4>Rv{F9m`}I^}=S=2IhXv4I<=m7_SjtO^%B_CNlXDk+&T2>{7OG!C3Fi zI>{RE;)`L-1TQbMSl!JKTrQcjBO~wEuO4L18q{#^9dHW%)NIoOX7DLx)jr}C&KTHA z&lb){FD5HY7PR_a)F&l9Pr`TSH=@K4C#SM95!^GMsVYVz;T{2+0TWh}e*3w!DyS~s zJXj+p^)k+cP#$(fV}44ESFygEc~)cD3gSup9yOS-ih%A5`Xf~4id~j@NGD6o%V&_GfYm zX0hM|uu*4kzXdWDeBU3mb+oP(&i4xHb6OxtCDp^P%rn4Vr!N-|ynUyZ$&%-i%=cDj zZNQ6plAA4$3b)k*ERM+aHez90|%0Suef)Xx-;t?GPr zY478hE``!vU`91-Isf^3VgTE#A^-*Nk^TndwcbvZh3Th@wTN}b(g3^osa-$DaxNbz z(vfSv+MM&rvjw(7&O+JfID{&wb)U-nUyo7h_&N9{htT${PwqKKXY6W7tgYdhMMljO zOQQSDt%zXdlkIu#FW?>TbMI~YI9G#jXSdmIgQl0gxSHb%G|ip(?=u-5*YS*(TN{tQ zh$$u&EINrQ{64YQJQ5#y?sA?>D@ZJt+{CKb-*L~={4c<+tp)! zm{j1Nys>0;b+U==tn+wk>%n-fvK1Y|g7Lbvf;%pi$Jvawu(>BO@tLI-&2vEdGphjY zM=B#ZzrJ7X=F*EzJq?RvTd8;2$;XEjEs})?lTPXFXAq}TuUR#imA_!O-ZLIjD0juZ zw$yHR~_at(QocG<)?em1O3_)kJDQbG%FY z0&PlNTx|TYWvp>!iSkZcigMn`8YhyMJ4nkN$Fg^|>ORb?U=+5U)R;Y}&Q4(wPTqbN(v<4KpsYA8&HeDYoR7|+7WA~g}{ppg}Ocvj_!{~2p(lbP&V zfjI&GiZ20vs;qdSp__-7xbDKf&;a;%%W@O;^%#j zm(tC4KCV7y&FnxE+gx~_Cy;MzLZ@6|0~wS{yK&TCJB1UdqSzH|(wN|Vpc=Juo!3Y}~6XW+wg3~AYxi?n7%qaf9Z@q*@N`uqkRnmeJ z5%~Qg=Yzo#*tAt({Cg0F4;*$Bl;lCX!l>w~-b`v0f-|M%v9Yuw1~ z+`~vQnK!SggDx;1$RKm<787zs)RL;3Ao(l)u+2W16Kjs8eC(TVWt=T-u3B0bJ5g1k zD;v``+_P`gF1Z(%zvjNv2@+i8LiJ=zFL^Hokl)iKsdXtcV#nL)&}gSujpnQLvxIHB zQTMi6#^)Hev-DV$Yw$RFz|`hJO`Q&92U3#5{CT~8mPT9jO`B^}os9)RA+m3$CWID1Wwx@kpS$M`*D6jDugg&!>{JPW9n^tzS9A#- z286~^lN6Wd?ZT;rILMaYUJJm`;M)||9fguOFZi78)$XeUhW!rka)0=2?vp9=!pZ!~ zIWuRz(3(CVYz;{Tm4QsTJgwR>QqK!Lx4r819nh7kypz+46Y?Bdf)Le8tK?b7Y0I;oT-V&;Eq-|T?0MC zZX33&J?&J5r8$*&MDN3wCz?hxAFIgGx|AZHaiUSXH8F2^ z=^MrY^fWO;p*1N%+IG*=22}bj?4Rz#J@tOCH(F(DVDJQWxm>R`u=?8=1tu5(-ms5t z6tv;Qk5ezwfUUNd6pW?X#c_KK`rLyKDkL1yBHEi@XIkVNoC|r>$DmiD>`1bnr+c&0 zz?obhsh!v!o9Kb@D-w7fp7P^RzIf@Q3R3q=qJ^l%KD|x&!WC$o^k%`ozeqF%dp>9 zaw8}JBcUY-hb8@hPO%$x<2;BHEP83J%3-L;@Qb~wVduW>kyAEhL{Cf;*0>U zf!@v9Ea)4u`$>w?R?}=O&r~Xoz8DaKxNMc)>1I2`3^6nfuwO?Oc<6@)>Y~bZ&U@!< zH=o!QbUm-a$jnz?mt;h6cdM#wfcR74d3&V>Xd?fQv_z-?ZAdi4!~*#)7W~)&eIkk3An0THd5v9WQ5hbz^?FckFxV@kfN+sxo2b<-=ASL8K6 zg)yPPU;rO~uIoejLF@y*Pm8d?()R;Rsd5o;tzL){P^;+Tv6TOe;wWZBbJ`2*&(zJ_G>8D1#SQ{P7{3R^;YC20)JF26$OACHic>wWXa`oA zv}#5!NWNPVt^%iJTa7Nqi9?{{S>LW6E6_3r!zX3dZNL5utX$JzV7#ZFKS#gc!d~m( zFZ7_OWVvxcUUC416n6KE153Q*ar zOtcbzn)U&_*HfvGuUg!42M(W(p;g!cGWiOpIhRi0{hMfT+2D1dlKPV4%w-p)I(BhD z0izH!JS+xkXS*cPb+%b{l(#Ss_nT$~j>Ylx3;>3SUvcZsw7Xk5Ujas6 zHMDW%tYayd+*81@Bp-B68T&3vc0E0RXAJf%V_$_4c((IOQ3qOd?ypoHfXIBX24|Q2 z%gHj9RBr_ECo}*N?ikDVt|-}Y%GBx}kP9BNkidjc1IG?R69U-vXYra#!{UkW z(H!SJoUTEBq4gOZa`Uo17eoe)(e3Q-Dev{e-9ffe=>v-}1W z5gRWVD3#ca#~XAK+eu4`+<~Yymvi&(ZsYnPF9C@SY9UtM{pEmH&C{U@B-Nv0n;Mr+`Bed?cK<^Z zbEP-}LVSI2eaQw$>%i5!%8vJPCHibgoUEE<1OFtp%03&}^mkwp(WQf_Ekg*NVmV;5 zm>BGb+`DsJ1Zp(B0!l^q1%Ox&K=$GH%?sxk;UwX42A;dY|?~`gJ2J^6DP9{mm-*pNE|2h3SrhHzHUMBf?x9 zcuMSm##2s~J`Z$C{lbQ;96BoZ1}Ua`dphHWy~S}rr{jLS=y8HVUvOhnQw3}b^3b%rgx7p=UJ79( zo67?F(CWL{3QG+q;$qn+{a&qT998@z@%_)n$bN?_A7Z4PBT{mBTV8?2o1-uy#X|z; z+=tZYeK)Ogg@xL>!zCEPdBB*WLyPMkQyBQrlwm(}_9s|KcibuNtkDuDb@^6*ceTyP zk5KGyiz;v5&$T`Q=KmhN#@-(D-cL~c+6tvBc-ft;lpB|nWpF?>w+=Es2hwN|;O0id z+-*A(mYRF~<&`Ivh85VgNyD?h{#cax%18hc-u$XN++e45dZDsadNQc0B`JVoJxcg7 zkdlY0fhv0V#Z&nCcSM8-%moOPP{8L9S&x8UCniyf;8K{!^Y(Py!fg{Dv90Q;^jWxz zUng8-A3#Ygoc#_?^oAN9ytkIn1gYcS!tpu3^3^fbBSUwi%8Ouc_v)Pq8< zV+Vn{K1W>(#?B(RX_C&vfJNvaVNbz^gHq7@Rt@@#<*5{E?^WIVIU%<84CW}&J!G!a z#!v=iq{Msuhfox#36Bl9Y0YAEKo>uJ8<_)kQtUb$#6Up0JDGd>3 z`{iTEjScl3!PDhoDggo#8vTp<#ob$>wOeVw*>^o zFVeWD@ZB3jYdztFf01UDBzzKvbHVJol#HQSe1k%EC0_+$Ec_Q!lDa!&P!IWE>DzO6ecrkr_ zg1mm7ecp9U{ZQWoWOyBT*}bBWM6QD-ScTP>w|?UR1h`qwoaja@eZs^Thx~9eOEQ#q z-vJ@j5lj;vThYY1>1Ki8??IbL@Z~&vRjp{Q-zb{!WMSv1n7k8&LP^aB(|nSwsQ6Cx z6SBR)Nj7*FEJ+!qu{Eq3!O1E4k|% z&P6y5O9>JTIMg{!DQnx_U{;~3E9m4hXN%x!A@5HJwqbHhHCR2-3YT}hL*syYTUAAo zRR|hlt@=|fu}$G`$jYzXT{VAffM?C3tq%@K3FAzC=Ov^%6mQ&{pa)EnZ1kBwN1nIg zF~>@IiE*|>sIp|u1fvV*Q7uHE!Rfyuxl9xj{n7Pj*i}52qpwn!KVu!!Sf4gJTS(Tk zK_n9Vi5hXo$m>pTmE;pqdsM_6OhtaDVqjy;P0u?>l{0~FV%v$>f2zQ+(wy-rF>$kG z{aHN(o%AWM;Q?YierNCcqJu_ftLd#{nf4d#J52;qwe@;yzky|)<%COvA(dgV<*EuL z&ob1xR-CXg+5#q#={1(SL+Zl3^jjpKp8Jiz^n(*0!5B%=Ld#9Yg9U zXXu!EE?4~L-UuNJHovp#hs}k6BgoMvT{e>cO|~$}_Le%iY)g*+jzqnhruM={^VK$ZF;>+IS)?pJ|JUVW5Q)CX;iKwsu<#8sKy8E>VNeq!LPwQrB6{;}r1{N6tHZ?} z;b5DB@{RJrN&G$NyEz+QnW616lH_d$@8WBg(Xk--(w0+~&5G<2@#orW#&FyMH7Il+ ziL`v3L+;P<;-g5u&l4_6dKm4Xe-(H`?p7SiH*ek{P=<=$U(5qe&iTl+)4azzOsLD} zkoIRt{$d-&adLo!GbJ>vh7u>ZKv*GR`TNza) z_+>X2+p?bS5Tb$k`8zhI`y;&>`wQx6! z#(k1y5NK3|LV%lxM1Ef@zcNLC7Y5O^`XvDq+WM*~5xkLdRL6DOa<4P#fk<1vP;a#0 zB5pci!ol!C%;Sz|IlTMT;%4T{c> z)VIzr&}C;*?@p)<7|5quqjfnIH!JDuo3P+U{5n@nOF0VAJ2Cyr2oJl5ku}LkojRji z4EFSdH%dfcZqW+~d!P;siRr#5FkMkPOpb*P(i3-Gs&V@SyjtFZaT8hAcH3PYYxL#2 z&p6GPFS_D{zRSDxr>M{qn?C3s-dANsio{8w9xnEN-to&aFJbRGVLBV~umvgdL);lA zB_}wTxB?8TRht8&eKI|hwDq1!(7D7)PWQ@R{2Q}BR_Co(+nb4s@+0PS@A;Cer7)&1 zl>yK}tpM1-r5`nUN3ZS&rjT<@TKTZ8Z#XTkz&^DI8*%3dCO8`v9(l>VWeyu2t&Vrc#MRFScG2&! zln_#u(@sElRFRX@Ij zb7OA%BDO^1n%Ef25QeKPnbfVS@X$nZQ%M|b>5YUOAKH=RIrp+PC+xLo1O>Gf+M_|X zx~@Q-j+q-6K-fd}ed2x1`*caPMF}HSR0%psT8Wm&m6vWtSj|xH_|YH+LNjQAgH6~X z8o~I;M5YHTx-ntmNMQwVyN>W)KM=I(dsD+KuM9b@!Zzm*x`)1nJo&=B)w#9BHQN3R z`Og3QSo-NQs{|^Zei6t_8Bz4ZiLeWZU=U?7*1Xu^KSrEK+`GRq`aMER5pm|#sQb5F z)WU+AWIQK{;;T9Qp$fdhvmfKl9y@*Imw8i&NEps!vt$~aD2Q{BP2n}OPCXdQWCsrm zFLM;2!u1@H5EDl$GWcf4fP9^C3gzoT+Uh`YzKY0WZAzH9>T46Z%s0+W1kwjfD~8hF z50<|WoQBLfu+EB4A0%M@_;9IIG@G;_3nIc1M&2T=WE(Lotu|3s4hF(O%XG^K0kh;B zMlxy^SAMPtchx~gNsNz5)cXx0Ph6t{d<7;??pR*iP7G}4S1;RMogU}S^e>>Nce1t$ zuZX@YL%%$!qf9?^)YI$dS{2N5Zr;}EBEp-h>zoc)Xuatk+LPIc)Y*L!5nNbRL;eoq zm_Ylv_0ZMjrfJ0WF>@?}2ElT&NRfU+0U{@_x$|h%VN?iMKAZX}i`0;I@=Dha>zvgR ze3yErh>E$KEmdis?8E8wSIwziMAEW0>N9OtZ?9C=rN@oOMiQPRWa*QyMWiq}(p%x` zZ?!1~>ri~QcV_?DL*851Y9PIcLDwd0w9@{LN$e$C3NbuZ|A1R}3&!i6uqVY7a1g2| zm@fRmQ{RtsWmvVawaO>HFS0?V1i74FU|7vzB#b2MTB%|eeGo_4Cv6_M`5w`>5j8IB zcXcy_8+j-+$aEmN(C5dK>+ZI%#0oWEOY|n?)dR-81yj`(b|6AbS%o2h zcxp*_$a8`@)r~yjtb7p?`vp}N-9gZh>gGL`mo1&Rc+XQjv|@C-D=$s01gi|PQhj!P zuAN87@trs?Q)RAdQd{^=M--AP(VG3D(!B!F4rSfA5IT!DPEo#(SNsO{aD3VdVf{Ja zINZ2@Ns_*>eU7SMJ=x>~s{q}C`nat}cV>~rVXd*(?nSF?q3C|Poum%Nbw(~`&;T^> z4wxCnHRO5EV_j9?GHblh5$*F6k%%&?qQhlrDWaI^X2UrlmI;4f`%O#R_mGCQ@_J|D zkR&~IBuuH7uN0iQ)XEx$?A?v)bPqez9lr9FaYcL_XrjF JDG<{2{a?A*fjFMs%XYaMwT0Ie}%Ce|PL`YCjP^j{9(rQppFyc^9(3S|VfHQ5;(kD<* zAT%o}DODROSt$ow2WNFhBNH=OGkY^ul9VL*{m#%xdixm=kR4Bx3sy7Bj7Axz-=PSuu#>NMLjntt6~G>vqslSeG7t zV#%#j%>@P1-;CVJ=8WMZ?tka1ZBA&i`8l|7yOnY5x4qN~>16qCf5L<>#4-q|*HcntovG=0^0{cN#cp(+a#0 zW(M_y+f$<#0Ft=xL!36gLF^W?%>b`qb=5;@5qw2IsTuwf#~E%Q!%SZi_0&Bd4!=!x zr2~R@Ihl_U<|!&?pd6Ix=PgZBrW*eleDB{NvuM*jAFT4ZmfaU;8Mf&vh0!^AH4Gz5 zV`ITJypq+bwg=DHW7nb4#c*(y3^-iN=Q(k{#FL628%ProxFY78uLHo^Bn& z1HX=>AL=;_GLYQ-)G|=E(8-`y))-l;ysxRqs+6o4!v~|aEO~p+!f%?k@XTR+k60|M z1gA#?^35Y9n2$cKpJ8eiigs%ie@&5mG+=4O`9&l0Q9lF4wU6EH;quO1EAtD&TD$;0 zGEo!_n`Zr~WS_rQJ>549g9cas&ua^PO7jT=&{=I+M4PG!Lc9)e z*$uI!W5MWFKDvf^g-Ap@mN`55mN_?*D+kM+47X2Ejqj|V`u2B5*(D2}sZK^pI*%7gXqmadiF*7_fY%`EFZLcrrYp?aG`L$+N^OVc3E42lY zHH&NC+(f4H@N=iZ_H>oOHlGg46TBa+|NgvYBhNnfkS3Dv4+q_D*>e$PQ_*}D4O^qy zyj*P_%{v(KK`7tvp3MjruXG&INM%J%CaRPthkl%aOb-XVvV1@3-| zB6F32zg$jjh2e~S41Bmnal|?ug9(>EwZ1(#op9c*8AMx)vdXNhPuq*^>fcy5Myljw z#6#I^&!*r1vC~X4T7XucVz~)b^?{SW(?R~}5r5t*-^?ZJ75@XB(6vs30QyQ9?8v?4 zAa8)h`p;viGSLFhD>YBLZsTPb{>k934lNa;;ZpMbOI=TUnkA?ipP%~A_mAGb=dcsY zP)*s-u+KD}$UP=ggFt}bH-pHVD=9%S0?!ekpu?=7K)^F-;6VgDfLM|Z^Y;vlcsA_c z=g^ifZ|Y>XSV2LFL&-}^yz_)U$b|QNH#zkY9|PeVg2+#vT0;hEEM9uhd3T(tO#;+k`xUz7Wm(TMjX1EI1nTk@V^5*h?xyF;GB{H zOZ)dD*-&dD;xN?z9I=}Ss!(R%75_jd{B2bg>XhtnV_1F8Ao_rs1)A*u`F|P=0A72d z|JMam0T>){?W1^?_J11`2VS?p|NH9!pc)crq-#h8 zBLZ-{KsSJ^l;gIoMWiJ_~oc1OdZg18QuaM0A#q?=>;i3fS`&crIZaiYW z^`ye}@E0yRuk^<-qB54Rn6jXe-#cLfJilJ{ z&$?)bMW?=Ipqi!S+v^UCCg$7YdA^)B$mCB8ixuBPxDm*Y_ryTFVGj(3M^{Mawc9wD ztsSegQun%94eLQbzP*}vFzb(}0fvIiLD^?z6N4#k{e?ljAWFCB=lLHYPSZXNUv1ph zi!nv(oEm1yDR+;b!x;kVrIO9h58GmsYnv569JD^?RW~>+>r+xvelzZgY!u@8eZRK0 zwvnJBI=b%wGEdt-uR}6i(ts1a{HS<+AJ&_Y2f=-Skev~No) z90NL%4p2ArpzMAWRcU|N!#9DB2j&!s>|wH0D}{*1TJAHMi0NFNmEh#Y_BNeT8h0Fr z$*;aYnL-*kLuovUucL`*!qCZ+XJ%$L#KxN4594ocJR>3^;wWVz>DvzH8$VUA^o1Z| zZ_HGi{1|q(KD;?yKfB$|vU!{_D1&fh9(7EhhY%FxIByLxXjSS@Hk`0`T{j>7P;9;3 zB9<^s@J)=cO^UJJC>jfLGBY!Ok46{05$Cm=qyAM=XhJ6BUZ#&N z3{htceuWx;d%ivR{B$iA5gCbP(d$xZ^T{TBmt;YRpO>ATU0e0(Y>+KzcW7ip)$91@ zb=`2&wev=wlOms!(p&T+feF~aOf~~i5wsyE-zw4;CpN`931LY1Umsex-U+j*%kspy?5cVds8R&2QPN$O!LrX#>=9L3(#f14^+`HWo*ls^& zek?m30jut^wf)x6d!rAtz004=Y~@zYFwK4R%zpfDs+T#C2qRD+~}9z zJeGa6=G(IF!-c zg^{4+;p_*Rm4W5A@a?dkId4=zr5yWJ$noC?0>i`Sk(_5PPQ%t+(d!)jxIwrh*YVyCE6xMpmGMPYJf|4zWYR zsvD>Y!|WFw3gRw|TvyryzpeFrq9fc7Jeo$bd*YZ%r$*owP8?#pr@q;2q6}ys=V#p) zFjsiHU;U86>{k&r`9xt?KwqzXQ$sKa!IFMOR^~ZFJeV2-fc^tzk*i&_23`M~9BZ)-3`v+rPEDc1k3C&)uBpH5PJQe7gugr{azkb0 z!;enlw5*+rf0(DQ2(_A8_|;pXlJJPiCv2qM*Nj=|j&!XD@=7CMzP28u3f%B+Vj!x$ z_Mag(k@x*riAAtIPkz0;vM>mj5HU`0+UZaC!@0S}(LC9GkB7FJf$uKcuIbgzI_Wpc zF!nSj&x5){i+z$X+R;!xmSwxI&SRU*Zc+y%InX=OuiOsjGz#4e(;TMCz8rPcCHi)7 z$6;z3gFY#DJUpBa3(5qpKRukQi1@5_hTbg|s*rQwA?(gKR+4>UKrQp(L?>DNtYJ2V z6Kj8SpQVBq~jkT`GQLA z4v-5t%5V)*a~<7ya0(ntG|NlWEl`QLNA{v8UoVdfYDRUPsc;6V7M`w9jWyWmW_oua ziOw67ZTyJrLm*$Y?anwn+87Q)BOPNPxFMQNN&K}wR3BQ2R}kl@cXzp8IPO+Neqe`u zMR@9z;2%>@?q~TLWd{cw)8)F>_WbDZlh4t?NMWjhV6|)-OV)~QO_Q53MGU+|7z_4-}UNZj&r}EA{ ze3I|W{?Csa)}!#uj~@91Hd1G+Cx?#jY-b^oNb4TGkWpG`6tbn0FAGi4`n=LJfvXD_cv9H@|*3dTT&*6E$mK5TYwrEN+(-i$qDGk^*H1f#xVd_{-0AE|t^qv3w8sJ1QMbil32%UV7ft{+x@ux+Q z1qRy4hsK_0yUM5w)Y*S?ju3)eKorH+t<(!KP{j{{Ex@f3e5KC&t5`>CV};<2SB~rP zlHOPUCod7S((;oL$?Agks-U5%pgf`HCwTN-0H7B0K&g9u7P*2rLkYZDHJk%6Ik0P% zz3NP5e_XMg8*b2_AFd+E$+j=slQ7U@IU=w*Uiw)-D@*1jJ!_;vR6tH)TxBZ812@hE z!G+(CV%l_{#>;&L%|(F%=B+mV!%uBK22Hfud-vjW$$-*>I{i$ADqGa&oyZ3PK$LKZi0rTIv2 zuJNg2FCgp9@h$U^s)-!DNA@5_#6ZN;SwGxV%#Z!ssrj4UQK#CYcEyCHKl4}_IVCv- z9BF>pN)qhclz&_W*-3|9^7IQ|)RP;=s&Pn43|9w<>L`yi5)Wrqu#hZ9SB*8ShS-;R z-w|FTM7oJBvR2W?1XI;v_7Q!<9IDo_1Ja$mxejJ?LT2`ARWz?-v=fOY8&OVPZ#&_` zQSs?qD^_r%22XQ=az=+&{}-WVfo7+bU^6wp{V=};sbM$V0w z+K!;!hcF!e;m+aCD}3QKI0|#50?U)ZMaC)^qe!?@6%yPx2HAFRB4+V#S|RsG+;)ck z6^9;!h%-yph98CwMNNjnNll1dw%vOR>xT7A;IJXQWmc()b1vpW75ERX7_ba9Hu?&e z3rBN7gk>xYde@12u63kOubZ}2@sHk?X8YaT@Rfm z)`aI0$=77{fY@>(K{LmOPaU8sLSA3qx*vjx>c5OBC1uSX?rn-?^bbZ_KtG}LM880NlWMy5OL+qi%wE&maLkEvYixnII)vS1;F%~u)+yR{#Oyd z1vR0F=E>EP{#S#4rVxkT;7AD~1+L6LwSFHx5LgIh(hmPC?LASTk-(q7;~XITEzQMW zzmN&^W42*`i}7|7fIjdV$~Tn#uj1#76h}+g-zkr>j(Tb9TV{J2kIlvxi?5^I<$-~L zI;+L%h1_2lbGIpBc;bU~SZ^ zT7WB1(xtI>y^X{y72PM@rNU#;`9|1RZr3nGtMWCY04k;7k}9saAI_X-{p^0fvW->j z`JpyjrSCz%-dcm>`@HGR$?9FqhaAI>ptSJmjr@e;wrBmRN&_~a7B`NfO0hZz8BhaP zhfQB2roRvotV#~Ung&YiUk4D9m?>NjEJensf(K#|o}W7PFZf)P`A{;awyI#Z6r|%GNf+QV1 zsg)3=?svP1&w;4L+>q+HBmOmVeL@!14byUrWgT+<9 z9ISa0{AzUB{*xYWEy>LSsS%5lh38(z%~hfL!T{Qfr%|;3?oNx`dLSOW&5zS;07vP{S{pTiX&oM`FBfD9J&=q4 zwd=|r#`C3~*E|^sZVF6-nO8j#|JdFxG|)@#&&<|(lPjuvN_wx#zBtkB;gCriq}o|G z7fT^E@gwMQ%4y01qEUrP`D)zK68*LiTmS}v`3=2l@Wn6`417*|l;%fy^*saDg~K@@ zLMM(N!5!QHx;p;`6xNoa-^HsY%XRr~g+#?K4Cj!O@gZS`sDboIh=`-rhI1C114)dg zwkqA8?OTu6%UT^F%-5Fac=U=7kY1AopmLiOb^sV9Yy{=Hh%4Ya7pRi+M-lNTeEzBa zh-VO9akSW68s1fh3o=re{i;f5R?7V``oHgcZyx$XGfX`D4z9gV-vJ_Xhz_;Jxs z;h>SNXjVT)+3wO8VN;^OO%X#xnfIgZAMVM8eUNeJ!L{bU)okGJ7K+vBeEWb~{G8F# z5H3(Iuu1!eYw_K=4*=$^F{{g&@`o|BPa*>OgK-hzo?iPuoQ9DId#aZ65#-r*+`yv{ zWcvo!caP#E9-cO2jZXhg=PPZ)ZP1mMTTL*%WfssC>&5|r6@UpdJD90!)U6NtM*6U=*rr*(y)@6%4e4Z;<8 zN8+)Rj7xjAo1`I90(2QJr}+>;z4WTl;hVtvjU+AoxBKdY$t(#ffyc;YJXQ1}_Xo9t z>cA6M3@e^(*3aGm#?up8JbyIK;%Ik3AFkccT7JJ|f*%VignU zaY%|}ORH44cz6YFi!of?tYzXD@u@-G+^Ia9uaHSVi&2DJ6M}f8q~*yB?;=}`OJYKS zRDt)x_^#B0P7HgyP>-jAr!#}A3u?htU`Pf-j8A~!WIHk(+Aj=g3<*BbWlI7ug+W4J9Q10 zi~J3=lzjz8NaFfVGt4^lf+QCk2uV)y?LBE#o(ZljhU!~ z&|Cq0Whw5^-0{v&zsI{hm(X3-8?}x(IHTa*ipC{xMK}!2FFEa;AOkA?nBY->de|>g zu%~)qre{}b|Hq8Cd%;vO8hY=$Pa$mExcE?$B#*xK#tA2e*nzP~aifNpC}9i;A%SXw z@ahpKLXv@@DC%z*aJBYSP~l}S3*V_I%&YGSH6{d9VrN60T`~=w#{DI@7bF8p+wCPf zQU4>%TYwQ9+80F{yo=- zkqz|&4w3Ia0B|D+004uwP7?CJEw{6PYFeNqk8(_UV-{gP691R`k42L(jhsDE1*_Xd!TGu3SAbj4s+DSBUMh&12SK*@jOT~K=YwsfTdA1zp& zRj<`rdPL%TyytHvo?4>+K$Zv4A7|`QiuI|!Yp#YqH!gg4-lv)&zHy`JCp;N@>!8X1 zrn)Ht0Ga9HSGKxuV={hFW8)u85q-{VrB~!7b5}ns`}iN;$^@oiL7FQ&pzVT_(sKQ4 z0_XG%bk2d0v$Cdx*j51^wFZQN$-%r{7*x|rpK#Rf9h!1`w~ zR)D7}djMGd;_?BzZM`N(i5Ehr{20&#Vu8S(UvdQG#lCrmw(@P|ufhTvZVG`|D!|yRBwqpeyeK+zO>W;j~Z$ZIug9Gc_(^i0WQ_jkmj3S>AnjME3$ND=9Jj zPa|&wJtVKXGn;qot@k-DL*M*1Tw7n??7=rktk7#70Mfvw*z@gYpWDq8=be$9jlBx} zpJ@-XS5lIaL5Eu3oi`(?X_tP0dq!{huiVX+TYYOhPmM)DzD#;QE)LP~W$ji%05-m_ z?dg(DqfFK!q|#3$T>A@L?ZNj!F47uZ`fF4%%~4r$4I{WyRX|)gJwH!)iBU@c+}WL}&PxPX^B0JCdVQS& z2sZ=3kjy%Q;A&iV)vRV~@_imod(VK2zVAiua}vnyu=E}-#h18YJq*tBWTj*5n>@CF z-$Rqn^W*i{cNdFWfK1((C{~|q>?Ql~=4|UXlS9kt#~8$WHvN_&yS&dBbjPjt6)zGA zAdS&*aB!Rf5PV=~G|vp61v%{(`1=#+J|{A$zxKX9V&Ss*GlBoh_hRR**Iseq|D>=7R$J4(lNJ#~QQ2iL*0c<`paTf0_=Za(FN7 zju&pOhLLMZ1fjyClO+OLNGa}a7rIy?kW+S?#2!w9kwRZ^JEITymFV+>i`QBN-9^W8 zwgjwz^G0|i#-KO!XuvR_Ma7nAlnyL zQCyV{sZ0UGgddIh`gpl=y+bqueV+&vXV*bM0R_}SOgpl_(|D5L_r7ph#E|WUb4bs> z&?%aN2=6Xd0j-k}g9Fd;>9BFxHvOz=Sl$)*s*XTea`bX63|F)Q|{OBs}JwkpK)_-28hd<~!(B z*wQo}KiMb>aCxyfdx)7mFi|k%tO!&%kTOGUNxiQSVNIxx#AzTx>85rNqT<6F=EUR- zi4;#T8@$1)c%St63uU3mx=9<$U#9hIi~QY^fy`Mt1BW7L^L%($e$m|EWlGx3U{2g( zgq&QEz~?B+ZXFlXfJB#%J{lW~;R`XDL+*sW2Ya)#)B>6FvQR>*>KB9+L9;9M$QzLR zFPI7vkJ_I70-HMPu*l9*&pIYh3+w=0v|@?`qEhm%m@<`D2*7Y(k;&76d$1U)x;tR< zl{7&?M1gW93vM*n&o~lgiU~*YA)9Tz_cJ~uMb(>t z3hFNCvB5VeAB7UsDpZvkr~!Z{08GCjT&jTBbqpiAiSy^OSt9pfn(TY8kgBJoa)#_6 zp`c7yNkfv^S>WI975A9HsXpFay3s%h9~oJfzAKLB?9QMa`Ut3Z@334diiTe65Aoj8_^aF;D- z15AwW;CJkc7kyNp`*aC@U7|lzei+p?HJ)vR*MEpo-1aKW{ zTe8^G&3XYXAZnKc!5O3Wyd~z#G(3mvgi?1^5qqo?gh*vb9IV`$>l)T~+;HTBp2>Z} z_&XY&ACLX->`Bq#?aH!)Ly>(eG)kvf)b*#TW0okOIB-8UsKPTVH^fXgY-eXJ``wo- z&q{DhxJrcNC{=|)1+{~;R9I%Cg5Ct{t2`TZoT8lnOkMAZmwT{q+{PiW$AhHVW_B)c zFfzkt)~^Nhqb7sp;!cyQVLDWbWJ{4=f49);?I_@(>_*oQVg3i-@L>k5vz75&!Ko`d zzG+qO$U{YUxEDOBYAA>(6Fg{?uv~$*|3CtQ-gDgwv^U6_jCpJ}EAO}x%3KeJzl(T+ zOi`CU2~C=jri=-h>y#BYCB{fVpb#=TLy=1{y1429|ABWCkIXhoY2w~+_#xTzMd&(V z^1V?jp8l1i+WE)$aken-VzP>HeGGxc=o0}4pP*ll-=&_hJ1D$YfAF8r62I;eeSvM_ zzp$$CSvB|ngp-pXzg;Op(|v0)Y1M{0Zg@4%uC#FgyZH3Jl=R*MqEzNw=t2?=_gIH7 zR&=cFrfpYn;YW8Gd~RsgWzBsXDzBPurYbg)ZO z9&yK(y(e!5lfem*uS2cb~*&lGN_t0iFOLK^Dj&9t3a(e+L+0UsivRs2J#rVVwv4; zlZnT@h&{4Q>nt5w+YkPMxdGFdCp!%6p4GiQJ;hoAxh|bBu7#UEF%?k8JTo8F8bw|y z60F`H+0$9h^PUKNvlOirWR}LW<7W=Xd+Zaz-O#P&L>XLVN@8YYY9vcqFxP-Y#0Fgz zp8bCyIvvro4odQbiMchZESx9@#y!sbIn`*<5u>_QrjsbF!5A7>Gc-%V!%@q83x9a(h=EGFI(t{slg1Tp*Srm1nA=fvm8f7yfbbo!qX->2)Cb*<2;x85kCxKcdp^R)(@2nSp)VH3H4z6;kHDxP# zB&DhaI^PcPT;3O}S2uMVC?r-E+Qy4s_sC_RV%Fr!aG8B<4zDExu&Dz$;9K5Jpv|LQ zFZw%BTRqI`+-K-O=Qs#X2T~@3|KnxKD@^Gcbv&#S2%V+$k!vDfkHSZt7Eh;Wsg?4S zD)U=IhTFzA6|P!^GV56|YO*JQ@2T~FvWDZ4`g4rxhV}ZrxT*pMK~*bo#;-6xo=X1m zkfv!s3c{XyP5XMogozwCM#8Xt9v8G1%vP*{@qVNgBJpt0sJlEWdgoMV45wTtg61~= zbzkA$V=u){=;43_`g1)L#Shc#qeTZoIOl{WF02a{RMVA@Ka!;09e_<9eqJib1<8Ml z3?Ef-Z;ETO|5NgqCX9O;0{LHhu0;SZ<5SKR<{QSzI>zZz2`yJBe@m@`d3*$H95sd= zFsN1VIlsl3E@;)iMFz?aG%heh&o6aKF`F(K4ex8(yb1>BASx zg7Do=Ucc{JSRLlc3|(m>t(4oQv!S+6@CrW;xan0mK+-8$!4h*Nu~l2qy_U z8eNTA`Z3PuB0oi6;fN_FIJ-pu=MO_h)M7BZVqn=$-$*N9roiQ^n#lIOzGQ?Qw#$Dw2qq`j7zkW@7^D`O(*sD zDI12Gz7!isX#k5_Qf~pEDxz-uDH5D0*fsbm@^Eq-YVOCCV02gFUqKvFt{pldYB`c{ zxZ*Qm-Y-n{j(a%Yx6oWDH4>|*UK8H{N0(LeUaT9SJ9^7hR8$&p>uc-g*U5#=KQyJ}z+ahYZM^6Tvv1rL<{dT%IV3|Ts>E%6`CozP!yXzi}r_?$KKju!jZJqOiu z!$lrLpZQ8LtvLIR6FkT1jbVK`oPr;OJ(^wZ@mRuC^kZerBYY9FR-uwz}b~9%~pC`aS|Nn;yUKrX;zix97`zaa1)xx0bFw1bosmY zpFhRWGe6tvE5880h6AB|rdN0lQ3<03p_1W-L>zho=q3CRW=tYe66hUBwhhbM#qLL zP7#Vg4Vm{2g%WEd$$P+PWD{uzE=aG$*^m;}zZFhT^q=euZpadtmFbBDCyx;<*z^&!Qs*zMkIFhYdbc{%JPAUTpo`Kf z^*@Xq`xS(y{DsNo*k3IktJ0c__(|x!&L0w%o}-d4CFYn1=p2q)LtpaZzPv?@kD(01 zzoN*53&Nbkn}-UNOEA*4z0Qew|J`}^$o+;e5DhaMqnPQ0(27ruvEd%`W<+#{;>sl5 zE?Y*n*NAwo3pvY4ID}kOw`bIADm8{&%%0SJy!lZ8P=37l3Ep5<&oN$Y8wyn*Hs~_P z;%xv_kejDSnRl6o?4|ZS=Y1v&eQl*U;~X14VYXMtl&u&o&mb-r-FQk_%$}el!gM3W;i9EyU&^V~f+pDThpZ(8wBodO)mAbwadt6hi zUS1psNh~U9F?B3C^4pv^vSPq)b~a=3x&MWPBNXwjGf$9Bw4lZgMPStXQAbNGGZ5+j ze7_t-q6gSiCo)U`Yx1Yp?yoPl3AxO7q{zN3#(5+5zXfbg+JN;ByJnUMAjH$+jS{Eu_sIJ`|i9E$Y_fDy*!d?I#Kuw zM!wD#I1cxx1zSRe?P^lL0na85B#Dy`u#xcXe4TrH_Iu)Y&GHSj9*<^@mknCq={UYf zcHsdGkeVv7_}k8>7%E+at_!T1syM|7R&pZ4n4E32li?9i24Oj~Cm7loLB9~{Zb#F;e9 zRM8Aqi%2JKPkYIce?gBT*x=2)xQ#2gClK*w%=KWy`v8HnB-UoR^=HeAo=j&4u!IeI z7vaNoP+r|;a(Z;e3b;Ic%WkuE+us&&u_>p2D?MB$D$!aRn<1hPu=!UAm{KZ)DH`So z7uy6_yadK}I|R^F31x3e+4tS>cAaWIUzOkZ5GMA^(4D@UC3j7V#-_I;7=AJ> z__%KeZ4?^%)o4i2n`?j-7#rRZim#P)TwNr>E|Z{Vm_)ZhL1LC|m}0Q)5?Bcix&uSN z1RQ>!;I6vjVQp|C^V|(3f}!dm!Vf21-er$sfL}Z1?JtFpZxtBYIXO8x%K#k@8aFc} zAx^)#?`&PH^DBg-F9&hmK9tbPLf}kLCBHW-6mj#6=C!4@$ge4PE3Qr2aC%Ha3%IdT zBz)-K0)o9yocQ7}e1yNF;n~QUOsGORMJ0{D$|1a|szI}Rc^P12t}PZS>GgILnhUdQ zoJSuGj5?IO{0i{6k4aHb8WwjtCGT{IA|T_G zl7%M%f^qROX2bDJHLQd8SKr6DxHxNyELJFbOp>=!aTZXAdez)i7IP*B9j#%A}L z2wmmrm+}+AcN0Uj@o1wkVaBEk5x1pGSJO}dD!g1`=HnfE`A3%@uDiLp=<_1Qp5<@| z_@I&1vgAp{%N^q_T9vV)k?4|-#LwV3Haffb-gzxeiyeX2Af8G>tFy?>KYK;|en1(= zheeO2Dc(Zw`;(~lLPew|bY45*oq!DDG5`jf?v1+fat~P$^ zeYw{U0H8@a#M}~~5*19+1V~vze#dNO9wRf|H*cbp<$vDol`^{O0~&gIT30QKx+M@n zG{`9-IjSU+fRlkH5TSFg^5n!ZFln$s{LN}=ih-YY;mk@F>^^##dewwsB#LGcAjZ;U zZ55FobbIRiKe}Bi22Ikdd=(ZHh19Xrme$$LYt~Mi4AED4cc5FZbw})d3KbMC_jKjG z0hs6g2Zlt(tv4Jg;h(}`npBgTs>jrXIf#9%dxMjVG_9j%nXU)dqQf>Lt~mBk02eKq z1f>SG)<4OzOd0A=PgP9&?JOyr*udewB=J-vXDKSgsBAm6{`b}5sKCr|k)Q1R!G2I^ z|B`9|V4;%Y*9(>^RL;DZNZhN_6Zi;w*f!r_b1#s8XZyLoY?w?4?xTQ|XLQ@uoDHYl z95>JOD$HtUH9ueh@u`xQ1??z9eD+dPC8+4^5>0*)-kl}cz$lJw_ZJIwK_~ZMS9RhZO?ZZNuigJ5sx@YE22ft2OxhDpEK4)nhK7n$xSKKQ0wVH5L zK|`EgIAthZoL=GC>lXIm*|SVn>L9N(IMe0-mBGOXhOa=SJ1cUTxSx8Fz(dcJuY%1^ z10!X8;H7{0Jl$?H1lhYZPH`q@*U;*{QrQ1N0}_=qc=IrimFTHNA7l^vx%nS@s0IZ6 z8CM)K1#rYd5#iw@o=8nZ?D>yC46HHj$EucevWO^6jFgzFP!;wPEQ`jJr~*abnB$LIzR5y4(1 zG(kF(5%F`caXPSEyTh|Xn6%7zWkwF6o!R~e-~vM3Kj7xcH$y+k5oo>@@o)9JY7LoEHCVrbxgh@t4jn1wOFSh zpN)7If`1@rBgM)n3<4(Ii+F)e`zBV@_s;U2YD=Tl`0Mj8kZKfrF44u%zVuWzmv=Aw z97y69M*R%tKJrUoNa$W=#sfuvGy;|$<@3!lC4y5>TAE>m3`3C5ZjH;ovqBiSaLC*iHfcxdCeSieQu*h%4o6wz6CA zRbtq!<~>yIqd+CVT+aAPj+@+U_L)0}*(H9pW0HU82%R8)nN6#K$~?q;_SOAxhWsB9 z`5NN{f{dZ*_w#luy)o(xzyWE&-#ky(urM5{)=pXAow`U7-!Wj*d**-x84J2wd9^n@ zdNU`P&r;E(kWlgdX$0#;=7lm~*9)z~zsi^WOvdT3Bz*t-;VTjhM?MVP6d31CXnnbs zWNYRRZFdr3DlH|2TQM#2iByT=qO^Y(XKa8h_R9)!T$QXpX&*4TPZM-2F)#0Fh`^@v z=6>(6L-G)4P0$9$p;kF`^su_Mzr&lgCdvPwEKKi2$LiUoIwSjUcO26KdXcgL8E>#X z5Y{bpDwV^;^l+~JuCs)N+M(+jP-^)_RLN2~1JB;dd{fTHaC;>?5Cu<^5=U-sm!R}P zxdb82L3$+6na73UJ! z{354O=r_Mj47ZpH9oqj$OgEv=gO}f^wVRG&4@7+anA{J?{4KkfUYgYoF4CzlVb-aW zyE#(DoOuEZxkvgFLLKR+7Z zPQCuu)$m2UeH{)X_2&M55HPMxP$tI4iVvy?F41H5oGIp6udby~H5*!&0hB(c(9Ix^0GQyP>jS1?a`ky29gaXD2OT4c)NiErp+&FPFObzV7YNbu5fV4G8 zgCXr3MYFS0{ORm5iiOthm)oq%O&K%fgB;x3uLch92r7$=J8wf-9$}2lBfuj8$v57eVj~N1ffn6aVKkn_g0Q&%`~0vG-4U5lkCIRQ)9KWxGZ6X-pjIDzNW!)-pW z&9b#KU|o(XP=V$XKtbMaQH_CV-}*S9w~}?$3jf)Um;3=X(CBo;@QF7&^`WF zs}Xr=hTQie?!^YtShrMTrbsC4dBQUPox%nq06o_Kh@{3*FXJ6oF1%(A$7@gzv4v%L zE}&3QuMY6kBblL6lFVq?slZu3t_+pR#SlS9Rs9rSJG`@-xnN5 zxS_A{=^GfMz}tN7XoOpr-x3TC1#Pj?ZWDDZFoio)w6T;Ap@6}tM3Z8e1p0%_|6)n3 z20~K{QW>zQodMIteyLH{#}~f_t~lTb^bo2A{5$=@=%Vz%HsnN~+cWt<7&vNrdbu<{ z2UcJUpUa02W$cF_7^NRk$@TW%{-c;>e|Mk5j1Bj~nQG=8}`m;#tU-oET2{rTY< zFT4`yO>;hr=9)fC=vn%M(XX}O>+DXi{K|fE_wH|hZrtb0B_?+C!u|~Sn9%&N24MpG zpZ|x1OkY8#z>U0^qOI70D96Se4D2as76$5g`$I8i;nC4iB3BM!;rB1D6F)z{9}iFD z&o^PhfK&IyU)$KwKd>Mxu5Lk~h=El^83xHhOPbs}@9B;vBHsa2zAq+f7VmleB>qJM z)Ul(W_gBz+6U7M=rCMy?voL5<@wz4wg%klBB~Tx;q?ru0+CzfOhf;F}j~8cY_np}S z-_xqO)~3a!vG_fYx{QrWN0VJ|qum}fUXGO>&MkEU|0nRze*r|Nz6~c0fk^bR{rc<1 zCzNTof(~IQ1pF}HX4uMLg5CP`0I>VwtPw|`fKaTVOW0-#x+&nX=`#Z`Dj8r^zLyG2 zB`AY1cz#GEVQBwcRB1R8qRC8`8Jx(!H*La{5Qzdyws(J_wRSb33W`jVF8yz4n4pXp z&`)5y`DKTEu{yv_s{ti$qI+}8BjC`me(X@F1-3xj)LGrYQNR;w<{0h$CJl|tUX&R9 zE{MV5J=tJl&d4mE($jSZD-psk=_e4S8i|TsM*Xs%<7HU{UeK=8!S(6feN_tsD=%oT zKu{$v7hHz>g^pXOfc=K<7MEpA*a%=wZl7R0yKpYe9MwzIJEuSHR%Rpxm`n&UqxFn9 zDK)F1Tsa=L@&AjlU?xn*S;RYFm+u*{3tjHTK2`BvyRc{lHjykYhvP7mU#d@p*KO}z zG;CrLYYbZqPv;vi9zH*b?=JtRy074>YK^*v11crb2-4k1N_RI12uOD*0@7U~Al)G$ z-5?E0Sac&u3eq4TrHF!l>*&4reaH6?UdM3^gT2o_`#gKCxz?IPxcU)&X*3e24T!Jt+U@K z5SC&nMfI7~GH3#RoM=p_S-(!MAIIS;R6SemJ?r*(%hb_KtB-dS zK`VdR@{+hm5b>Gsj;*lAuX=Z$+m?~U?yuw$YVHWj)!PlGnJ$`l{LM>-8NrhhQY7@~ z65-#a{i&)h&(~RmRT(xY08xSoNe=KZ$XfE+ouNHlco>fqJA9TQvadRaTC{czf@KPb z&(DCaQv^@BgX0qkDTmkTCCP+pYWZhQxcyNL>AD9oxt;RWQsPE;63%_xlwA^EPKk<_!E361QusBEo{@j+Jm*-Gt4G6104{#fV)e+!iZhihx z`yl9gmmfarZ{tAQO#g7XEQja*zl4juUJU3I1$X)X`0Oj}`J;eB;1!`=2C?NKDPf1b z?k?Nk5O@pE#Rm(gQ=;x1$pf2fdBBYnYGKh9Rk_$n@8-b>f$tsL6H@Nn6^VU78G*v} zgNr=_XkFB50Ui!ioRjH`224wPxmckLm6PdvsS0{;7+7d(PjOjy3rUXpHOCh9&{!dV<#Lte`QqG*NspLezW9(LY9Glss7njpkG`j}3>%5qq;5OS> z%jJ>re`1^vYI;C$0%jo4Y}(PO5;I`fd?yF-&})>G%eah$$sqHdnE`IRJH-rg`ssCR z@CR=u3Ps-CDb{D@JO%Vvs*(*d_ulL$A6*PpyD=VSRW13vE}D+K&92Sll{);_%fp)o z;wTdDMMp|lGqBn9Afe$f-DdRDLtZ;Z!gAlfb<10S5DhyL z(Nt-hzVKA%l=N1N<9fMZShwQ@h@2d4>t96v^|y5JTVykCcSO)IeG{+mep)BJxg`t$ z+GTcm2ygN*+|2X0)Q_YTqpGcZTf)7>zR6{L_*A2T`5#m#3#hKqaf)qwuBj5JW|b^H zz-hFBQuF97p=QxwKFns>o{(@(7@GsSxN~LK-61m7mjYEtg8sT?)0QQYnj-eTs7g5# zpN^i%`R1f0v%8O&e$2a^)@9cKyT^ZvII(QNc^Jytm3L~nBJ*RU!VR##IDd{8=M4%9 zy4r~GL`y~n!72GI*#F$`9f5q2cGJz|n*uNRYe;G^b$f>wpRw{2)S4#yFg>10>nQbq zig^5&tPSG#a-+*%MsTe?O^h$+&EDxg`!;(0H#LJ=Y*z})V~w}KGHteJi+!AxqK%EO zpExC0*_$Ep#-~K=PjIYfLrv$DG9)JI%{fB{)*WZ{PFe!ZDg`A;hFPu;jsA#fE61DI zhx~8xS1^W6pPO-IPg(w(3S2}1!nYK@37}Gx$|-b`SIInhoyEQDnk<24xs{Ch*}B${ z(sSW0PFAOTEf!t;XE9N^zePR|`UFET9{vwV7*u@MJsF(vx`{{;jC3;YT&olpQmecZ zjlQk^0yfDUOK!TQeYV#(x6pc58ZP6-{SD|qe*@fLZ9@$xoQ4W-tO6L zI)M^4FfLFh8WqC3+1&4_mC-yo*R{IHY;|eT2)nSMQ>k6*|H;c4%)uR<3J{IJmGeajwGi03eK#A`UbiCL{9!8BYhu|Z}kI#JC^Qh zuR|5dzsU>r2@=Z#Iriu^Hn)|3cq={ltIKw=#_^CXE7K$*T%^ z=+HV#`mE(1?a+r4TUNGEPezVU4xP-<(ntK^W+01#^>bn*FB>^^1@X%t*AeJbI;3pO zHrORIVOooXL^DP^lAHnBENVNvhIMP3FYsxDNww7dVcpsDrpHAp+fwDm74{{ksg!?l zTb~%B94q`fH8nR<-L|{bWd?>PMYRwstv|9%jPat;mZ0Wd$s-54N@Px0JH@ZJv#Pwl zKlJ;ZN(S1SIZ6jN=|i!V0+Maf(&+#86GEyKyKS)X>O0L|dTaB&BIPy}`;5OD*%2!B z>!q+j;rY?9VpCTi%Wo)!dEL)Nwx{b~I%nI4G7DCabM;A#{!PXSO&G1`$EQ$?rGbHp zD#&JvOPS#Y3!STX4$ z^c%JK7fLis?!UNwK>^}I_;*R9JnN5cM`|hHGzU2>GAA2|4Bfub5+(G*Zs_BF_ui9mISOUaZ`)ApFDE?-!bR>v3Tm>_QtnQ;Q zFKe(zi#dSC&Yw|JyVn;kaMwwIdAhQeRzeDwiIGCgbqkM2Ul?2z5q;Ftb8Gmr3p7RJ z-#?(~tlti`mN8EKIWgr21VGQ#u(7h)8WY;MC!}{@2cJsfePWa z>L`2d%Yyq5SgHF_uj7XUId>%VzPRs+Qj0{LBv>5BA(-eGiqsdsdh-V$Yy|<9`iB@oyl>N0wE9srvEUGdAX$?`QXrhZfr*nf$=x zHF1l_y*E=QE#khFKpS8|->HZ)xQvGNeK7RvV}@q6{BWXScFCaywx97ml?^8q_j}*d z>DIcHt(`oa)!Omv7NYy}1o9v>u=Al7>05pU-Q1OTM*x#PhCTy+1F!qm*xlw*Pg!Jf z2$XxEvCyY;nIyTc_Vj}!mlLBk(&qbtaHy;xHfB*8O4+FQT=4{di zYEy#*7P(nT-#~-EFvIi z({Jiutek0A!y<4J#OIPdv9$ ztoDMtIRQoum|(i2c2cMq?};oeod{VnxLXwBxqv448gV?>CGrldq(36&E<3Qu@eQI% zxMnLuS)7trwvbOQ^g)EgFGV~9rvbh6ybrZzW~Fg;oY{u@$y7ewcvcv3V_?pc1O;4&Rl7M=OnYKbNh*v+>+tX8UqlGi0Syf5N#aj)4)H;kUHywE&yVvjch(8|1ny~L) z2`d{e0NmgPKyB%)1X-!6oz(0(*VDy*C!IK(e*QqGt6R-XPa?b=!w zhhlG~nL{nVDC!!K0ZY7w-&DE50!J!0jg_r)@}leJduiV`e0kvS;Hjm%rW%lRY74o; z%B^KwPC|Ljf<%C1NbSeLn`uaAjaTQ>&76N!jJ?SU}` z#gWf79GsMvrNFyc{0?*(C7~FhEU(?KOVIx|fFgNuf`pwmBn89z*6o)9L+*E09X}zY zPqZfO+gbvZ$wd8Ls2R5oen&G&G-Ura*t5v|RZ&XitIIzuS34;A5I{R)*Y?75u$e<(csAK=WmV(a5?Zx@hPTbxx%3oN2O5oCuNv=*U;;wYZ z>JwjhA9SPFLAZh##0w=U@sh?V0EMgyR7MLEM5`!mm#ost(QkL$Q2$sL`$9tnIf9)u z2qL*J#&KW0V9HnFd`%E{!uzo~Qof3W0>;RqcLy`*E^h5W8{3cF;yln^vytKG+li9-TCw5 zwHi;uYnVc+-J?Z)Wlm|YZpB=^ojBQ{cgew&-dI6>i7r`vAwSvn(aZ_-Ee1c{9?|b# z{foDGBmY-l)dV+Q!cvc<c~Ld;b)}e$FSofW601$ii^p3c(#ra`aDiUaj(@DfEg=2!BMi=_hUB7Zs7%ykqBoKQPwyl`L$S}U ztNKn}ekWi++X znQE-xI75~`2_y(EQE97w(Un6YV5FL-O1sFs1h&lGV~^ruzD!rHoP55p_Af0_tx`8U za}v~T`_I?eKk%o|04cp_(S)&B|IEC9Gw=3J*7{L~;6EU^&l|zTy%hR;7*^!^W?sQX(nHK?sSk+bazJ4e zhD;dE;N3Xf^=N3DRhTYM$W`XuD9rLbqB+8PlPWf#&S$$P#!8GHm)l zY0<=i?cV2gJAwmG#H*gA()$+#^^WsAlt}LT(Py)|%hhQ=hf^|KZzRdu&Whi_4-$pBVpY-*C1l`eO_kEhzYw4U z*_fSKjo;y)%zuhRyVBpqp!bw@P83CJamjZx$IBYr9QVIaIx(OEz)Ur3Vkb%8U<$Uh zH<#ERZ!c)%;R$CNT4G%qongBYpOc;mfSR08IV(yp;Jd=|7(C&;kKm~&1006b9%B0k zRPp*8S;tP?3)qAV5PXd8{QYuj64Zb?g~mKSzo@6s1IMT@8jbWbXrc`SYp3)18ZQfL zrYa&8Bi(zLLVM`3HV37edDC;AyED%Wmw}~RC5=ZLVH)stYQE(yvd_j!a4?vt?wExV zrTq?j0H9To)8G-g9KPzthb&?%${T5k$E39xtobm2N7h@!g{)w-cNE8WT zAKe;Lb>U3<`ex+qE{a95M5T2eV_HQEUq3Klji9iU?WrDrPyV6C>_hqUIbvD&Igb0) zhxd?Crmy|a`oA2sK0tg1>OmxNyx#BB8L469cbJd^Uy@`GOt&?LWm|-Bz1^g@6kKE< zR5fcMJvOmZ%t}ldEEAE`@5^1u+RS+e(=k}fZrlE9d;06a?nZ9C$e$EriqQCpV)2eN z95*Qmrbt@!5^pfuVeCOBa^dmo+~-U2aBgxiHagb&C?}_*yyJcymbU!5VS^4%!6sDu zHaim?^{d?unT_`GyCNsQe|dB1j>;leD~&3lixEl(xNDFv`anf+1lO<96?kC{4Eg$i zpPnIvCtz>6hzlLj{{ewJ_LO*dG{9%#^*ym~Y3apM?w-V0R#a$ojqCjI7K@Ei-(YT< zo%LeBmJS)96(KhHHdqI4m%QPfUn73fm9piq_*XyJZG|bs8I0_;w;`LZK9lBHVBr9M zekJBzqSz(#<|d(ID!izlew=L6gY!f(bkY0bC4aFX+vmV^8p%>dMj8kRC3*{o2ZeAfMStF z7KNuc3>aY4wd!K#c&o;!No8lcnjLt5$S-^G6)I^HmM(o z5gdph@LyeGc>5M7biJ3`&$C>&zUECu3wdTi7kU!EzFTENVR0nr<&W_X;=7ZnS8b&C4p(m6 z#A~rQP!U>$2|(s+FJzsTMwlw+*SRJ+J3KA=gq{NfP*xj?qRko!nm0j9Gy>{U@BCuq zG5Yl?(=e0jnF8_7qyDKso$+9uecfNY8%HV1gb^8;AqljxEJB-x$0 zY`Ir&qFnk23aMCRB4hT3JMn zq}#siPY`HV1&e6)W@X6KUH_vwkkV&bu6M)4!Xi+Ud__x!+qcac2o6l_OIZ|tju_}9 zM<2>-MN*?b^UoarE_0UCv^ZN(dvf8JR7IDtLfs?MxcC;&ZLd_*(f7^6KOyNMGUcU> z%hsj6iYFIf^C1(n#J>QY@=nZs%$s)lAxJ)|d7GOq=%`ttALHKmWL~M89n;*1ynval zH6M#gE?OZ;444I;o<@TNHM3?-391(T{b{LpHa# ze>EX4KEwtJZ-o55-8cXH=QbH%>D=graN0wsZ?TU?XCFKJ}fJyKxNYj zhzLVEuAuveAW1f0si^{FPG^glDdRr04RAL$po>=;M>d5W=uxoolB!-`!3ZIA%5bP_ zt5h?JT@qAEoseG#)^e5`eUE=nMg6W?WuoA+SoR-jH;5Qz24ay85=hHEq-T_Fg|4y% zEu&(K^I{RSG(z)Zkmuf~MbRdt#HT5i!5&NtU@p$bm-)*6xUBHxv1E1H(xWE4+vo515+Yp zwQ?z9*&8utT0jS`KkXCFbUY7Bi!)@6t$Z}-knC9Ko#>DFoxew6nT&|(6VOKhoydIFhoDn&}{C=eYm<&xAocylg zM(<-1tn9clDXh-)FkaQAL9yF_%03*58D-@BG@nY|Bf6#B{m&{}|de;u!wF=kDVVMnDdwEF9#*(Uf zC9C+DNQNvZbp{j}!42pyRZxUQS*T#_kPwI-s&=GXG%e(|Q-Gd5I#n2E{oGLG_wmy` za-rw))hxiBd0ImfrX({&`}fJ>he<)Mr#x`b4hRtMVvirXT74C^x5OaE?ql{~Cn5cQ zIX0(BkFxM?M)`9nD=V(yuVAn9O342iH7v7&d_-(rrH-0mxfuPmIu7kiXw=$;Yf22W zH@gHpWx9Kn=e7-JEZ$t;a!kstk@xF`2$t}dCn62=49iY%tp8rLn;1iaGCExtc2=t8 zrS;%`5o#U&E_|71_1!|s@mbCCuZLB5s>tW-zeHfLQ5(v+J#^$qt|7{Xi69E7P(EIP z73j=}XMtc?@#roZlL~jJqWS4$^3-3{;|adPS&?C^OG80bnMSPM&D&N6&VQ3I85p{- z6h<|O6ZFxc(?yffTALo=W8L}!>|=+5spkz3gj8=;Q#m8JWH5+Ru^16C4g9|;Y!dH= zyDP#u8s%oFWtz5V>;TB$Hfo`KJs*LJeio%$xGoc%kZJk_j;@WJ$;hO&ZiaTTztQ&rEW6+8{vfXsSImdJsCL zHoxX%6)^Vrl<&wE#~JFxM^2`yQ9LVPLZScD_xab|Py%veDF%(#b^U1C0EM$MI-OAr z+M<~ZyYR7vsVjQuP(jKS3`g_4#Si`Z_X${#hYL4hfQmmsssfHQ*u#3q*zBJ0{5w%9 zxh?snCekL>`q>YCN+h>9M)e{=Tku~Msu`?=BA8;*ER912XM*Etl%ZGUi- znt4)R7e;b`63EbBYM^}G8kha?ZI2!5pdlR7d&%%1?E$ z>vIspaVEMnvnKDy->kKV^@=T=CL$w5B8MBRk{QY-h!ysrPnbY@KO~reJr3YqdLWzd zf(^&^*t0jBy-2^M;0JgZSBIFrr#PtZmAsC|B&e>L@zq_$REO+y5x&z)l&->vfU^B7hJ?u{|Tc6L_}*t*L`r{4blmN-?&q* zGQ@gIdkgSX_D%1LarFs*`*^JqeQ@LR9l@WmO3{LyGK{f#31+w&@jhoZcBjfTCYbOx z1JH8DfpBWnXiwYW`!xv&H3qo>-weP0M%pEmm6f?)dNTl+Uo0MlcR+TxTN7hsLaE`o z>#4G#C4!x9bLI=SXZDmeEn6DfYt6n$S~&Yh59@fPZo)rm_GMkbgvf3k(w`1#C3Eu< zlH`)mglUeSk@^s4(#k-i%VUW(hbruPR=a!cWxXokLR8$4r29^2!4~@`igF;RxP!;E zVXBIpVyNXwn1FkYO%uWpZ5b*O|p{`ab6YGVu-!g_1YyS|N}w^!`!tY*hp)6o*r(pq4JB*@64#${sfgB@P3LEcc6=qdIfsC-E zYS{~So#9;ZY~&rEtFu6R*V{V@WTz!7-wXiZW7QeDAFE3{ZW2Xy2!N`aY z#z|qJS5l?w50ojL#GY{wd2ClWT0nV7x-}SJH|tKKCflm0R4`;ws7u9)G|O_jL+Rx-Erm0JNnO6BC!8>@Bn< zzwGF^Ar_HCgM!$gT^>!Yz}@TKsQs8@f1Z!Q7-8)qPj<(?KZ5>bl(Jv!qgGus9br&G zu0>!Z4d3XhqPoT+`Esi~>uuke5};AirP zd=3oK<0CvP1E{E|9JS1gqnI2#@RBgVM_ zG!BL5K?&ir@swloU#aBXk2V}`Jym~)nu>~ANqDmg1#D4@K)0=JY)qld@-mVJUmj){ z1#4^80up#7Z|Z!HkLnVyJ1WyVO|@rh}!r)&?Y z?_;|Xvo_Ae%W{{;w`OimiM|&oLi0Fqdl986t&N8gGR*|vdU(Iq-4_na$I>Y1);m|p zx?wF83se{eF;afFNOy?$OOM{(rQ7niO(eHJ%no-Kabrqv4#-o<@e%YrWF~*Z zR8Jcq%TjRr%8}!PnOegq;?laL=rxSyGr!f;B7FD4UX#G7T`{~{UuaS1-qSnEdGliO zk?K1NOEj zF@{ukB)VAJV@2DXbpOpdXZH}wG8@b39LXZ>*V`0SE+O3+^}jGU*QH4d+cuSY$jBvGp8D1YX}c(13!fH^BV&}#H^jIufzxT|INK+WcXeJ92GP91G7Vp|J?`eDM63aZO$5h zI(lFDnNbx!68o5-Hp12^+v27>JGO-!g#Y{- zud@5%&I{Ec7*Z%?TNcauQt)g`^;{)*M(;DQ7r@fh`c$!ytDr-Svp3|eT5jR4SU~F; zx~PWV5lU<)KMPw%&)W#M_$0~f!G-dinfU}b+*t_)u@w=i1t&s5H+mc#Nrsu8E|!1i zX1|zC+asHllr+tyy0+be@_Rqr?RYzW24Sk;^-INu6^6bqo6`P7**`kp;o(e?hlRKw zjDoIp!&|9U9uEZ-4-dhsnYphP;MAqX74wGl$1$UDnI+* zi8vxdR@VYNXi_E2lMVf-3d`wKcFCv%in6=Y2`JlU3{faZO_syiKHx?oVYjLo_p(aINjaqs1xK(Wlq z@Xe)($9_@Y{lDX9*8Ak^e1*F{g%+xg{CGDBQMb>w$u>%l3R}K@GNse$kW(DBoqY9H zm2E(cqB8w~NKFML)vo%ABtsYVYkSwl zXPt6nU2%%pBa?fc7$RnhAE{z4>46Vn9v zvu=TvDqqtBGf|#pU?eCXft}s?l5NB&8Y=R}lqu$SWV8Rg?4i^4kya$naA<`ko#6H{ z3u8xuV&g~w_qUwl-B5O2Lp@o_mkUC4!^y8hU$TY{JFY}>T3uJhwR;tQHo8Oj&hsHa z4V0uq4%kFO*r?7N_3Y=*Nf@dEDU+%I9KjMQE1e#`r~Cl#2(RzlcnOAkj7tIq^B#Fj z$GJITd71z)SRVPsj@S`bvPyD27fN!Fa&+WpOt41j8=+|CW@=7A(ZsrXpi?4CdwN#9POUE`CCKkg8c>>yc&Lwdy*vQ31W3Gj;BuN$v zbouV##j>L4|3vP3u{Dtn+bKho@*YO^tmoTOZBuPB;yR^@Wm5*yP0xW*r5J#S%v3Uh3EpakU)45&=4(sxP#AF%+0*yCWV zS*9S?^l?=t7LuVV0Jvi32Yo*Yzo&cd$|Keg!=lPLp`^C5UZ6L^0D3;x~>@|S^4Z(w-6qTBU zFUh96_XZ=rY8ywSwa)BrsLp`pr<5Es1>r|`SRiG`IQ4B=~5JL$#moU?{^4m+IMr zyi8TbO)jnY-Y=!J*c(ZNS2)Qy$-s{s&-Zh6i#CLA8v+hYdLo#K?Yf=6q-Z=Iv+oJMDdr<8>Q2PM4jkP5_S3xy6I z&IQ!}-VsdqaP`!A*e)!(J?QCp_nWGx?Wc_)OVxV9^fT9s-V5G~C=6aQ!V1rRS#whp z(oCkG)~r7r8>$9xW`2kkimeuW@g*Q@fRC%(G1s^?=_S1k>&ClG-=?b#{EaAcodKdy zp2kd9-&F)Lg?8N73$HIF%gg3jl0gzzsl;Y((zw$9{kR(pG_PgPqZn=pm{>?NSSraS zRy~oLI8AMkO8UrzuhMi@;j+TJo+@xBUXS&;o}r#P@k`A#%jwFhnrDvRGAk^D%GgYQ z^Ef$neZQ?-vw>2Wl{HsC<)0ga;M!DPPGoy?ySPt6eV{U+X+CaxzKbEx^d(#1$;0|C z(W+W0^a%cWKV-fWM_B$MNX}Y(+hId^ zD@f|M&$pnhBR%T;j}24}cV7xz1L*j5XX*(qg^-l8vN91FnewAY6o||$6?}YrU44B8 zQP16c;OvOJbq21O=U@jo2J*XaX}#!m{gd30SfSe$5jdXc-ut1FQu0}ie_x zkD$J>?@OS44{9A7@-VcMVF|~SEB_uchI!oxrCjZ{KQ;}m_ zViiTbtwHj>&QoPeF5y8SqaTAP-%k(nyTD1G&JJKrEV)+ zQ*?`w^7R#Y6cJG&JwhRhR?KDGDrM5{j*4*wv+p|}85tQ73CYGX8H>KDsRq^~8|Lg3+n^#$kHb`6KHB zJ^Ip=6e380e2q$clCBY4fR#+S5-8{sz*eB-vuhQi<$#+pe9=}=MydZZ00luUib=Zkhlmbn8Rj1H~U2+R8o_u z&S5{Ba1b`R6WSTR*n%;;W?sNIGNdYZ`tmrU>DJ3@W5K9s?}HyhQCSF*SRGqiTWs@g zMr5S_^Qw~TK0qi@MP%@ha2YFgUFH)bS@B4 zbzEFrWFOa!;-Y3gyheNAxjZwAbl;j24qd#8pYY|m-+o)^Guaut$5JqOr=WWyS=$%P862EsjyFe3 zx3=t&`4cE_^UX_G?B6m1zJGcg{rF~iHn(wWew{()ENmJQIQ%;=hO4IZ)<^HKAE#e5 zftp+3_0dOh>8h<~MrwL`a$|94z|RR-9rnNi5VSozJ3ARP`;`Qq7c!tHDg-spJWtgE zI_lMOJ;{29_F-WX1`B$bK^LFR9J`*rd9C>KwCi_uYFe5jun{M6 z1AlBgjqn^RSPgwwmB|+KsgEsosjjG)s2UlfN{^1l;$&k}nePmE;m8YHmvnv`lf72{ z6(mH)Ru)AzHZ~5dtgPfg_)fkz_#gh9_-q?b)|Fs9*=akPTfOMiJ=WI4793(f zAZNli@?v6e9^Ib%09c}FWyP{OKVRxI_{Mw#l5If_531jgeywSBES0!XfLH(N2S6Bl zKMCyP#f~DsJ(fI|uB@thFX+5b2C>BPPK~j)*kP|LVhgn$^Z5*c{Q6$@4Z%y7FB1d_ zv&fHz_I=Y{Xr1!SlL)x?b8T3+88-QnhUfyE1nzOLAH3f?+&OWbPgoG7D~=y(D20WE z%T2G^+1Vi)BHHeKe3=PsO@)?4&uqsa(CQE!#@&2w7o z?$P0)_MVEb4Yx_V+-n!$pjQls7$RW*�mgb4yEkGBPr{cL_B1_xIQK_S8E&JJ+_i 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 literal 0 HcmV?d00001 diff --git a/README_files/figure-gfm/unnamed-chunk-6-1.png b/README_files/figure-gfm/unnamed-chunk-6-1.png deleted file mode 100644 index 360e568a546e5c5141abe5a4a0b6b87627aea0d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26109 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 3.4.0. Install it with `install.packages("ggplot2")`. Estimation and all other plotting functions @@ -12,16 +27,12 @@ work without ggplot2. ```r plot_cv(cv, lambda_1_all = common_grid, lambda_2_all = balance_grid, n_observed = sum(!is.na(target)), log_labels = TRUE) -plot_cv_profile(cv, lambda_1_all = common_grid, lambda_2_all = balance_grid, - n_observed = sum(!is.na(target)), log_labels = TRUE) ``` Use the exact grids supplied to `cv.learner()`. With independent penalties, replace `lambda_1_all` with `lambda_1_row_all` and `lambda_1_col_all`. -Heatmaps then have one panel per balance penalty; profiles have one panel per -column penalty and one line per balance penalty. Zero candidates require -`log_labels = FALSE`. Heatmap positions are equally spaced grid slots, whereas -profile positions use the numerical candidate values (or their base-10 logs). +Heatmaps then have one panel per balance penalty. Zero candidates require +`log_labels = FALSE`. Heatmap positions are equally spaced grid slots. The CV object's `mse_all` contains pooled held-out squared-error sums. Supplying the total number of observed target entries converts these to MSE. Without that @@ -48,7 +59,7 @@ ggplot2::ggsave("cv-heatmap.png", plot = result$plot, width = 8, height = 5.5, d For multiple pages, `result$plots` contains every ggplot page. Inspect exported images at the intended size to check text and legend placement. -## Population latent spaces (paper Figure 4) +## Population latent spaces ```r plot_source_spaces(target, list(A = source_A, B = source_B), r = 3) @@ -65,7 +76,7 @@ or `max_display` limit plotting cost without changing the fitted spaces. All populations must have aligned variables in the same order. Labels default to Row index and Column index; use biological labels only when appropriate. -## Variable contributions (paper Figure 5) +## Variable contributions ```r plot_contributions(fit, r = 3) @@ -78,24 +89,3 @@ factors, which include scaling. The plot uses separate color scales on the two sides and reports display clipping if explicit limits are supplied. These component-specific scores depend on basis choice in repeated singular subspaces; the function does not match components or assign biological meaning. - -## Method comparisons (paper-style simulation panels) - -```r -plot_simulation_results(results, uncertainty = "none", - ylab = "Frobenius estimation error") -``` - -Supply one row per replicate and method, with `scenario`, `rank`, -`variance_ratio`, `method`, and `error`. For the paper's error definition, calculate -`norm(estimate - truth, "F")`. Use the same metric throughout the input. -The function computes means, sample SDs, SEs, and counts and returns the summary -invisibly. Optional bars are one SD or SE, not confidence intervals. - -`plot-paper-displays.R` includes a small reproducible simulation with three -replicates, fixed penalties, and ranks 2 and 3. It is an interface demonstration, -not a reproduction of the paper or a tuned comparison of methods. It prints -LEARNER stopping codes; code 1 means the current stopping rule was met, not that -a global optimum has been established. For a formal study, choose and validate -optimization and tuning procedures and use enough replications to assess -uncertainty before drawing performance conclusions. diff --git a/man/plot_contributions.Rd b/man/plot_contributions.Rd index d389ecf..6f5637f 100644 --- a/man/plot_contributions.Rd +++ b/man/plot_contributions.Rd @@ -47,8 +47,8 @@ displayed \code{row_index} and \code{column_index}, \code{limits}, and in columns; all values are returned without display clipping. } \description{ -Show squared singular-vector entries for a fitted target matrix, following -Figure 5 of the LEARNER paper. Higher contribution scores are darker red. +Show which rows and columns contribute to each latent component of a fitted +target matrix, using squared singular-vector entries. Higher scores are darker red. } \details{ An SVD of the estimated matrix supplies U and V. Row scores are U squared @@ -64,6 +64,6 @@ subspaces can change component-specific scores. Components from different populations are not automatically aligned or given biological labels. } \examples{ -fit <- dlearner(dat_highsim$Y_source,dat_highsim$Y_target,r=3) +fit <- dlearner(dat_multisource$Y_source, dat_multisource$Y_target, r = 3) plot_contributions(fit,max_display=50) } diff --git a/man/plot_cv.Rd b/man/plot_cv.Rd index c686b20..f665d1b 100644 --- a/man/plot_cv.Rd +++ b/man/plot_cv.Rd @@ -39,7 +39,7 @@ SSE. Missing target entries must not be included in this count.} \code{NULL} draws all panels. Not used in common-penalty mode.} \item{log_labels}{Logical scalar. If TRUE, display base-10 logarithms of -the candidates on the heatmap axes, as in the paper's plotting script. +the candidates on the heatmap axes. All candidates on those axes must be strictly positive. Log labels are rounded to two decimal places. Defaults to FALSE.} diff --git a/man/plot_cv_profile.Rd b/man/plot_cv_profile.Rd deleted file mode 100644 index 6818211..0000000 --- a/man/plot_cv_profile.Rd +++ /dev/null @@ -1,76 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plot-cv-profile.R -\name{plot_cv_profile} -\alias{plot_cv_profile} -\title{Plot cross-validation sensitivity curves} -\usage{ -plot_cv_profile( - x, - lambda_1_all = NULL, - lambda_2_all = NULL, - lambda_1_row_all = NULL, - lambda_1_col_all = NULL, - n_observed = NULL, - lambda_2_index = NULL, - log_labels = FALSE -) -} -\arguments{ -\item{x}{A result returned by \code{cv.learner()}.} - -\item{lambda_1_all}{Common-penalty candidates in the exact order used for CV. -Required for a two-dimensional score array; omit for separate penalties.} - -\item{lambda_2_all}{Balance-penalty candidates in the exact order used for CV.} - -\item{lambda_1_row_all, lambda_1_col_all}{Separate-penalty candidates in the -exact order used for CV. Both are required for a three-dimensional array.} - -\item{n_observed}{Optional positive integer giving the number of observed -target entries used in CV. If supplied, scores are divided by this count and -labeled Validation MSE. Otherwise the original sums are labeled Validation -SSE. Missing target entries must not be included in this count.} - -\item{lambda_2_index}{Optional vector of distinct integer indices into -\code{lambda_2_all}, selecting panels to draw in separate-penalty mode. -\code{NULL} draws all panels. Not used in common-penalty mode.} - -\item{log_labels}{Logical scalar. If TRUE, display base-10 logarithms of -the candidates on the heatmap axes, as in the paper's plotting script. -All candidates on those axes must be strictly positive. Log labels are -rounded to two decimal places. Defaults to FALSE.} -} -\value{ -Invisibly, a list of absolute-score diagnostics: \code{scores}, -\code{scale}, \code{grids}, \code{best_index}, \code{shown_lambda_2_index}, -\code{color_limits}, \code{unavailable_count}, \code{log_labels}, -\code{score_range}, \code{minimum}, \code{exact_minima}, \code{boundary}, -\code{lambda_2_slice_range}, and \code{x_order} (horizontal drawing order). -This base-graphics function does not return ggplot objects or the relative -display fields returned by \code{plot_cv()}. -} -\description{ -Compare validation scores across the common space penalty, or across the -row-space penalty at each fixed column-space penalty. Each line corresponds -to one balance penalty, making nearly overlapping curves visible. -} -\details{ -Supply the exact original grids. The horizontal candidates are sorted only -for drawing lines; arrays and selected indices retain CV order. Duplicate -horizontal candidates are rejected. In separate mode there is one panel per -column-space penalty; \code{lambda_2_index} selects the balance-penalty lines. -At most four panels are drawn per page. With \code{log_labels=TRUE}, the -horizontal coordinate is the actual base-10 logarithm, not a rounded label. -All vertical limits use the complete score array. Non-finite values break -lines. The graph does not quantify uncertainty or establish that a parameter -has no effect; exact ties and boundary selections are reported separately. -} -\examples{ -set.seed(42) -cv <- cv.learner(dat_highsim$Y_source[1:12, 1:8], - dat_highsim$Y_target[1:12, 1:8], r = 2, - lambda_1_all = c(0.1, 1, 10), lambda_2_all = c(0.1, 1), - step_size = 0.0003, n_folds = 2, control = list(max_iter = 20)) -plot_cv_profile(cv, lambda_1_all = c(0.1, 1, 10), - lambda_2_all = c(0.1, 1), n_observed = 96, log_labels = TRUE) -} diff --git a/man/plot_objective.Rd b/man/plot_objective.Rd index 0ac992c..6b9c83a 100644 --- a/man/plot_objective.Rd +++ b/man/plot_objective.Rd @@ -2,44 +2,46 @@ % Please edit documentation in R/plotting.R \name{plot_objective} \alias{plot_objective} -\title{Plot the recorded LEARNER objective history} +\title{Plot the objective function values of a LEARNER fit} \usage{ -plot_objective(x, last = NULL, main = "Recorded objective history") +plot_objective(x, iterations = "all", main = NULL) } \arguments{ -\item{x}{A result returned by \code{learner()}.} +\item{x}{A result returned by \code{learner}.} -\item{last}{Optional positive integer specifying the number of final updates -to show in a second panel. \code{NULL} draws only the full history.} +\item{iterations}{Iterations to show: \code{"all"} (the default), +\code{"near_optimum"}, or a vector of iteration numbers such as +\code{1:1000}. \code{"near_optimum"} shows the iterations from the one +with the smallest objective value through the last iteration. If the +smallest value occurs within the last 10\% of the iterations (and at least +the last 10), the last 10\% of the iterations are shown instead. The +horizontal axis is the same as for \code{"all"}, except in that case.} -\item{main}{Character scalar used as the title of the full-history panel.} +\item{main}{Title of the plot. By default, \code{"Objective function"}, +with \code{"(near optimum)"} added when \code{iterations = "near_optimum"}.} } \value{ -Invisibly, a list containing \code{iteration}, \code{objective}, -\code{last_indices}, \code{recorded_min_index}, and -\code{convergence_criterion}. Plotting does not modify \code{x}. +Invisibly, a data frame of the plotted iterations and objective +values. } \description{ -Inspect the objective values returned by \code{learner()}. Optionally show -the last updates beside the full history to reveal small oscillations. +Plots the values of the objective function at each iteration of the +numerical optimization algorithm in \code{learner}. } \details{ -Update numbers start at one. The initialization objective is not stored in -\code{x$objective_values}. The marked minimum is therefore the lowest -\emph{recorded} objective, not necessarily the objective of the returned -estimate, which can retain the initialization. Panels use their own vertical -scales. A stopping code of one indicates the objective-change rule was met; -it does not establish a global optimum or stability of the factors. - -Non-finite recorded values are shown as red triangles at the top of a panel, -with a legend; their vertical positions do not represent objective values. -At least one recorded value must be finite. Graphics parameters are restored -on exit; this function sets its own panel arrangement. +The red point marks the smallest objective value, which is also +given above the plot. The objective value at the initialization is not +included. With \code{iterations = "near_optimum"}, the plot shows a shaded +band spanning each pair of consecutive objective values, so that values +that alternate between iterations remain visible, and a line at their +average. } \examples{ fit <- learner(dat_highsim$Y_source, dat_highsim$Y_target, - r = 3, lambda_1 = 1, lambda_2 = 1, step_size = 0.0003, - control = list(max_iter = 100)) -plot_objective(fit, last = 30) + r = 3, lambda_1 = 1, lambda_2 = 1, step_size = 0.003, + control = list(max_iter = 1000)) +plot_objective(fit) +plot_objective(fit, iterations = "near_optimum") +plot_objective(fit, iterations = 1:100) } diff --git a/man/plot_simulation_results.Rd b/man/plot_simulation_results.Rd deleted file mode 100644 index 56122f2..0000000 --- a/man/plot_simulation_results.Rd +++ /dev/null @@ -1,61 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plot-simulation.R -\name{plot_simulation_results} -\alias{plot_simulation_results} -\title{Compare methods across simulation settings} -\usage{ -plot_simulation_results( - data, - uncertainty = c("none", "sd", "se"), - main = "Simulation comparison", - ylab = "Estimation error", - xlab = expression(sigma[0]^2/sigma[1]^2) -) -} -\arguments{ -\item{data}{Data frame with columns \code{scenario}, \code{rank}, -\code{variance_ratio}, \code{method}, and \code{error}. Each row must be one -replicate result, not an already aggregated mean. Scenario and method are -nonempty character strings or factors; rank is a positive integer; the -variance ratio is positive; error is finite and nonnegative. Use the same -error definition across all methods and scenarios. For the paper's metric, -supply the Frobenius norm of estimated minus true signal.} - -\item{uncertainty}{One of \code{"none"}, \code{"sd"}, or \code{"se"}. -Bars show one sample standard deviation or one standard error of the mean, -not a confidence interval. Bars are omitted for groups with one replicate.} - -\item{main}{Overall title.} - -\item{ylab}{Vertical label naming the supplied error metric.} - -\item{xlab}{Horizontal label; defaults to the target/source noise variance ratio.} -} -\value{ -Invisibly, a data frame with grouping columns and \code{mean}, -\code{sd}, \code{se}, and \code{n}. Missing groups are not filled with zero. -} -\description{ -Plot replicate means by noise-variance ratio, with panels for each scenario -and rank. The default method colors and line types follow the simulation -figures in the LEARNER paper. This function summarizes supplied results; -it does not run simulations or reproduce the paper's experiments. -} -\details{ -Scenarios and methods follow their first appearance in the data; ranks and -ratios are sorted numerically. At most two ranks and three scenarios are -drawn per page. All pages share vertical limits and method styles. Missing -method-by-ratio groups break curves. Canonical method names are -\code{"Target-only SVD"}, \code{"D-LEARNER"}, and \code{"LEARNER"}. -Other names are permitted and receive distinct styles. Replicate identifiers -are not inferred: callers must avoid duplicate records or mixing experiments. -} -\examples{ -results <- expand.grid(scenario = c("High similarity", "Low similarity"), - rank = 2, variance_ratio = c(1, 3, 5), - method = c("Target-only SVD", "D-LEARNER", "LEARNER"), - replicate = 1:2, stringsAsFactors = FALSE) -# Illustrative values only; replace with measured errors from simulations. -results$error <- 3 + 1 / results$variance_ratio + results$replicate / 10 -plot_simulation_results(results, main = "Illustrative input format") -} diff --git a/man/plot_source_spaces.Rd b/man/plot_source_spaces.Rd index a0f23ab..7e341fc 100644 --- a/man/plot_source_spaces.Rd +++ b/man/plot_source_spaces.Rd @@ -50,7 +50,7 @@ full row-by-row projector. Its population name identifies the input. } \description{ Plot column-space and row-space projection submatrices for the target and -each source, following the layout of Figure 4 in the LEARNER paper. +each source, to compare their retained latent spaces. } \details{ For an SVD basis B, the displayed matrix is a submatrix of B B-transpose. @@ -66,7 +66,6 @@ singular values at the truncation boundary the retained space is not unique. The input order must already encode any desired phenotype or genomic ordering. } \examples{ -a <- dat_highsim$Y_source[1:20,1:10] -y <- dat_highsim$Y_target[1:20,1:10] -plot_source_spaces(y, list(A=a, B=a*1.1), r=2, max_display=20) +plot_source_spaces(dat_multisource$Y_target, dat_multisource$Y_source, + r = 3, max_display = 20) } diff --git a/tests/testthat/test-paper-plots.R b/tests/testthat/test-paper-plots.R index c4e75c2..c4c6a53 100644 --- a/tests/testthat/test-paper-plots.R +++ b/tests/testthat/test-paper-plots.R @@ -15,21 +15,6 @@ test_that('CV annotations distinguish small differences from exact ties and nume c(common=TRUE,balance=FALSE)) }) -test_that('CV profiles sort horizontal values without reordering stored scores', { - path<-plot_device();on.exit({grDevices::dev.off();unlink(path)},add=TRUE) - old<-graphics::par(c('mfrow','mar')) - x<-list(mse_all=matrix(6:1,3),lambda_1_min=10,lambda_2_min=1) - d<-plot_cv_profile(x,lambda_1_all=c(100,1,10),lambda_2_all=c(.1,1),log_labels=TRUE) - expect_equal(d$x_order,c(2L,3L,1L));expect_equal(d$scores,x$mse_all) - expect_equal(d$best_index,c(3L,2L)) - expect_equal(graphics::par(c('mfrow','mar')),old) - expect_error(plot_cv_profile(x,lambda_1_all=c(10,1,10),lambda_2_all=c(.1,1)), 'distinct') - d<-plot_cv_profile(cv_plot_example(),lambda_1_row_all=c(10,0), - lambda_1_col_all=c(.2,3),lambda_2_all=c(0,1),lambda_2_index=2) - expect_equal(d$shown_lambda_2_index,2) - expect_equal(d$best_index,c(2L,2L,2L)) -}) - test_that('space plots compute full-matrix spaces before taking common submatrices', { path<-plot_device();on.exit({grDevices::dev.off();unlink(path)},add=TRUE) # This rank-one matrix has analytically known normalized left/right vectors. @@ -69,24 +54,3 @@ test_that('contributions use squared normalized singular vectors, not raw factor expect_error(plot_contributions(y,r=2),'zero singular') expect_error(plot_contributions(y,r=1,column_index=3),'valid integer') }) - -test_that('simulation plots summarize replicate errors and preserve graphics state', { - path<-plot_device();on.exit({grDevices::dev.off();unlink(path)},add=TRUE) - d<-data.frame(scenario='High',rank=2,variance_ratio=c(1,1,3),method='LEARNER',error=c(2,4,1)) - old<-graphics::par(c('mfrow','mar'));original<-d - for(uncertainty in c('none','sd','se')) { - out<-plot_simulation_results(d,uncertainty=uncertainty) - expect_equal(out$mean,c(3,1));expect_equal(out$n,c(2L,1L)) - expect_equal(out$sd,c(sqrt(2),NA));expect_equal(out$se,c(1,NA)) - expect_equal(graphics::par(c('mfrow','mar')),old) - } - expect_identical(d,original) - d$error[1]<-NA;expect_error(plot_simulation_results(d),'finite') - d$error[1]<- -1;expect_error(plot_simulation_results(d),'nonnegative') - d$error[1]<-1;d$variance_ratio[1]<-0;expect_error(plot_simulation_results(d),'positive') - expect_error(plot_simulation_results(data.frame()),'nonempty') - # Empty scenario/rank combinations and pages must not synthesize zero errors. - d<-data.frame(scenario=rep(c('A','B','C','D'),each=3),rank=rep(1:3,4), - variance_ratio=1,method='Other',error=1:12) - expect_equal(nrow(plot_simulation_results(d)),12) -}) diff --git a/tests/testthat/test-plotting.R b/tests/testthat/test-plotting.R index 15d7863..b6a0d66 100644 --- a/tests/testthat/test-plotting.R +++ b/tests/testthat/test-plotting.R @@ -1,37 +1,77 @@ -test_that('objective plots retain the update indices and restore graphics state', { +test_that('objective plots show the requested iterations', { path <- plot_device() on.exit({grDevices::dev.off(); unlink(path)}, add = TRUE) - graphics::par(mfrow = c(2, 1), mar = c(3, 3, 2, 1)) - old <- graphics::par(c('mfrow', 'mar')) - fit <- list(objective_values = c(9, 5, 6, 5.5), convergence_criterion = 2L) + # Decrease to the minimum at iteration 31, then values that stay above it. + fit <- list(objective_values = c(seq(100, 10, length.out = 30), rep(c(9, 9.5), 50)), + convergence_criterion = 2L) original <- fit - result <- withVisible(plot_objective(fit, last = 2)) + result <- withVisible(plot_objective(fit)) expect_false(result$visible) - expect_equal(result$value$iteration, 1:4) - expect_equal(result$value$last_indices, 3:4) - expect_equal(result$value$recorded_min_index, 2L) + expect_equal(result$value$iteration, 1:130) + expect_equal(result$value$objective, fit$objective_values) + expect_equal(plot_objective(fit, iterations = 2:4)$iteration, 2:4) + expect_equal(plot_objective(fit, iterations = c(5, 2, 2))$iteration, c(2, 5)) + # From the smallest objective value (iteration 31) through the last iteration. + expect_equal(plot_objective(fit, iterations = 'near_optimum')$iteration, 31:130) expect_identical(fit, original) - expect_equal(graphics::par(c('mfrow', 'mar')), old) - expect_equal(plot_objective(fit, last = 100)$last_indices, 1:4) }) -test_that('objective plots support singleton, flat, and failed histories', { +test_that('near-optimum plots share the horizontal axis of the full history', { path <- plot_device() on.exit({grDevices::dev.off(); unlink(path)}, add = TRUE) - for (values in list(4, c(4, 4, 4), c(4, Inf, NaN))) { - fit <- list(objective_values = values, convergence_criterion = 3L) - result <- plot_objective(fit, last = 1) - expect_equal(result$recorded_min_index, 1L) - expect_equal(result$objective, values) + fit <- list(objective_values = c(seq(100, 10, length.out = 30), rep(c(9, 9.5), 50)), + convergence_criterion = 2L) + plot_objective(fit) + usr_all <- graphics::par('usr')[1:2] + plot_objective(fit, iterations = 'near_optimum') + expect_equal(graphics::par('usr')[1:2], usr_all) + # A history still decreasing at the end shows its final stretch with its own axis. + fit <- list(objective_values = seq(100, 10, length.out = 100), convergence_criterion = 1L) + plot_objective(fit) + usr_all <- graphics::par('usr')[1:2] + plot_objective(fit, iterations = 'near_optimum') + expect_gt(graphics::par('usr')[1], usr_all[1] + 50) +}) + +test_that('near-optimum plots show the final stretch when the minimum is at the end', { + path <- plot_device() + on.exit({grDevices::dev.off(); unlink(path)}, add = TRUE) + # A history that is still decreasing at the last iteration. + fit <- list(objective_values = seq(100, 10, length.out = 100), convergence_criterion = 1L) + expect_equal(plot_objective(fit, iterations = 'near_optimum')$iteration, 91:100) + # Short histories are shown in full. + fit <- list(objective_values = c(9, 5, 4), convergence_criterion = 1L) + expect_equal(plot_objective(fit, iterations = 'near_optimum')$iteration, 1:3) +}) + +test_that('near-optimum plots handle alternating and missing values and keep graphics settings', { + path <- plot_device() + on.exit({grDevices::dev.off(); unlink(path)}, add = TRUE) + # Alternating values after a decrease; near_optimum draws them as a band. + values <- c(seq(50, 11, length.out = 40), 10 + rep(c(0, 0.5), 200)) + values[100] <- NA + fit <- list(objective_values = values, convergence_criterion = 2L) + old <- graphics::par('mar') + result <- plot_objective(fit) + expect_equal(result$iteration, seq_along(values)) + expect_equal(plot_objective(fit, iterations = 'near_optimum')$iteration, 41:440) + expect_equal(graphics::par('mar'), old) +}) + +test_that('objective plots handle flat, singleton, and non-finite histories', { + path <- plot_device() + on.exit({grDevices::dev.off(); unlink(path)}, add = TRUE) + for (values in list(4, c(4, 4, 4), c(4, Inf, NaN), c(Inf, 3, 1, NA))) { + fit <- list(objective_values = values, convergence_criterion = 2L) + expect_equal(plot_objective(fit)$objective, values) + expect_true(length(plot_objective(fit, iterations = 'near_optimum')$iteration) >= 1L) + } + fit <- list(objective_values = c(2, 1, 3), convergence_criterion = 2L) + for (bad in list(0, 4, 1.5, NA, numeric(), 'near', TRUE)) { + expect_error(plot_objective(fit, iterations = bad), 'iterations') } - expect_error(plot_objective(list(objective_values = c(Inf, NA))), 'No finite') - expect_error(plot_objective(list()), 'objective_values') - fit <- list(objective_values = 4, convergence_criterion = 1L) - expect_error(plot_objective(fit, last = 0), 'last') - expect_error(plot_objective(fit, last = 1.5), 'last') - expect_error(plot_objective(fit, main = NA_character_), 'main') - fit$convergence_criterion <- 9L - expect_error(plot_objective(fit), 'convergence_criterion') + expect_error(plot_objective(list(objective_values = c(Inf, NA))), 'learner') + expect_error(plot_objective(list()), 'learner') }) test_that('CV plots retain candidate order, mark the global minimum, and scale SSE', { @@ -147,3 +187,17 @@ test_that('paper-style log labels preserve scores and candidate selection', { expect_error(plot_cv(x, log_labels = NA), 'TRUE or FALSE') expect_error(plot_cv(x, log_labels = 'yes'), 'TRUE or FALSE') }) + +test_that('objective plots reject ranges with no finite values before drawing', { + path <- plot_device() + on.exit({grDevices::dev.off(); unlink(path)}, add = TRUE) + fit <- list(objective_values = c(3, 1, NA_real_, Inf, NaN)) + old <- graphics::par(c('mar', 'usr')) + expect_error( + withCallingHandlers(plot_objective(fit, iterations = 3:5), + warning = function(w) stop('Unexpected warning: ', conditionMessage(w))), + 'selected iterations contain no finite objective values') + expect_equal(graphics::par(c('mar', 'usr')), old) + expect_equal(plot_objective(fit, iterations = 2:5)$objective, + fit$objective_values[2:5]) +}) From d1b7ca6e048797673985d728bf01604c0ad75f84 Mon Sep 17 00:00:00 2001 From: Jodie Li Date: Thu, 15 Oct 2026 22:28:25 -0500 Subject: [PATCH 5/6] Center objective windows on the minimum and document source comparisons --- R/plotting.R | 23 +++++++++-------------- README.Rmd | 23 +++++++++++++++++++++++ README.md | 25 +++++++++++++++++++++++++ inst/examples/plotting-guide.md | 14 +++++++------- man/plot_objective.Rd | 10 +++++----- tests/testthat/test-plotting.R | 31 ++++++++++++++++--------------- 6 files changed, 85 insertions(+), 41 deletions(-) diff --git a/R/plotting.R b/R/plotting.R index ee62a14..d7b1cee 100644 --- a/R/plotting.R +++ b/R/plotting.R @@ -6,11 +6,11 @@ #' @param x A result returned by \code{learner}. #' @param iterations Iterations to show: \code{"all"} (the default), #' \code{"near_optimum"}, or a vector of iteration numbers such as -#' \code{1:1000}. \code{"near_optimum"} shows the iterations from the one -#' with the smallest objective value through the last iteration. If the -#' smallest value occurs within the last 10\% of the iterations (and at least -#' the last 10), the last 10\% of the iterations are shown instead. The -#' horizontal axis is the same as for \code{"all"}, except in that case. +#' \code{1:1000}. \code{"near_optimum"} shows a window centered on the first +#' smallest finite recorded objective value. The window contains 10\% of the +#' recorded iterations (rounded up), with a minimum of 10, or all iterations +#' for shorter histories. At either boundary the window shifts to stay within +#' the recorded history. Both axes adjust to the selected window. #' @param main Title of the plot. By default, \code{"Objective function"}, #' with \code{"(near optimum)"} added when \code{iterations = "near_optimum"}. #' @@ -43,15 +43,12 @@ plot_objective <- function(x, iterations = "all", main = NULL) { finite <- is.finite(objective) minimum <- which.min(replace(objective, !finite, Inf)) near <- identical(iterations, 'near_optimum') - tail_view <- FALSE if (identical(iterations, 'all')) { shown <- seq_len(n) } else if (near) { - # If the minimum is the end of the history, show its final stretch instead. - tail_length <- max(10L, ceiling(0.1 * n)) - start <- min(minimum, max(1L, n - tail_length + 1L)) - tail_view <- start != minimum - shown <- seq.int(start, n) + width <- min(n, max(10L, ceiling(0.1 * n))) + start <- max(1L, min(minimum - floor((width - 1L) / 2), n - width + 1L)) + shown <- seq.int(start, length.out = width) } else if (is.numeric(iterations) && length(iterations) > 0L && all(iterations %in% seq_len(n))) { shown <- sort(unique(as.integer(iterations))) @@ -69,9 +66,7 @@ plot_objective <- function(x, iterations = "all", main = NULL) { old <- graphics::par(mar = c(5, 4.5, 4, 1.5)) on.exit(graphics::par(old), add = TRUE) - # Near the optimum, keep the horizontal axis of the full history so that the - # two plots line up; only the vertical axis is zoomed. - xlim <- if (near && !tail_view) c(1, n) else range(shown) + xlim <- range(shown) graphics::plot(shown, values, type = 'n', xlim = xlim, xlab = 'Iteration', ylab = 'Objective value', bty = 'l') graphics::title(main, line = 2) diff --git a/README.Rmd b/README.Rmd index 08332b6..a145bf8 100644 --- a/README.Rmd +++ b/README.Rmd @@ -151,6 +151,29 @@ direct <- dlearner(sources, target, r = 3, source_weights = weights) dim(direct$dlearner_estimate) ``` +### Compare source choices + +For this simulated dataset, the true target signal is available. Compare +D-LEARNER fits at the same rank to isolate the choice of source spaces: + +```{r source-comparison} +estimates <- list( + A_only = dlearner(sources$A, target, r = 3)$dlearner_estimate, + B_only = dlearner(sources$B, target, r = 3)$dlearner_estimate, + Weighted_A_B = direct$dlearner_estimate +) +source_errors <- vapply(estimates, function(estimate) { + norm(estimate - dat_multisource$Theta_target, "F") +}, numeric(1)) +round(source_errors, 3) +``` + +Lower Frobenius error is better. In this example, source A alone gives the +smallest error, while the weighted combination improves on source B alone. +The weights 0.7/0.3 are illustrative, not optimized. These results describe one +simulated dataset; they are not CV errors or evidence that combining sources +always improves estimation. + ### Latent spaces and contributions Compare the target and source spaces, or inspect which variables contribute diff --git a/README.md b/README.md index 69a98ac..3258f07 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,31 @@ dim(direct$dlearner_estimate) #> [1] 100 50 ``` +### Compare source choices + +For this simulated dataset, the true target signal is available. Compare +D-LEARNER fits at the same rank to isolate the choice of source spaces: + +``` r +estimates <- list( + A_only = dlearner(sources$A, target, r = 3)$dlearner_estimate, + B_only = dlearner(sources$B, target, r = 3)$dlearner_estimate, + Weighted_A_B = direct$dlearner_estimate +) +source_errors <- vapply(estimates, function(estimate) { + norm(estimate - dat_multisource$Theta_target, "F") +}, numeric(1)) +round(source_errors, 3) +#> A_only B_only Weighted_A_B +#> 2.323 17.214 5.324 +``` + +Lower Frobenius error is better. In this example, source A alone gives +the smallest error, while the weighted combination improves on source B +alone. The weights 0.7/0.3 are illustrative, not optimized. These +results describe one simulated dataset; they are not CV errors or +evidence that combining sources always improves estimation. + ### Latent spaces and contributions Compare the target and source spaces, or inspect which variables diff --git a/inst/examples/plotting-guide.md b/inst/examples/plotting-guide.md index 0de30af..1e80e7c 100644 --- a/inst/examples/plotting-guide.md +++ b/inst/examples/plotting-guide.md @@ -7,16 +7,16 @@ cannot establish convergence or statistical superiority. ```r plot_objective(fit) # All iterations -plot_objective(fit, iterations = "near_optimum") # From the smallest value onward +plot_objective(fit, iterations = "near_optimum") # Around the smallest recorded value plot_objective(fit, iterations = 1:500) # A specific range ``` -`"near_optimum"` starts at the iteration with the smallest objective value (or, -if that is within the last 10% of the iterations, shows the last 10%) and -draws a shaded band spanning each pair of consecutive values, so values that -alternate between iterations remain visible, with a line at their average. Its -horizontal axis matches the full history unless the final stretch is shown; -in that case the horizontal axis also zooms to the selected stretch. +`"near_optimum"` centers a window on the first smallest finite recorded +objective value. It shows 10% of the recorded iterations (rounded up), at least +10, or the full history if fewer than 10 were recorded. At either end the window +shifts to stay within the history. Both axes zoom to the selected window. +The shaded band spans consecutive objective values and the line shows their +pairwise average; the band is not an uncertainty interval. ## CV heatmaps diff --git a/man/plot_objective.Rd b/man/plot_objective.Rd index 6b9c83a..7b7ce87 100644 --- a/man/plot_objective.Rd +++ b/man/plot_objective.Rd @@ -11,11 +11,11 @@ plot_objective(x, iterations = "all", main = NULL) \item{iterations}{Iterations to show: \code{"all"} (the default), \code{"near_optimum"}, or a vector of iteration numbers such as -\code{1:1000}. \code{"near_optimum"} shows the iterations from the one -with the smallest objective value through the last iteration. If the -smallest value occurs within the last 10\% of the iterations (and at least -the last 10), the last 10\% of the iterations are shown instead. The -horizontal axis is the same as for \code{"all"}, except in that case.} +\code{1:1000}. \code{"near_optimum"} shows a window centered on the first +smallest finite recorded objective value. The window contains 10\% of the +recorded iterations (rounded up), with a minimum of 10, or all iterations +for shorter histories. At either boundary the window shifts to stay within +the recorded history. Both axes adjust to the selected window.} \item{main}{Title of the plot. By default, \code{"Objective function"}, with \code{"(near optimum)"} added when \code{iterations = "near_optimum"}.} diff --git a/tests/testthat/test-plotting.R b/tests/testthat/test-plotting.R index b6a0d66..7101c73 100644 --- a/tests/testthat/test-plotting.R +++ b/tests/testthat/test-plotting.R @@ -11,26 +11,27 @@ test_that('objective plots show the requested iterations', { expect_equal(result$value$objective, fit$objective_values) expect_equal(plot_objective(fit, iterations = 2:4)$iteration, 2:4) expect_equal(plot_objective(fit, iterations = c(5, 2, 2))$iteration, c(2, 5)) - # From the smallest objective value (iteration 31) through the last iteration. - expect_equal(plot_objective(fit, iterations = 'near_optimum')$iteration, 31:130) + # A 13-iteration window centered on the first minimum at iteration 31. + expect_equal(plot_objective(fit, iterations = 'near_optimum')$iteration, 25:37) expect_identical(fit, original) }) -test_that('near-optimum plots share the horizontal axis of the full history', { +test_that('near-optimum windows include both sides and zoom the horizontal axis', { path <- plot_device() on.exit({grDevices::dev.off(); unlink(path)}, add = TRUE) - fit <- list(objective_values = c(seq(100, 10, length.out = 30), rep(c(9, 9.5), 50)), - convergence_criterion = 2L) - plot_objective(fit) - usr_all <- graphics::par('usr')[1:2] - plot_objective(fit, iterations = 'near_optimum') - expect_equal(graphics::par('usr')[1:2], usr_all) - # A history still decreasing at the end shows its final stretch with its own axis. - fit <- list(objective_values = seq(100, 10, length.out = 100), convergence_criterion = 1L) + # Interior minimum followed by a long increase. + fit <- list(objective_values = (seq_len(101) - 51)^2) plot_objective(fit) - usr_all <- graphics::par('usr')[1:2] - plot_objective(fit, iterations = 'near_optimum') - expect_gt(graphics::par('usr')[1], usr_all[1] + 50) + full <- graphics::par('usr')[1:2] + result <- plot_objective(fit, iterations = 'near_optimum') + expect_equal(result$iteration, 46:56) + expect_gt(graphics::par('usr')[1], full[1]) + expect_lt(graphics::par('usr')[2], full[2]) + # At the beginning, shift the window right; exact ties use the first minimum. + fit$objective_values <- seq_len(100) + expect_equal(plot_objective(fit, iterations = 'near_optimum')$iteration, 1:10) + fit$objective_values <- rep(1, 100) + expect_equal(plot_objective(fit, iterations = 'near_optimum')$iteration, 1:10) }) test_that('near-optimum plots show the final stretch when the minimum is at the end', { @@ -54,7 +55,7 @@ test_that('near-optimum plots handle alternating and missing values and keep gra old <- graphics::par('mar') result <- plot_objective(fit) expect_equal(result$iteration, seq_along(values)) - expect_equal(plot_objective(fit, iterations = 'near_optimum')$iteration, 41:440) + expect_equal(plot_objective(fit, iterations = 'near_optimum')$iteration, 20:63) expect_equal(graphics::par('mar'), old) }) From fb8b7ea0291e35d380fe995111a1f2df401d5c5b Mon Sep 17 00:00:00 2001 From: Jodie Li Date: Thu, 15 Oct 2026 22:48:29 -0500 Subject: [PATCH 6/6] Remove unused CV caption helper and test plotting errors --- R/plotting.R | 17 ---------- tests/testthat/test-cv-plot-errors.R | 48 ++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 17 deletions(-) create mode 100644 tests/testthat/test-cv-plot-errors.R diff --git a/R/plotting.R b/R/plotting.R index d7b1cee..2c3d185 100644 --- a/R/plotting.R +++ b/R/plotting.R @@ -421,23 +421,6 @@ prepare_cv_plot <- function(x, lambda_1_all = NULL, lambda_2_all = NULL, boundary = boundary, lambda_2_slice_range = range(slice[is.finite(slice)])) } -# Caption reports numerical facts rather than inferring a flat direction from color. -plot_cv_caption <- function(d) { - number <- function(z) formatC(z, digits = 6, format = 'fg', flag = '#') - relative <- if (d$minimum > 0) paste0(' (span ', formatC(100 * diff(d$score_range) / d$minimum, digits = 2, format = 'f'), '% of minimum)') else '' - line1 <- paste0('Range: ', number(d$score_range[1]), ' to ', number(d$score_range[2]), relative, - '; selected minimum = ', number(d$minimum), '.') - boundary <- names(d$boundary)[d$boundary] - line2 <- paste0(if(length(boundary)) paste0('Grid boundary: ', paste(boundary, collapse = ', '), '; ') else '', - 'lambda_2 slice span at selected space penalty: ', - formatC(diff(d$lambda_2_slice_range), digits = 3, format = 'g'), '.') - line3 <- if (d$exact_minima > 1) paste(d$exact_minima, 'exact minima; first in CV order selected.') else 'Unique minimum among evaluated candidates.' - line3 <- paste('X: selected parameters.', line3, - if(d$unavailable_count > 0) 'Gray: unavailable.' else '') - for (i in 1:3) graphics::mtext(c(line1,line2,line3)[i], outer = TRUE, - side = 1, line = 0.4 + (i-1)*1.1, cex = 0.78) -} - # Five ticks include the numerical endpoints even for a narrow score range. draw_color_key <- function(left, right, bottom, top, limits, colors) { edges <- seq(bottom, top, length.out=length(colors)+1L) diff --git a/tests/testthat/test-cv-plot-errors.R b/tests/testthat/test-cv-plot-errors.R new file mode 100644 index 0000000..ec0239f --- /dev/null +++ b/tests/testthat/test-cv-plot-errors.R @@ -0,0 +1,48 @@ +test_that('CV heatmaps reject invalid titles and separate arguments in common mode', { + args <- list(x = cv_plot_example(FALSE), lambda_1_all = c(10, 0), + lambda_2_all = c(0, 1)) + for (title in list(NA_character_, character(), c('a', 'b'), 1)) { + expect_error(do.call(plot_cv, c(args, list(main = title))), + 'main must be one nonmissing character string', fixed = TRUE) + } + for (extra in list(list(lambda_1_row_all = c(1, 2)), + list(lambda_1_col_all = c(1, 2)), + list(lambda_2_index = 1))) { + expect_error(do.call(plot_cv, c(args, extra)), + 'Separate-penalty arguments require a 3D score array', fixed = TRUE) + } +}) + +test_that('CV heatmaps explain how to install the optional plotting dependency', { + # Older supported testthat versions do not provide local_mocked_bindings(). + skip_if_not_installed('testthat', minimum_version = '3.2.0') + original_require <- base::requireNamespace + testthat::local_mocked_bindings( + requireNamespace = function(package, ...) { + if (identical(package, 'ggplot2')) return(FALSE) + original_require(package, ...) + }, .package = 'base') + expect_error(plot_cv(cv_plot_example(FALSE), lambda_1_all = c(10, 0), + lambda_2_all = c(0, 1)), + 'Install it with install.packages("ggplot2")', fixed = TRUE) +}) + +test_that('CV heatmaps explain when the plotting dependency needs updating', { + skip_if_not_installed('testthat', minimum_version = '3.2.0') + # Simulate availability/version checks without changing installed packages. + original_require <- base::requireNamespace + original_version <- utils::packageVersion + testthat::local_mocked_bindings( + requireNamespace = function(package, ...) { + if (identical(package, 'ggplot2')) return(TRUE) + original_require(package, ...) + }, .package = 'base') + testthat::local_mocked_bindings( + packageVersion = function(pkg, ...) { + if (identical(pkg, 'ggplot2')) return(package_version('3.3.6')) + original_version(pkg, ...) + }, .package = 'utils') + expect_error(plot_cv(cv_plot_example(FALSE), lambda_1_all = c(10, 0), + lambda_2_all = c(0, 1)), + 'requires ggplot2 >= 3.4.0. Update it', fixed = TRUE) +})