Changes in version 0.6.0 (2026-10-24) Breaking changes - as_scmp() no longer converts Monocle3 cell_data_set objects. The from = "cds" route and the interactive Shiny app it opened to pick the root and the branching paths are removed, together with the exported m3_select_path() and the internal shinySelect() and align_pseudotime(). as_scmp() now takes any SingleCellExperiment (subclasses other than cell_data_set used to return NULL), and from defaults to "sce", its only accepted value. - The interactive, align_pseudotime and anno_col arguments of as_scmp() are removed. They only applied to the Monocle3 route, where align_pseudotime = TRUE never worked. Scripts written for earlier versions, including the README, the vignettes and the code accompanying the 2024 paper, pass align_pseudotime; delete that argument from the call. - The interactive branch selector will be maintained in a separate package that depends on Monocle3. Until then, the removed code is preserved on the archive/shiny-monocle3 branch (commit 65b5458). - To analyse a Monocle3 trajectory now, extract the pseudotime and a path label for every cell and pass the counts and that cell table to create_scmp(). The "Trajectory input" vignette shows the extraction steps. - showCoeff(), showGroupCoeff(), showInflu(), showParams(), showSigProf(), showSol() and showTS() no longer have view and return arguments: they always return their result and never open the data viewer. Delete view = and return = from calls; to browse a result, use View(showCoeff(obj)). - .ScMaSigPro_show(), the helper behind the show() method, is no longer exported. Printing an object is unchanged. - sc.p.vector() and sc.t.fit() run their per-gene fits with BiocParallel. The parallel and n_cores arguments are replaced by BPPARAM, which defaults to BiocParallel::SerialParam() and fits one gene after another, as parallel = FALSE did. Replace parallel = TRUE, n_cores = k with BPPARAM = BiocParallel::MulticoreParam(k) on Linux and macOS, or with BPPARAM = BiocParallel::SnowParam(k) on any system. Windows, where parallel = TRUE fell back to one core with a warning, can now fit in parallel with SnowParam(). The results are identical with every backend. - sc.p.vector() and sc.t.fit() no longer show a progress bar; with verbose = TRUE each reports the number of genes and the backend in one message. For a per-gene progress bar, pass a param created with progressbar = TRUE, such as BPPARAM = BiocParallel::SerialParam(progressbar = TRUE). - plotTrendCluster() loses its parallel argument, which had no effect; delete it from calls. - ScMaSigPro objects have a new version slot, the version of scMaSigPro that created them, and an updateObject() method brings objects saved by earlier versions up to date. An object saved with saveRDS() or save() by scMaSigPro 0.0.5 or earlier has no version slot: printing it says so, the steps, pb_counts(), the plots, queryCoeff() and the show*() helpers stop with "update it with updateObject() first", and the setters stop with "slots in class definition but not in object: "version"", until it is updated, for example with ob <- updateObject(readRDS("ob.rds")). Its data and results are kept. Objects binned by 0.0.4 cannot be updated because their Dense slot is invalid; updateObject() stops with the command that bins their cells again with sc.squeeze(), which replaces the 0.0.5 advice to run sc.squeeze() on them directly. The bundled scmp.ob dataset is regenerated with the slot; its other slots are identical. - The log_offset argument of sc.p.vector() and sc.t.fit() and the log_offset parameter are removed; delete log_offset = from calls. Objects saved with the parameter stop the steps with "update it with updateObject() first". updateObject() removes it and, when it was FALSE, replaces the stored size factors by their logarithm and warns that the fits must be run again. - plotTrendCluster() no longer has the arguments plot and includeInflu, plotIntersect() no longer has width_ratio and show_sets_size, queryCoeff() no longer has includeInflu, and sc.cluster.trend() no longer has verbose. None of them had an effect; delete them from calls. Changes affecting results - The version number moves from 0.0.5 to 0.6.0, a minor release, because the changes in this section change the results of some analyses. - sc.squeeze() with bin_method = "Freedman.Diaconis" or "Scott.Normal" now makes the number of bins that the rule gives. Both rules give a bin width, 2 IQR / n^(1/3) and 3.49 sd / n^(1/3) for the n pseudotime values of a path, and that width was used as the number of bins, so the number of bins depended on the units of the pseudotime: on splat.sim, whose pseudotime runs from 1 to 100, the two paths got 21 and 24 bins with Freedman-Diaconis and 20 and 23 with Scott, where both rules give 5 and 5, and with a pseudotime from 0 to 1 each path got a single bin. The number of bins is now the range of the path's pseudotime divided by the width, multiplied by drop_fac as for the other rules, so splat.sim gets 5 and 5 bins with both rules, and the five paths of multi.lin.sce get between 3 and 9 bins instead of between 2 and 5. When the width is 0, because the interquartile range or the standard deviation of the pseudotime is 0, the rule gives one bin and says so in a message. This correction changes the bins, and every result computed from them, for these two rules only; "Sturges" (the default, the rule described in the paper), "Sqrt", "Rice" and "Doane" keep their number of bins. With split_bins = TRUE the two rules now split the bins of both bundled datasets instead of stopping with "makes no progress". There is no option to bring back the old numbers of bins, which came from using the width as a count; to reproduce an analysis made with these rules by version 0.0.5 or earlier, run that version. - sc.p.vector(link = "identity") now fits with the identity link. link only renamed the link of the family, while glm() fits with the link functions of the family, so every fit of sc.p.vector() and sc.t.fit() used the log link whatever link said, and objects fitted with link = "identity" by earlier versions hold log-link fits under that name: run sc.p.vector() on them again. The default family is now negative.binomial(theta = 10, link = link), a family passed in family keeps its own link, and the stored link parameter, the default of sc.t.fit(), is the link the fits used. family = gaussian() without link is therefore recorded with the identity link instead of "log", and no longer fails with "cannot find valid starting values" when a bin has a zero count. Both functions now stop with an error when link is given and is not the link of family (it used to be ignored), when family is not a family object, and when offset = TRUE is combined with a link other than the log link: the size-factor offset is added to the linear predictor, so it scales the fitted mean under the log link but only shifts it under the identity link. Pass offset = FALSE with the identity link, and with family = gaussian(), which used to run with the offset. Results with the log link, including the defaults, and with the maSigPro settings family = gaussian(), link = "identity", offset = FALSE are unchanged. plotTrend() draws the fitted curve on the scale of the link, and now draws the points of identity-link fits on that scale by default (see "New features"). - The reference path no longer depends on the locale of the R session. sc.squeeze() put the paths in the order of the session's collation, and sc.set.poly() takes the first path as the reference that the other paths are compared with, so the same labels could give another reference path, other design columns and other gene lists in an interactive session than under R CMD check, which uses the C locale: on multi.lin.sce with the root labelled "root", the reference was "Y_15" in the C locale and "root" in en_US.UTF-8. sc.squeeze() now orders the paths by the levels of the path column when it is a factor, as R's model formulas do, and otherwise in C-locale (byte) order in every locale: digits before uppercase letters, uppercase before lowercase. Nothing changes in the C locale for character labels, nor in any locale for labels that sort there as they do in C, such as splat.sim's "Path1" and "Path2", nor for a factor whose levels are in that order. An object built in another locale from labels that sort differently there, typically labels that differ in case such as "root" and "Y_15", can get another reference path, and a factor whose levels are in another order now gets its first level as the reference: the full-model p-values of sc.p.vector() are the same up to rounding, but the coefficients, the models selected by sc.t.fit() and the gene lists change. To keep the reference path of such an analysis, name it with the new reference argument, for example sc.set.poly(ob, reference = "root"). - sc.squeeze() puts each cell in the bin whose numeric bounds hold its pseudotime. It read the bounds back from the interval labels of cut(), which round them to three significant digits, so a cell lying between a bound and its rounded value was counted in one bin and placed in the next one, and the scmp_l_bound and scmp_u_bound columns of the bins held the rounded values. The bins are now the equal-width intervals that the paper describes, of size range / number of bins (Srivastava et al., Bioinformatics 2024, Section 2.1), and these columns hold their exact bounds. On splat.sim, whose pseudotime takes whole values, no cell changes bin and only the stored bounds change. On multi.lin.sce, 12 of the 1977 cells move to the neighbouring bin with the default rule, "Sturges", and between 4 and 15 with the other rules, which changes the size factors, p-values and gene lists computed from these bins. The labels keep three significant digits whatever the size of the values, so the error grew with the magnitude of the pseudotime: with 1000 added to the pseudotime of splat.sim, 50 of its 200 cells were in the wrong bin. The bins no longer depend on the origin or the units of the pseudotime, and sc.squeeze() is about four times faster on multi.lin.sce. - The bins of sc.squeeze() are closed on the left and open on the right, except the last bin of a path, which is closed on both ends, as the paper states: "bins are structured as left-closed and right-open intervals, except for the last bin, which is closed on both ends" (Srivastava et al., Bioinformatics 2024, Section 2.1). The code was meant to follow this rule, but the test that applied it compared a logical vector with max() and was true for every cell, so every bin was closed on both ends and a cell on the bound of two bins went to the earlier bin. Such a cell now goes to the later bin. Only cells exactly on a bound move: on splat.sim, whose pseudotime takes whole values, one cell with the default rule, "Sturges" (Cell169, at 50), 11 with "Sqrt", one with "Rice" and none with the other rules; on multi.lin.sce, none. A moved cell changes the size factors of every bin a little, and so the p-values, coefficients and gene lists: on splat.sim with the default settings, one of the 49 genes of sc.p.vector() is replaced by another and the "all" list of sc.filter() grows from 31 to 32 genes, and in the Basic Workflow vignette the Path1 list grows from 24 to 25 genes (the bundled scmp.ob is regenerated). With split_bins = TRUE, the bins of a split follow the same rule, and a split counts only the cells of the bin it splits, where it also counted a cell on its lower bound that belonged to the previous bin. - sc.squeeze(split_bins = TRUE) splits the bins of a path that hold more cells than the mean plus the standard deviation of the sizes of the path's initial bins, empty bins included. The mean and the standard deviation are no longer rounded to whole numbers before they are added. - sc.squeeze(prune_bins = TRUE) removes the bins of a path that hold fewer cells than the mean minus the standard deviation of the sizes of the path's initial bins, empty bins included. The limit is no longer computed over the bins of all paths together after the split, nor made positive with abs(). - sc.squeeze() with bin_method = "Sturges" multiplies log2(n) + 1 by drop_fac and rounds the product up, as for the other rules, also for the bins of a split; it no longer rounds log2(n) + 1 up before drop_fac. - The offset of sc.p.vector() and sc.t.fit() is the logarithm of the size factor of each bin. With the default log_offset = FALSE it was the size factor itself. - sc.t.fit() uses the offset that sc.p.vector() computed from every gene it fitted and stored in the Design slot, instead of computing the size factors again from the non-flat genes. It computes the offset in the same way when the object has none. - With offset = TRUE, sc.p.vector() and sc.t.fit() fit the raw counts, and the size factors are computed from the raw counts by the median-of-ratios method, over the genes without a zero count. Both used the counts plus 1. When every gene has a zero count in some bin, sc.p.vector() stops with an error that suggests fewer bins or offset = FALSE. - sc.filter(vars = "groups", intercept = "none") leaves out the intercept of the reference path and the dummy of every other path, and lists the reference path first. It left out the first two terms of the model, which is the same with two paths; with more paths the dummies of the other paths still counted, and with a single path the first term in pseudotime was left out, or the call failed with degree 1. New features - assign_paths() turns the lineage weights and pseudotimes of a trajectory tool, one column per lineage as slingshot and tradeSeq give them, into one pseudotime value and one path label per cell. A cell is on a lineage when its weight there is at least min_weight (0.5). Cells on every lineage are labelled as the root, cells on some of the lineages get the names of those lineages joined by shared_sep ("_links_", the separator sc.restruct() expects), and cells on one lineage get its name, so the result is the cell table that create_scmp() takes and the root and link paths that sc.restruct() restructures. - paths_from_graph() gives the lineage weights of the cells of a trajectory graph, such as the principal graph of Monocle3 or a minimum spanning tree of clusters: each lineage is the shortest path from the root node to an end node, and a cell has weight 1 on the lineages that pass through its node. With a pseudotime, assign_paths() turns these weights into path labels. It needs the suggested package igraph. - scmpResults() returns the per-gene results of an ScMaSigPro object as a DataFrame with one row per gene and the columns of the steps that have run: the full-model p-value and adjusted p-value and the non-flat selection of sc.p.vector(), the p-value and R-squared of the model selected by sc.t.fit() and whether the gene has influential observations, one logical column per gene list of sc.filter(), and the cluster of sc.cluster.trend(). Values a gene does not have are NA; logical columns are FALSE for the genes outside a set. scmpToRowData() writes these columns, prefixed with scmp_, into the rowData() of a SingleCellExperiment (or any SummarizedExperiment), matching the genes by name and replacing the scmp_ columns of an earlier call. - sc.set.poly() has a reference argument that names the reference path, the path that the dummies and their products with time compare the other paths with, for example sc.set.poly(ob, reference = "root"). The default, NULL, takes the first path of the binned data, as before. The bins keep their order and the full-model p-values are the same with any reference; the coefficients, the models selected by sc.t.fit() and the "groups" lists of sc.filter() are those of the chosen comparison. - sc.p.vector() has two count filters: min_counts, the smallest total count of a gene over the bins of the design, and min_bins_expressed, the smallest number of bins in which its count is above 0. Genes below either are left out of the fit, have no p-value and do not count in the multiple-testing correction, and a verbose run says how many were left out. The size factors of the offset are computed before these filters, on every gene left by min_na and by the removal of genes with only zero counts, so the filters change the results of the genes they keep only through the multiple-testing correction. Both default to 0, which keeps every gene, so results are unchanged. The values used are stored in the new min_counts and min_bins_expressed parameters; updateObject() sets them to 0 in objects saved before they existed, and the bundled scmp.ob is regenerated with them, its other slots unchanged. min_na stays as it was, and its help page now says what it is: the smallest number of bins with a value (not NA), as min.obs in maSigPro, which removes genes only from counts with missing values, such as microarray data given to create_scmp(use_as_bin = TRUE). - sc.p.vector() can estimate the theta of the negative binomial family instead of using the fixed theta of family: with theta_estimate = "gene" it estimates theta by maximum likelihood on each gene's full model and fits the gene with its own theta, and with theta_estimate = "global" it fits every gene with the median of these estimates. The estimates and the choice are kept in the distribution parameter (theta_gene and theta_estimate), so sc.t.fit() and plotDiagnostics() fit each gene with the same theta, and showParams() shows "Negative Binomial(theta per gene)" for "gene". The default, "none", keeps the fixed theta, 10 unless family says otherwise, as in maSigPro and the 2024 paper, so results are unchanged. Estimation needs a negative binomial family. - plotTrend() and plotTrendCluster() choose the scale of the points from the link of the fits. The fitted curve is the polynomial of the selected model on the scale of the link: the log of the mean for log-link fits, the mean itself for identity-link fits, such as those of link = "identity" or family = gaussian(). logs = NULL, the new default, logs the points for log-link fits, as logs = TRUE did, and leaves them as they are otherwise, so that points and curve are on the same scale; the y-axis label then has no "log". Plots of log-link fits, including every plot made with the default settings, are unchanged, and logs = TRUE and logs = FALSE work as before, except that the points of an identity-link fit are no longer divided by size factors left in the object by an earlier log-link fit. - sc.p.vector() has a test argument for the test of each gene's full model against its intercept-only model. "chisq", the default, is the chi-squared test it ran before; "F" is the F test of anova(test = "F"), which uses the residual degrees of freedom of the full model for the estimated dispersion. The Gaussian family keeps the F test with either value. "F" is refused for families whose dispersion is fixed, such as the binomial and Poisson families. The choice is kept in the new test parameter, which updateObject() sets to "chisq" in objects saved before it existed. Bug fixes - sc.t.fit() with selection_method = "forward", "two.ways.backward" or "two.ways.forward" failed on R >= 4.3 unless parallel = TRUE was set, with the error "'length = ' in coercion to 'logical(1)'" raised by its progress-bar code. The three methods now run with the default arguments and, like "backward", reproduce maSigPro::T.fit() exactly on the package's Gaussian and negative binomial test data, apart from the entries of the influence matrix of the two forward methods, which maSigPro leaves empty. - sc.t.fit() with selection_method = "two.ways.backward" or "two.ways.forward" could run forever on some genes, because removing and re-adding terms can cycle; maSigPro's two.ways.stepback() and two.ways.stepfor() do the same. The selection now stops when it comes back to an earlier state: the two-ways forward selection with the current model, and the two-ways backward selection after removing, as "backward" does, the least significant term while its p-value is above the level, so that every term of the model it returns is significant. Where the copied maSigPro code stopped with an error, the selection now returns a model: tied p-values when a term is removed, a design with at most one estimable term, column names that are not syntactic (forward and two-ways methods), and a two-ways forward step that removes every term (intercept-only model). Every other gene gets the same model as before. - The validity methods of ParameterConfig, MatrixDesign, VariableProfiles, Estimates and Significant, the classes of the slots of an ScMaSigPro object, return a description of every problem instead of stopping at the first one, as S4 validity methods should; those of Estimates and Significant checked nothing. They now check what the workflow relies on: one value in each Parameters slot and a family object in distribution, design matrices and offsets with one row per bin, adjusted p-values named like the p-values, t-score and path-coefficient matrices with the genes of the coefficient matrix, and gene lists made of gene names. Every object the workflow creates passes these checks, so results are unchanged. - validObject() on an ScMaSigPro object reports the problems of every slot, each prefixed with the slot name, instead of stopping with the error of the first invalid slot. validObject(new("ScMaSigPro")) now returns TRUE; the empty Sparse and Dense slots of a new object used to be invalid. - sc.set.poly() failed with "incorrect number of dimensions" when there were several paths and only one bin had the earliest binned pseudotime, as with create_scmp(use_as_bin = TRUE) and a single sample at the first time point. It now builds the same design as for any other input. - The setters of the Sparse and Dense slots check their value instead of storing it as given, which could leave an invalid object or metadata that no longer matched the counts. cSparse<- (and cSparse() with value) needs one row per cell, named after the cells and in their order; a table without rownames is taken in the order of the cells and no longer drops the cell names. eSparse<- and eDense<- need a matrix with the dimensions of the slot whose row and column names, when present, are those of the slot in the same order; missing names are filled in, so pb_counts(assay_name =) on such an assay no longer returns zero counts. cDense<- still accepts a table with another number of bins, as when bins are rebuilt by hand in the "Shared root" vignette, but it now removes the counts that no longer fit, with a message, so the object stays valid until pb_counts() computes the counts of the new bins. - The setters (cSparse<-, cDense<-, eSparse<-, eDense<-, pathAssign<-, predictors<-, and cSparse(), cDense(), pathAssign() and predictors() with value) check the object they return with validObject(). pathAssign<- and predictors<- used to store any matrix, which left an invalid object when its rows were not the bins of the other design matrix (one per bin, with the same names) or when the predictor matrix had another number of columns than the design has terms; such a matrix is now an error and the object is unchanged. Replacing a design matrix by one with the same rows and columns works as before. - as_scmp(), create_scmp(), sc.set.poly(), sc.p.vector(), sc.t.fit(), sc.filter() and sc.cluster.trend() check the object they return with validObject(), as sc.squeeze(), sc.restruct() and pb_counts() already did. Arguments that used to be stored as invalid parameters are now errors naming the parameter: two values for p_value in sc.p.vector(), log_offset = NA with offset = FALSE in sc.p.vector() and sc.t.fit(), and a missing or repeated root_label or path_prefix in as_scmp(). An object broken by hand stops at the next step instead of passing through. Results on valid input are unchanged. - The steps, pb_counts(), the plots, queryCoeff() and the show*() helpers check the object they are given before doing any work. An object saved by an earlier version of scMaSigPro stops at once with "update it with updateObject() first" instead of at the end of a step. A Dense slot without pseudo-bulk counts for its bins, as after changing the bins with cDense<-, asks for pb_counts() instead of failing in assay() or with "subscript out of bounds". sc.p.vector() before sc.set.poly(), sc.t.fit() when sc.p.vector() selected no gene, and sc.cluster.trend() before sc.filter() name the step to run instead of failing with "'min_na' is too high", "'x' must be an array of at least two dimensions" or an error from hclust(). sc.cluster.trend(), plotTrend(), plotTrendCluster() and plotDiagnostics() now reject an object of another class with the message of the other functions, which sc.squeeze(), sc.restruct(), pb_counts(), plotBinTile() and plotIntersect() now share. - Arguments that take one of a few values must be one of them, given once, and the column names and labels given to as_scmp(), create_scmp(), sc.squeeze(), sc.set.poly(), sc.restruct() and pb_counts() must be single strings. Two or more values used to pass the check and then fail with "the condition has length > 1" or "Unable to estimate bin size", or to be rejected by validObject() after the work was done; they now stop at once with the argument's message. The choices are checked before any work: bin_method and aggregate of sc.squeeze(), aggregate of sc.restruct() and pb_counts(), selection_method of sc.t.fit() (whose error no longer names the function), vars and, with vars = "groups", intercept of sc.filter() (an unknown intercept used to fail with "object 'select_cols' not found"), geneSet, cluster_by, cluster_method, fill_na and use_dim of sc.cluster.trend(), summary_mode and, with logs = TRUE, logType of plotTrend(), model of plotDiagnostics(), and query and change of queryCoeff(). - sc.filter() with its default vars returns the "all" gene list instead of failing with "the condition has length > 1". sc.squeeze() with additional_params = NULL or list(), and as_scmp() with additional_params = NULL or without labels_exist, use the defaults instead of failing with "argument is of length zero". - sc.cluster.trend() checks that the gene set is large enough: at least two genes, as many as k for "hclust" and more than k for "kmeans" and "Mclust". A smaller set used to fail inside hclust(), cutree(), kmeans() or Mclust(), and a set of one gene clustered with "kmeans" or "Mclust" returned clusters of the coefficients or bins instead of genes. - sc.filter(vars = "each") failed with "got class "matrix"" when every term selected the same number of genes, as with term_p_value = 1, and with "got class "character"" when no term selected a gene, as with term_p_value = 0 or an rsq that no gene reaches. It now returns one gene list per term in every case. - sc.t.fit() ignored its p_value argument and always selected the terms at the level stored by sc.p.vector(). The argument now sets the level, and its default is still the stored level, so calls without it are unchanged. When no gene keeps a coefficient at that level, sc.t.fit() stops with "No gene has a coefficient significant at p_value = ..." instead of "'data' must be of a vector type, was 'NULL'". - sc.p.vector() with no significant gene replaced the offsets but kept the profile and the parameters of the object, so after an earlier run sc.t.fit() went on to fit the genes of that run. It now writes the profile (the p-values, and no non-flat gene) and the parameters in this case too, and sc.t.fit() then stops with "No gene with a non-flat profile found". - sc.p.vector() failed with "invalid 'length' argument" or "dim(X) must have a positive length" when one gene was left after removing the genes with too many missing values or only zero counts; it now fits that gene. - sc.p.vector() and sc.t.fit() give the same results whatever the names of the design columns. With path labels or predictors<- names that contain spaces or |, as multi.lin.sce labels joined with link_sep = "\\|", sc.t.fit() returned no p-value, coefficient or t-score for any term (NA in showSol(), showCoeff() and showTS()); with a repeated name sc.p.vector() failed with "duplicated name ... in data frame using '.'"; a column named y replaced the counts of every gene, and one named (Intercept) took the p-value of the intercept. The fits now name the terms by their position. Designs with unique syntactic names, such as every design sc.set.poly() builds from syntactic path labels, give identical results. - sc.cluster.trend(cluster_method = "Mclust") failed with "could not find function "mclustBIC"" unless mclust was attached with library(mclust). It now needs mclust only to be installed, and gives the same clusters as with mclust attached. - sc.cluster.trend(cluster_method = "Mclust") named the clusters "1", "2", ... instead of after the genes when the model had one component, as with k = 1. - sc.cluster.trend() with an hclust.distance other than "cor" clustered the coefficients (or the bins, with cluster_by = "counts") instead of the genes, and failed with "elements of 'k' must be between 1 and ..." when k was larger than their number. It now clusters the genes with that distance. - sc.t.fit() returned coefficient, t-score and path-coefficient rows, all zero, for the genes that have no row in showSol() because their selected model has no significant coefficient, as happens with the forward methods and with a small p_value. These rows are removed, as maSigPro does, so showCoeff(), showTS(), showGroupCoeff(), queryCoeff() and sc.cluster.trend(cluster_by = "coeff") list the genes of showSol(). The genes with influential observations are unchanged. - sc.squeeze(split_bins = TRUE) never returned when splitting a large bin gave the same bin back, as it did with bin_method = "Freedman.Diaconis" or "Scott.Normal" on the bundled datasets before their number of bins was corrected (see "Changes affecting results"). It now stops with "Splitting the bins of '' with bin_method = '' makes no progress" when a split gives back bins it has already produced. With the other rules, the check does not change the bins of runs that ended before. - sc.restruct() read the paths from a column named group instead of the path column of the object, so with another path_col the bins of the linked paths were added twice and the call failed with "duplicate 'row.names' are not allowed". It now uses the path column. - sc.restruct() with link_sep = "|" failed with "replacement has 1 row, data has 0", because the separator was used as a regular expression, in which | matches the empty string. A separator that the link label contains as written is now matched as fixed text; otherwise it is still a regular expression, so link_sep = "\\|" works as before. The default stays "_links_". - eSparse() and eDense() with their default i returned the first assay of the slot whatever its name, so for an object built from a SingleCellExperiment whose first assay is logcounts, eSparse(ob) returned the log counts. They now return the counts (bulk.counts) assay, and the first assay only when the slot has none of that name, as before. - eSparse<- and eDense<- worked only with a base matrix and an explicit assay name; anything else failed with "unable to find an inherited method". They now also take a dgCMatrix or a data frame of numbers, and without a name they set the counts (bulk.counts) assay, as their help pages said. - showInflu() failed with "tscore is not computed yet" after sc.t.fit() when no gene had influential observations, and so did showCoeff(), showTS(), showSol(), showSigProf() and showGroupCoeff() with includeInflu = FALSE. It now returns the empty matrix of influential genes; before sc.t.fit() it stops with "'influential' is not computed yet". - plotDiagnostics() failed with "Can't add stdRes_vs_fitted_plot to a object" unless patchwork was attached. It now loads patchwork itself and gives the same plot; when patchwork is not installed it says how to install it. - Printing an object restructured by sc.restruct() listed the paths of the cells, including the root and link paths that the end paths now contain. Once the cells are binned, printing lists the paths of the bins, in the order of the cells. - plotTrend() and plotTrendCluster() drew the term of degree 6 with the fifth power of the binned pseudotime, and failed with "invalid 'times' argument" when poly_degree was above 6. The curves now use one power per term; for degrees up to 5 they are unchanged. - plotTrendCluster() titled the panels by the order in which the clusters first appear among the genes, but took the number of genes in each title from the cluster numbers, so with cluster_method = "kmeans" or "Mclust" a panel could show the trend of one cluster under the number and size of another. The panels now follow the cluster numbers of sc.cluster.trend(); "hclust" numbers its clusters in that order already and gives the same plot. - plotTrendCluster(lines = TRUE) failed with "values must be type 'double'" on fits without an offset, because the path labels took the place of the counts in the lines, and with "object 'points_combined' not found" on fits with an offset when points = FALSE. - plotTrendCluster() left out the genes whose trend could not be computed, such as genes outside the gene lists with significant = TRUE, without saying so; it now warns with their names. It checks summary_mode and, with logs = TRUE, logType as plotTrend() does: a wrong value used to give a plot without data. - create_scmp() ignored bin_counts, bin_cell_data and feature_data, and checked bin_counts against bin_cell_data by the wrong dimension, so a call with bins failed unless there were as many genes as bins. It now builds the Dense slot from bins made outside scMaSigPro: bin_counts has the genes of counts as rows and one column per bin, and bin_cell_data one row per bin with the binned pseudotime in ptime_col and the path in path_col (and, optionally, the bin sizes in scmp_bin_size); the workflow then continues with sc.set.poly(). feature_data, one row per gene, becomes the rowData() of the slots it builds. Calls without these arguments are unchanged. - sc.t.fit() with selection_method = "forward" or "two.ways.forward" returned an influence matrix (showInflu()) whose entries were all NA, as maSigPro::T.fit() does, because the forward selection numbered the rows of its fits instead of keeping the bin names, so the influence measures could not be matched to the bins. The entries now say, for every bin of a gene with influential observations, which of DFFITS and Cook's distance is above its cut-off, as they do for "backward". The genes with influential observations were already the right ones: the columns of showInflu(), includeInflu, the coefficients, the gene lists and the clusters are unchanged. - sc.p.vector() failed with "6 for 'min_na' is too high", whatever min_na was, on an object with a single gene, because the counts of that gene were taken as a vector instead of a one-row matrix. It now fits the gene. - sc.p.vector() and sc.t.fit() stop with an error when the link functions of the family are those of another link than the one it is labelled with. Earlier versions renamed the link of the family without changing its functions (see "Changes affecting results"), so an object fitted with link = "identity" by version 0.0.5 or earlier holds a family labelled "identity" whose functions, like its fits, are those of the log link; sc.t.fit() fitted such an object again with the log link under the identity label, and now stops and asks to run sc.p.vector() again. updateObject() warns about these objects. A family whose link was renamed by hand is refused the same way; links that make.link() does not know, such as power links, are not checked. - sc.squeeze(split_bins = TRUE) no longer removes cells from the object. A bin with too many cells was split between the rounded bounds of the labels of cut(), so a cell just outside them was in none of the new bins and was removed from the Sparse slot with the warning "cell(s) could not be assigned to a bin": 3 of the 1977 cells of multi.lin.sce with the default rule and up to 212 with the others. When the rounded bounds of a bin held none of its cells, the split failed with "'from' must be a finite number". The new bins of a split now hold every cell of the bin they split, and cover it without gaps: the first starts at its lower bound and the last ends at its upper bound, where they spanned only the range of its cells. - sc.squeeze(split_bins = TRUE) failed with "missing value where TRUE/FALSE needed" when the rule gave a path a single bin, as "Freedman.Diaconis" does for a path whose pseudotime has an interquartile range of 0: a split takes the bins larger than the mean plus the standard deviation of the bin sizes of their path, and the size of a single bin has no standard deviation. Such a path now keeps its bin. - sc.p.vector(offset = FALSE) and sc.t.fit(offset = FALSE) no longer leave the offset of an earlier fit in the Design slot, and sc.set.poly() stores no offset. plotTrend() and plotTrendCluster() divide the points by the size factors only when the fits used the offset; they divided them by an offset left by an earlier fit. updateObject() removes such offsets from saved objects. - Error and progress messages are corrected: misspellings (doesn't exit, Overwritting, onservations, selction, select on of), the class name (ScMaSigPro, not scMaSigPro), and pointers to functions that do not exist (plotIntersect() now points to sc.filter(), showPoly() to sc.set.poly()). plotDiagnostics() reports an invalid model instead of a missing gene, and messages no longer contain line breaks and indentation copied from the source code. Code that matches the old wording, such as the Overwritting assay warning of eSparse<- and eDense<-, must be updated. - sc.set.poly() stops with "'poly_degree' must be a number of at least 1" when poly_degree is below 1, missing or not a single number. Such values gave a degree-1 design recorded as degree 0 with several paths, and an error in sc.t.fit() with one path. A fractional degree still uses its whole part, now also with one path, where a value between 1 and 2 gave a quadratic design recorded as degree 1. - as_scmp() passes its input through updateObject(), so a SingleCellExperiment saved by an older version of SingleCellExperiment, such as the bundled splat.sim and multi.lin.sce, is stored up to date with labels_exist = FALSE as it already was with labels_exist = TRUE, and updateObject() on the new object has nothing to do. Counts and cell metadata are unchanged. - sc.squeeze() stops with "It should be at least 0.3." when drop_fac is below 0.3; the message said "between 0.3 and 1", although larger values are accepted. - Printing an ScMaSigPro object ends with a newline, so the console prompt no longer follows its last line. Documentation - The ScMaSigPro class help page explains why the class holds two SingleCellExperiment objects and its own result classes instead of extending SingleCellExperiment, and no longer says that it inherits from it. The help page of assign_paths() describes the input contract: the cell table of pseudotime and path labels that create_scmp() and as_scmp() take, which code outside the package, such as a companion package for Monocle3, should return. as_scmp() and create_scmp() point to assign_paths() and paths_from_graph(). - The vignettes are rewritten: "Quick start" (vignette("scMaSigPro")) replaces "Quick Start Guide", "Shared root" replaces "Common Root Cells", "maSigPro and the ScMaSigPro class" merges "scMaSigProClass" and "Bridging maSigPro Analysis", and "Trajectory input", "Model and tuning" and "Frequently asked questions" are new. They read the data and the results through accessors. The package website, https://spriyansh.github.io/scMaSigPro/, holds them with the help pages and this news. - ?scMaSigPro opens a help page for the package, and citation("scMaSigPro") gives the reference of the article that describes the method. - Every exported function and class has a Value section and a runnable example. Each accessor shares one help page with its replacement function, the readers of the results of sc.t.fit() share ?showSol, and showParams() and showPoly() share ?showParams; the old topic names open these pages. The pages link the other functions of their family, cite the methods they implement or use, document the bundled data sets column by column, and are written in British English. - The error of as_scmp(from = "cds") points to the "Trajectory input" vignette instead of the removed "Common Root Cells" vignette. - CONTRIBUTING.md explains how to report a problem and how to propose a change, CODE_OF_CONDUCT.md adopts the Bioconductor Code of Conduct, and the repository has a pull request template, issue templates and a release checklist. The README shows badges for the checks, the test coverage, the lifecycle stage, the article and the latest tag. - Authors@R gives the names of María José Nueda and Marta Benegas Coll as the article does, and lists the European Union as funder. Dependencies - shiny, plotly, igraph and scales are no longer dependencies, so installing scMaSigPro pulls in 55 non-base packages instead of 98. - assertthat is no longer a dependency. Argument checks use an internal helper and stop with the same messages. A check given NA or the wrong number of values (for example two values for an argument that takes one) now reports its own message instead of assertthat's generic "assert_that: ..." text. - stringr is no longer a dependency; string handling uses base R and gives the same results. Without assertthat and stringr, installing scMaSigPro pulls in 52 non-base packages instead of 55. sc.restruct() still accepts a regular expression as link_sep, such as "\\|". - e1071, entropy and MatrixGenerics are no longer dependencies. Binning computes the skewness (Doane rule) and the equal-width bin counts in base R with the same results, and row sums and means use base R or Matrix. Matrix, which was always installed with SingleCellExperiment, is now declared in Imports. - dplyr is no longer a dependency, and rlang moves from Imports to Suggests because only the tests use it. sc.squeeze(), plotTrend() and plotTrendCluster() group and summarise with base R and give the same bins, counts and plot data; the data of the lines layer of plotTrend() is a data frame instead of a tibble. - magrittr is no longer a dependency: the package code no longer uses the %>% pipe, which scMaSigPro never exported. - mclust moves from Imports to Suggests. sc.cluster.trend(cluster_method = "Mclust") and the plot of plotIntersect() check that mclust and UpSetR are installed and say how to install them; plotIntersect(return = TRUE) does not need UpSetR. - ggpubr is no longer suggested: the vignettes arrange plots with patchwork. - The ggplot2 functions in use are imported one by one instead of the whole package, and the dgCMatrix class is imported from Matrix. With these changes installing scMaSigPro pulls in 41 non-base packages instead of 52, and Imports lists 12 packages instead of 18. - parallel and parallelly are replaced by BiocParallel, and utils is no longer imported. Installing scMaSigPro pulls in 48 non-base packages instead of 41. - maSigPro moves from Imports to Suggests: only the tests that compare scMaSigPro with maSigPro and the "maSigPro and the ScMaSigPro class" vignette use it, and the vignette shows that code without running it when maSigPro is not installed. Installing scMaSigPro pulls in 44 non-base packages instead of 48 (maSigPro, mclust, venn and admisc are no longer installed with it). - igraph is suggested again, for paths_from_graph() only. Installing scMaSigPro still pulls in 44 non-base packages. - slingshot is suggested, for the test and the "Trajectory input" vignette that run a slingshot trajectory of multi.lin.sce through assign_paths() and the workflow; both skip it when slingshot is not installed. - DelayedMatrixStats is suggested: the slingshot section of the "Trajectory input" vignette needs it through slingshot's lineage inference. - scMaSigPro depends on R (>= 4.6.0) instead of R (>= 4.0). Internal - Unused code is removed: debugging leftovers, commented-out code, the never-enabled useWeights branches of sc.p.vector() and sc.t.fit(), the unexported and broken ScMaSigPro() constructor, and 19 unused importFrom directives. Results are unchanged. - The size factors that sc.p.vector() and sc.t.fit() use as offsets are computed by a new internal function written from the published median-of-ratios method (Anders and Huber 2010, Genome Biology 11:R106) instead of by a copy of DESeq2's estimateSizeFactorsForMatrix(). The copy was LGPL (>= 3) code without credit in a GPL (>= 2) package. No DESeq2 code remains, so the licence stays GPL (>= 2) and no DESeq2 author needs to be added to Authors@R: the function implements the method as published and copies no code. Offsets and results are identical. When every gene has a missing count, sc.p.vector() now stops with "Cannot compute size factors" instead of an unrelated error from the model fit. - sc.t.fit() uses one per-gene function for its four selection methods instead of four copies that differed only in the selection function, and the internal get_os(), used only to keep Windows sequential, is removed. - The four stepwise selection methods of sc.t.fit() are implemented by new internal functions written from the published maSigPro method (Conesa et al. 2006, Bioinformatics 22:1096) instead of by a modified copy of maSigPro's stepback(), stepfor(), two.ways.stepback() and two.ways.stepfor(), and the internal extract_fitting() no longer calls maSigPro::position(). Terms are handled by column position, so the internal clean_string() helper is removed. On every design where the copied functions returned a model, the new ones fit the same sequence of models and return an identical fit; the maSigPro oracle tests and the golden results are unchanged. - sc.set.poly() builds the polynomial design, and sc.t.fit() and plotTrend() the coefficients of each path, with new internal functions written from the published maSigPro model (Conesa et al. 2006, Bioinformatics 22:1096) instead of maSigPro's make.design.matrix() and reg.coeffs(). For every whole degree of at least 1 where maSigPro returned a design, the new function returns an identical one, and the path coefficients are identical; the maSigPro oracle tests, the golden results and the plots are unchanged. scMaSigPro no longer calls any maSigPro function. - The package reads and writes the Sparse and Dense slots with the SingleCellExperiment accessors colData(), assay(), assayNames() and their replacement forms instead of reaching into the internal slots of those objects, so it no longer depends on how SummarizedExperiment stores its data. Results are unchanged. - The show() method of ScMaSigPro objects holds the printing code instead of calling the internal .ScMaSigPro_show(), paths_from_graph() finds the end nodes it cannot reach with igraph::distances() instead of silencing the warning of igraph::shortest_paths(), and plotTrend() and plotTrendCluster() log their points without silencing warnings, so a negative pseudoCount that makes a point negative now warns. The output is otherwise unchanged. - Before it stops with "Unable to estimate bin size", sc.squeeze() prints "Could not estimate the number of bins:" and the reason, instead of "Error message:" and the reason. - R CMD check on macOS, Windows and Ubuntu, test coverage, lintr and the R-universe build with BiocCheck run again in GitHub Actions, on pull requests to main as well as on pushes to it (R CMD check also weekly). Every job, the pkgdown site included, runs only in spriyansh/scMaSigPro, not in other copies of the repository such as BioBam/scMaSigPro. .github/dependabot.yml asks Dependabot for weekly updates of the actions, in one pull request. Changes in version 0.0.5 (2026-10-13) Bug fixes - sc.squeeze() no longer scrambles the cell metadata in the Sparse slot. Binning stacked the cells path by path, but the count matrix kept its original column order, so colData(), counts(), assay() and cSparse() paired every cell with another cell's counts (on splat.sim, counts(ob@Sparse)[, "Cell2"] returned the counts of Cell1). Pseudo-bulk counts, fits and gene lists were not affected because they match cells by name. Running sc.squeeze() again repairs objects binned with 0.0.4. - The Dense slot is now a valid SingleCellExperiment. It used to report 0 genes (dim(ob@Dense) was 0 x ), failed validObject(), and ob@Dense[1:3, ] errored. pb_counts() now builds it from the bulk.counts assay and the bin metadata together; sc.squeeze(), sc.restruct() and pb_counts() check the result with validObject(). - pb_counts() names the bins after the rownames of the Dense metadata (the bin_col column is used only when there are no rownames) and stops on duplicated names. After bins were reordered by hand with cDense<- (Common Root Cells vignette), the counts kept the old, duplicated bin labels and sc.p.vector() failed with "subscript out of bounds"; such objects now run through the workflow. - When split_bins = TRUE leaves cells outside every bin (3 of the 1,977 cells of multi.lin.sce with the default bin_method), sc.squeeze() now warns and removes them from the Sparse slot. Previously they were silently dropped from the cell metadata only, which left the object invalid. They were never part of any bin, so results do not change. - The bundled scmp.ob dataset was regenerated: it passes validObject() and its results are numerically unchanged. - Printing a binned object lists the branching paths in the order they appear in the cell metadata, as before binning. Documentation - Vignettes no longer download data or images from the web. The "Common Root Cells" vignette uses the bundled multi.lin.sce dataset and includes the Monocle3 extraction steps as an unevaluated recipe; the Quick Start vignette no longer writes into data/ during the build. - Installation instructions use BiocManager::install("BioBam/scMaSigPro") instead of pinning Bioconductor 3.14. - Scripts that regenerate the bundled datasets live in inst/scripts/; the dataset help pages point to them instead of embedding the code. Dependencies - SummarizedExperiment (already installed with SingleCellExperiment) is declared in Imports. Internal - .Rbuildignore is now tracked, so fresh clones and CI build the same package as the maintainer. - GitHub Actions rebuilt on the current r-lib templates: R CMD check (plus a weekly run), test coverage via codecov, lint (advisory), pkgdown, and the R-universe build that mirrors Bioconductor's submission checks including BiocCheck. Workflows run on main and devel. - DESCRIPTION: title in title case, Description cites the paper, ORCIDs for all four authors (the previous OCRID typo meant none were recognised), LazyData removed, roxygen markdown enabled. Changes in version 0.0.4 (2024-07-30) New features - Updated plot functions to use a custom palette. - Updated plotTrendCluster function for distinct layers. - Enhanced plotIntersect() with return capabilities. - Enhanced plotTrend with curves, lines, and points. - Updated ordering by frequency in UpsetR. - Added sc.restruct() function. - Exported pb_helpers(). Bug fixes - Removed patchwork call. Documentation - General code styling. - Updated README with citation information. - Single comprehensive data documentation. - Added a new vignette. Dependencies - Removed hard dependency on RColorConesa and ComplexUpset. - Removed assertthat, stringr from namespace. - Removed maSigPro::position from namespace. Internal - Added clean_string() internal function. Changes in version 0.0.3 (2024-03-03) Bug fixes - Fixed the indexes of eSparse() and eDense(). - Removed the ComplexUpset error due to ggplot 3.5.0. Dependencies - Removed the dependence on SummarizedExperiment and ComplexUpset. - Added patchwork, ComplexUpset and UpSetR to the suggested packages. Internal - Updated test cases: functionality of eSparse() and eDense(); testing sc.squeeze() against manual pseudo-bulking. Changes in version 0.0.1 (2023-12-20) - Initial GitHub release.