
R package for CEP-IP (cell explanatory power with inflection point) and its extensions SKCS (Spearman-Kendall cell-level score) and CMRF (copula monotone recovery filter) for co-expression analysis in single-cell data.
| Table of Contents | ||
|---|---|---|
| No. | Section | Descriptions |
| About the package | ||
| 1 | Overview | Aims of the package and its functions |
| Getting started | ||
| 2 | Install | Installation from GitHub |
| 3 | Quick Start | A first run on simulated data |
| 4 | Worked Example | Each step of the method on the SEA-AD example data |
| Outputs and reproduction | ||
| 5 | Output of cepip_save() |
Tables and plots written by the package |
| 6 | Reproducing the SEA-AD Analysis | 698 published values checked |
| 7 | Reproducing the PCa Analysis | 133 published values of the CEP-IP paper checked |
| Using this work | ||
| 8 | License and Data Terms | MIT License and the terms of the example data |
| 9 | Citation | Citations of the methods |
cepip analyzes the co-expression of a gene of interest (GOI) with a set of partner genes in single-cell data. The partner genes are genes whose expression rises with the GOI across cells. Their mean in each cell is called the composite. In most datasets, only some of the cells follow this relationship closely. cepip identifies these cells, finds the GOI level at which the relationship changes, and relates the result to donor-level outcomes.
The package has three parts:
The downstream functions compute the share of each donor's cells in each group, the differential expression between groups, and the association of a donor-level measure with clinical outcomes.
The functions, in the order of the analysis:
| Step | Function | Output |
|---|---|---|
| Partner screen | cepip_screen() |
GOIs ranked by their number of dual-filter partner genes (Spearman rs and Kendall τb) |
| Composite | cepip_composite() |
mean expression of the partner genes per cell |
| CEP-IP GAM | cepip_fit() |
GAM-REML-PRSS fit (k by the lowest PRSS), deviance explained (DE) |
| EP and TREP | cepip_ep() |
explanatory power per cell and the TREP cells (top round(n x DE) by EP) |
| Inflection point | cepip_ip() |
IP from the residual GAM R(x), 95% CI, IPy, IPRS |
| SKCS | cepip_skcs() |
per-cell Spearman and Kendall contributions and the SKCS-core cells |
| CMRF band | cepip_band() |
spine, δbase and δadapt band in copula space (Stage 1) |
| CMRF gate | cepip_cmrf() |
RSk-fCV gate (Stage 2) and the Module (OriRef plus Recovered cells) |
| Validation1 | cepip_validate() |
GAM refit on the Module cells and label agreement |
| Groups per donor | cepip_donor() |
share of each donor's cells in the Module Post-IP and other groups |
| DEGs | cepip_deg() |
donor-paired differential expression between two groups |
| Donor outcomes | cepip_associate() |
OLS of donor outcomes on a donor measure with covariates |
| All steps | cepip() |
runs CEP-IP, and SKCS and CMRF when asked |
| Output | cepip_save() |
numbered tables (one workbook) and plots in one folder |
Each step has a plot function: plot_screen(), plot_prss(), plot_fit(), plot_residual(), plot_pitcher(), plot_copula(), plot_band(), plot_rskfcv(), plot_module(), plot_validate(), plot_donor(), plot_deg(), and plot_associate().
The full analysis of the SEA-AD preprint, with all scripts, the container, and the console logs, is in the repository CEP-IP_with_CMRF_in_SEA-AD.
# install.packages("remotes")
# remotes::install_github("kahkengwong/cepip")
install.packages("cepip", repos = c('https://kahkengwong.r-universe.dev', 'https://cloud.r-project.org'))
The class tests of CMRF use the suggested packages clinfun, Hmisc, and energy. The screen uses pcaPP for a fast Kendall tau-b when it is installed.
library(cepip)
d <- cepip_toy # 1,200 simulated cells, 12 donors
res <- cepip(d$x, d$y, donor = d$donor, id = d$id, cmrf = TRUE)
res
plot_fit(res)
plot_module(res$cmrf)
cepip_save(list(toy = res), file.path(tempdir(), "toy_output"))
skcs = TRUE adds SKCS. cmrf = TRUE adds SKCS, CMRF, and Validation1. CMRF always uses the CEP-IP inflection point.donor, every GAM includes a donor random intercept and weights each cell by 1 / (cells of its donor).workers runs the GAM k searches and the RSk-fCV gate in parallel.[!NOTE]
cepip(x, y)with the default settings is the published CEP-IP, except for one setting. Cells are labelled Pre-IP or Post-IP on the response axis (y < IPy).ip_axis = "x"gives the published split on the GOI axis (x < IPx).
[!IMPORTANT] The package does not transform the data. The published CEP-IP analyses used log2(value + 1) of Seurat log-normalized data, and the SEA-AD analysis used ln(CP10K + 1). The default thresholds (screen, DEGs, outcomes, and covariates) are those of the published analyses. They should be adjusted to each dataset and question.
This section runs cepip on the SEA-AD example data and explains the method two panels at a time. The data are the 13,331 PVM_3 cells of the neocortical regions (NeoC). The GOI is MT-CO3, and the composite is the mean of its eight mitochondrial partner genes. The run uses the settings of the paper and gives the published values.
pvm3 <- cepip_example("pvm3")
d <- pvm3[pvm3$region_set == "NeoC", ]
mt8 <- c("MT-ATP6", "MT-CO2", "MT-ND3", "MT-ND4", "MT-CYB", "MT-ND1", "MT-ND2", "MT-CO1")
y <- cepip_composite(d, mt8)
res <- cepip(d[["MT-CO3"]], y, donor = d$donor, id = d$barcode, cmrf = TRUE, ip_k = 10,
y_spine = d$composite_part8, workers = 4) # y_spine: composite of the paper's data export
The code under each figure is condensed from the package source, and the file is named in its first line. The figures are drawn by assets/readme_figure.R. The six panels are also in one figure: assets/cepip_example_figure.png.

