ISSUE
f0135ec
Description
The biological ceiling constraint introduced in the latest version of ApplyPropagation.R inadvertently reverses downregulation (knockdown) signals during network propagation. Rather than showing reduced expression post-perturbation, genes that are expected to be downregulated (e.g., APOE, CTSD) instead appear upregulated, which is the opposite of the intended effect.
Root Cause
The ceiling is applied unconditionally inside the iteration loop:
See the ceiling constraint in ApplyPropagation.R#L86-L87
exp_update <- methods::as(exp_update, "CsparseMatrix")
exp_update@x <- pmin(exp_update@x, max_obs[exp_update@i + 1])
For downregulated genes, delta starts negative L57(delta <- exp_per - exp), correctly reflecting reduced expression post-perturbation. Each iteration propagates this signal through the network L75(delta <- network %*% delta) and adds it to the perturbed expression L81(exp_update <- exp_per + delta).
The problem arises from the unconditional application of the ceiling constraint regardless of perturbation direction. First, L82 (exp_update[exp_update < 0] <- 0) correctly floors expression at zero. Then, L86 converts exp_update to a sparse matrix, dropping all zero entries from @x. At this point, the ceiling on L87 (exp_update@x <- pmin(exp_update@x, max_obs[exp_update@i + 1])) operates only on remaining non-zero values, artificially pulling them down toward max_obs, distorting what should be a naturally decaying signal.
By the next iteration, L90 (delta <- exp_update - exp_per) computes a new delta from this distorted exp_update, corrupting the negative signal that should carry the downregulation forward through subsequent iterations.
Expected Behavior
The ceiling constraint should only apply to knock-in / upregulation (perturb_sign > 0). Knockdown / downregulation should propagate freely downward, constrained only by the existing floor at zero.
Proposed Fix
Wrap the ceiling constraint in a perturb_sign check:
if (perturb_sign > 0) {
exp_update <- methods::as(exp_update, "CsparseMatrix")
exp_update@x <- pmin(exp_update@x, max_obs[exp_update@i + 1])
}
This preserves the biological intent of the ceiling (preventing upregulation beyond observed baseline max) while allowing downregulation to work correctly.
Steps to Reproduce (I tested this on the microglia data)
- Run the updated f0135ec
ApplyPropagation with a knockdown perturbation (perturb_dir < 0)
- Plot Per Perturbation vs Post Perturbation expression distributions
- Observe that Post Perturbation distributions are not meaningfully smaller and compare with previous version
Evidence
APOE & CTSD (directly perturbed genes - downregulation ): Post-perturbation distributions appear identical to pre-perturbation, showing no downregulation signal across all cell types (Homeostatic, Immune-Primed_3, Stress-Response_1, ABeta-Associated, DAM).
JAZF1 (not in the perturbation module): Despite not being a direct target, JAZF1 shows unexpected expression changes post-perturbation — consistent with the ceiling constraint incorrectly interfering with propagated signal in downstream genes as well.
📎 See attached violin plot:
This JAZF1 case is particularly telling because it demonstrates the bug affects not just directly perturbed genes, but also secondary propagation targets.
Final Notes
- Old version correctly shows downregulation; new version does not
ISSUE
f0135ec
Description
The biological ceiling constraint introduced in the latest version of
ApplyPropagation.Rinadvertently reverses downregulation (knockdown) signals during network propagation. Rather than showing reduced expression post-perturbation, genes that are expected to be downregulated (e.g., APOE, CTSD) instead appear upregulated, which is the opposite of the intended effect.Root Cause
The ceiling is applied unconditionally inside the iteration loop:
See the ceiling constraint in
ApplyPropagation.R#L86-L87For downregulated genes,
deltastarts negative L57(delta <- exp_per - exp), correctly reflecting reduced expression post-perturbation. Each iteration propagates this signal through the network L75(delta <- network %*% delta) and adds it to the perturbed expression L81(exp_update <- exp_per + delta).The problem arises from the unconditional application of the ceiling constraint regardless of perturbation direction. First, L82 (
exp_update[exp_update < 0] <- 0) correctly floors expression at zero. Then, L86 convertsexp_updateto a sparse matrix, dropping all zero entries from@x. At this point, the ceiling on L87 (exp_update@x <- pmin(exp_update@x, max_obs[exp_update@i + 1])) operates only on remaining non-zero values, artificially pulling them down towardmax_obs, distorting what should be a naturally decaying signal.By the next iteration, L90 (
delta <- exp_update - exp_per) computes a new delta from this distortedexp_update, corrupting the negative signal that should carry the downregulation forward through subsequent iterations.Expected Behavior
The ceiling constraint should only apply to knock-in / upregulation (
perturb_sign > 0). Knockdown / downregulation should propagate freely downward, constrained only by the existing floor at zero.Proposed Fix
Wrap the ceiling constraint in a
perturb_signcheck:This preserves the biological intent of the ceiling (preventing upregulation beyond observed baseline max) while allowing downregulation to work correctly.
Steps to Reproduce (I tested this on the microglia data)
ApplyPropagationwith a knockdown perturbation (perturb_dir < 0)Evidence
APOE & CTSD (directly perturbed genes - downregulation ): Post-perturbation distributions appear identical to pre-perturbation, showing no downregulation signal across all cell types (Homeostatic, Immune-Primed_3, Stress-Response_1, ABeta-Associated, DAM).
JAZF1 (not in the perturbation module): Despite not being a direct target, JAZF1 shows unexpected expression changes post-perturbation — consistent with the ceiling constraint incorrectly interfering with propagated signal in downstream genes as well.
This JAZF1 case is particularly telling because it demonstrates the bug affects not just directly perturbed genes, but also secondary propagation targets.
Final Notes