Aims. Panel A shows the cells that follow the global monotonic relationship between the GOI and its partner genes. Panel B finds the GOI level at which the best-explained cells move from below the population curve to above it. This level is the inflection point (IP).
Methods.
$$S_i = \frac{4,(R_{x,i} - \bar{R})(R_{y,i} - \bar{R})}{(n-1)^2} \qquad\qquad K_i = \frac{C_i - D_i}{n-1}$$
# R/skcs.R (condensed)
Si_norm <- 4 * (rx - mean(rx)) * (ry - mean(ry)) / (n - 1)^2 # S_i above; rx, ry: ranks
Ki <- cd / sqrt((n - 1 - tx) * (n - 1 - ty)) # K_i; tx, ty: ties of cell i (tau-b form)
is_core <- (Si_norm > 0.05) & (Ki > 0.5)
[!NOTE] Si in this README is the normalized score
Si_normof the package. With ties, Ki uses the tau-b denominator, as in the code above.
# R/ip.R (condensed)
rm <- mgcv::gam(residual ~ s(GOI, bs = "tp", k = 20) + s(donor, bs = "re"),
data = rd, weights = w, method = "REML") # rd: residuals of the top-EP cells
i <- which(r_hat[-g] < 0 & r_hat[-1] > 0)[1] # r_hat: R(x) on a grid of g points
ip <- x_seq[i] - r_hat[i] * (x_seq[i + 1] - x_seq[i]) / (r_hat[i + 1] - r_hat[i])
Results.
ip_axis = "y").
Aims. The fixed Si and Ki gates leave out cells that still follow the relationship. Panel C builds a band around the curve of the SKCS-core cells and finds the other cells that fall inside it. Panel D tests these candidate cells and keeps those that do not make the monotonic curve worse.
Methods.
$$\delta_{adapt}(u) = \delta_{base}\left(1 + \kappa, e^{-(u - IP_u)^2 / (2,bw^2)}\right), \qquad \kappa = 0.5,\ bw = 0.1$$
# R/band.R (condensed)
u <- rank(x) / (n + 1); v <- rank(y) / (n + 1) # copula space
delta_base <- weighted_quantile(abs(v[core] - g_u[core]), w_core, 0.95)
delta_adapt <- delta_base * (1 + kappa * exp(-(u - IP_u)^2 / (2 * bw^2)))
pass1 <- abs(v - g_u) <= delta_adapt # candidates: !core & pass1
# R/cmrf.R (condensed): one iteration of the gate
p <- wilcox.test(rmse_B, rmse_A, paired = TRUE, alternative = "greater")$p.value
eff <- median(rmse_B - rmse_A) / median(rmse_A) # dRMSE
bv <- IQR(rmse_A) / median(rmse_A) # background variability
if (p < 0.05 && eff > bv) active[worst] <- FALSE # remove the candidate farthest from the curve
[!TIP]
cepip_band()is fast. It can be run with several values ofkappaandbwand checked withplot_band()before the slower gate ofcepip_cmrf().plot_rskfcv()shows the folds and the test RMSE of curves A and B.
Results.

Aims. Panel E shows the final Module. Panel F places the cells on the two cell-level scores, Si and Ki, and shows the fixed gate that the Recovered cells failed.
Methods.
# R/cmrf.R (condensed)
cls <- ifelse(core & pass, "Original + Refined", ifelse(core, "Original only",
ifelse(pass, "Recovered", "Excluded")))
in_module <- cls %in% c("Original + Refined", "Recovered")
u_SK <- rank(Si_norm) / (n + 1); v_SK <- rank(Ki) / (n + 1) # pitcher plot coordinates
Results.
cepip_save()
One folder per arm, with no subfolders. Files are numbered in pipeline order, and the region set (or other subset) is part of each name.
| File | Content |
|---|---|
00_settings.txt, 00_log.txt
|
settings, versions, summaries, and the console log |
<arm>_cepip_tables.xlsx |
one sheet per table, from 01_screen to 14_associate
|
<arm>_cepip_cells.xlsx |
the cell-level tables, when they are too large for the first workbook |
02_prss to 14_associate (.png and .pdf) |
the plots |
cepip_example() downloads the example data of the paper from Hugging Face (kahkengwong/cepip_example):
cepip_example_clear() deletes them.reproduce_seaad.R runs three arms:
inst/reference/seaad_reference.csv.Rscript "$(Rscript -e 'cat(system.file("scripts", "reproduce_seaad.R", package = "cepip"))')" cepip_seaad 4
| Value | PVM_3 NeoC | PVM_3 NonNeoC | HKG8 NeoC | HKG8 NonNeoC |
|---|---|---|---|---|
| Cells | 13,331 | 11,132 | 1,432 | 1,079 |
| IPx | 2.4953 | 2.6572 | 1.5552 | 1.8130 |
| IPRS | 0.791 | 0.819 | 0.639 | 0.571 |
| SKCS-core cells | 8,407 | 6,941 | 250 | 199 |
| OriRef / Recovered | 8,103 / 1,351 | 6,646 / 1,089 | 239 / 287 | 191 / 240 |
| Removed by the gate | 643 | 619 | 65 | 22 |
reproduction_check.csv and reproduction_check_log.txt in the output folder.[!NOTE] The DEG step runs only when the DEG matrices are available. When a data folder is given, it must hold
pvm3_deg_NeoC.rdsandpvm3_deg_NonNeoC.rds. Without them, the 56 DEG values are marked NOT RUN inreproduction_check.csv.
inst/scripts/reproduce_cmpb_pca.R reruns the prostate cancer (PCa) analysis of the published CEP-IP paper.ip_axis = "x").GSE185344_Seurat_processed.RData (9.5 GB) from the Hugging Face dataset kahkengwong/CEP-IP_Framework and the SeuratObject package.Supplementary_Table_10_Deviance_Components__EP.xlsx) is in the same Hugging Face dataset as the .RData, and in the paper's Zenodo record.Rscript "$(Rscript -e 'cat(system.file("scripts", "reproduce_cmpb_pca.R", package = "cepip"))')" cepip_cmpb_pca 4 <folder with the .RData> <Supplementary Table 10 .xlsx>
| Sample | Cells | k | DE | IPx | IPRS |
|---|---|---|---|---|---|
| HYW_4701 | 985 | 6 | 0.3155 | 3.7963 | 0.696 |
| HYW_4847 | 820 | 3 | 0.2865 | 2.1185 | 0.670 |
| HYW_4880 | 337 | 6 | 0.5887 | 3.2237 | 0.769 |
| HYW_4881 | 1,232 | 10 | 0.8346 | 3.2233 | 0.768 |
| HYW_5386 | 156 | 4 | 0.3095 | 2.3856 | 0.631 |
| HYW_5742 | 1,468 | 4 | 0.5106 | 3.3212 | 0.719 |
| HYW_5755 | 857 | 3 | 0.5276 | 3.4270 | 0.691 |
inst/reference/cmpb_pca_reference.csv):
cmpb_pca_check.xlsx, and the summary to reproduction_check_log.txt, in the output folder.[!TIP] The first run saves the GAM data of the seven samples as
pca_trpm4_ribo_cells.csvin the output folder. Later runs with the same output folder read this file, so the 9.5 GB .RData is needed only once.
The package code is licensed under the MIT License.
The example data are derived from the Seattle Alzheimer's Disease Brain Cell Atlas (SEA-AD) of the Allen Institute for Brain Science. Their use is subject to the Allen Institute Terms of Use (noncommercial research use) and Citation Policy. Commercial use needs the written permission of the Allen Institute.
The example data and the example figures in this README are derived from the SEA-AD data (SEA-AD portal), described in:
If you use cepip, please cite:
K.K. Wong. CEP-IP: An explainable framework for cell subpopulation identification in single-cell transcriptomics. Computer Methods and Programs in Biomedicine, 282 (2026) 109372. https://doi.org/10.1016/j.cmpb.2026.109372
If you use SKCS or CMRF, please also cite:
K.K. Wong. CEP-IP with CMRF in SEA-AD Microglia: A Mitochondrial Co-expression Module Linked to Cognition. bioRxiv (2026). https://www.biorxiv.org/content/10.64898/2026.10.22.761284v1
@article{Wong2026CEPIP,
author = {Wong, Kah Keng},
title = {{CEP-IP}: An explainable framework for cell subpopulation identification in single-cell transcriptomics},
journal = {Computer Methods and Programs in Biomedicine},
volume = {282},
pages = {109372},
year = {2026},
doi = {10.1016/j.cmpb.2026.109372}
}
@article{Wong2026SEAAD,
author = {Wong, Kah Keng},
title = {{CEP-IP} with {CMRF} in {SEA-AD} Microglia: A Mitochondrial Co-expression Module Linked to Cognition},
journal = {bioRxiv},
year = {2026},
doi = {10.64898/2026.10.22.761284},
url = {https://www.biorxiv.org/content/10.64898/2026.10.22.761284v1}
}