Skip to content

Normalization factors and DESeqDataSetFromTximport equivalent#412

Draft
maltekuehl wants to merge 4 commits into
scverse:mainfrom
complextissue:main
Draft

Normalization factors and DESeqDataSetFromTximport equivalent#412
maltekuehl wants to merge 4 commits into
scverse:mainfrom
complextissue:main

Conversation

@maltekuehl

@maltekuehl maltekuehl commented Sep 13, 2025

Copy link
Copy Markdown
Collaborator

Fixes #305 and closes #359. Adds support for normalization factors based on length provided by pytximport. Very much still a draft.

Open issues:

  • Is the example dataset okay? Others seem to be synthetic and much smaller. Do you have some simple or synthetic source data that could be used to create an AnnData object with pytximport? Once clear, we should probably also add tests for correctness, comparing against a reference to prevent accidental future drift.
  • I have yet to figure out how size factors should be implemented in the case that we are also calculating normalization factors. Should they be equivalent to size factors in the standard case or some transform of the norm matrix adjusted counts? Similar for logmeans, as I do not fully understand where else this data is used throughout the code. Any help would be appreciated.
  • The statistical testing structure is quite a bit different in PyDESeq2 compared to DESeq2 and I may not have grasped all subtleties, would be thankful for help from maintainers to ensure that everything was adjusted.
  • The initial scaffold of this PR was LLM-generated, and while I provided ample context (including the issues and relevant code from DESeq2) and clear instructions and have checked and already adjusted the output quite a bit, it would be best to examine these changes critically.

Aside: Tests pass locally but fail due to an older AnnData version on Python 3.10 here. Would you be open for this PR to also include an update of the pre-commit, GitHub Actions, full move to uv/hatch/ruff like other scverse ecosystem packages and targeting Python 3.11 - 3.13?

CC @BorisMuzellec

@BorisMuzellec

Copy link
Copy Markdown
Collaborator

Hi @maltekuehl, sorry I haven't had the time to review your PR yet.

To answer your last remark: I'm all for switching to uv for package management. I don't see much of an issue in dropping support for python 3.10 starting from v0.5.3 as many packages (e.g., numpy) have also stopped supporting it in their latest releases.

Please go ahead if you wish to handle those changes! I'd just suggest you make them in a separate PR.

I'll try to have a look at this PR ASAP

[pre-commit.ci] pre-commit autoupdate (scverse#415)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for normalization factors from pytximport, implementing functionality equivalent to DESeq2's DESeqDataSetFromTximport. It enables gene-length correction for transcript-level quantification data (e.g., from Salmon/Kallisto/RSEM).

Changes:

  • Adds from_pytximport parameter to DeseqDataSet with validation, normalization factor computation in fit_size_factors, and propagation through IRLS/Wald test/LFC shrinkage
  • Adds estimate_norm_factors function in preprocessing and updates irls_solver to accept per-gene normalization factors
  • Adds tests, example notebook, and documentation for the pytximport integration

Reviewed changes

Copilot reviewed 12 out of 17 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
pydeseq2/dds.py Adds from_pytximport flag, validation, normalization factor computation in fit_size_factors, and passes norm factors to IRLS
pydeseq2/ds.py Updates Wald test and LFC shrinkage to use normalization factors when available
pydeseq2/preprocessing.py Adds estimate_norm_factors function implementing DESeq2's estimateNormFactors
pydeseq2/utils.py Updates irls_solver to accept and use per-gene normalization factors
pydeseq2/default_inference.py Passes per-gene normalization factors through to irls_solver
pydeseq2/inference.py Adds normalization_factors parameter to abstract irls method
tests/test_pytximport.py Tests for detection, validation, normalization computation, and full pipeline
examples/plot_pytximport_example.py Example notebook demonstrating pytximport integration
docs/source/index.rst Documents pytximport support
docs/source/refs.bib Adds pytximport citation
pyproject.toml Updates ruff config to newer tool.ruff.lint format
docs/source/.DS_Store Accidentally committed macOS metadata file
.gitignore Adds sphinx auto_examples and ruff cache

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pydeseq2/dds.py
Comment on lines +747 to +754
# Store sample-wise geometric mean of norm factors as size_factors
# This maintains compatibility with existing code while incorporating
# the gene-length correction
# with np.errstate(divide="ignore"):
# log_norm_factors = np.log(norm_factors)
# log_geom_mean_per_sample = np.mean(log_norm_factors, axis=1)
# self.obs["size_factors"] = np.exp(log_geom_mean_per_sample)

# integer counts and that pytximport was used with counts_from_abundance=None
# (raw counts) to generate the AnnData object.

adata = ad.read_h5ad("../tests/data/pytximport/test_pytximport.h5ad")
# When pytximport data is used, PyDESeq2 computes normalization factors
# that account for both library size and gene length differences.

dds_explicit.fit_size_factors()
Comment thread pydeseq2/dds.py
self.from_pytximport = from_pytximport

if self.from_pytximport:
print("Detected pytximport data with length offsets.")
Comment thread pydeseq2/ds.py
offset = np.log(self.dds.obs["size_factors"]).values

if "normalization_factors" in self.dds.obsm:
offset = np.log(self.dds.obsm["normalization_factors"])
@gitbenlewis

Copy link
Copy Markdown

Hi @maltekuehl, thank you for working on this. I encountered the same need and only found this draft after implementing a tested version in my fork.

I have described the implementation in issue #305, and the code is available here:

gitbenlewis@3e9eb78

My implementation uses an optional transcript_lengths argument and stores the inputs and fitted normalization factors in AnnData layers. It includes targeted coverage for the normalization formula, ratio and poscounts, input validation, sparse AnnData input, dispersion fitting, Wald testing, LFC shrinkage, Cook’s outlier replacement and refitting, and VST.

I do not want to duplicate or compete with your work. Would you prefer that I contribute relevant changes and tests to this PR, or open a separate draft PR so the maintainers can compare the approaches?

I would be happy to coordinate and adapt the API or implementation based on your and the maintainers’ feedback.

@maltekuehl

Copy link
Copy Markdown
Collaborator Author

Hi @gitbenlewis, great to see that there is interest in this!

I unfortunately had to abandon this PR a while ago due to lack of time, but I think it should still be relevant and the approach had been discussed with @BorisMuzellec beforehand.

I am open to somebody taking over this draft PR and driving the implementation of the change. If you have the availability and would be willing to contribute, from my side that's great, though the project maintainers would also need to comment (cc, @Zethson).

I would however encourage you to explore the approach I was taking beforehand and to create a comparison with your approach to discuss this together between everybody before you go on to create a full PR.

One important aspect that this PR wanted to cover is the connection with https://github.com/complextissue/pytximport which would be the natural fit in any Python workflow, so whichever way this is implemented, it would be great if that compatibility could be kept and the examples and docs could point there, too (while of course still crediting the intellectual work done by the original tximport team!).

@Zethson

Zethson commented Jul 14, 2026

Copy link
Copy Markdown
Member

If you have the availability and would be willing to contribute, from my side that's great, though the project maintainers would also need to comment (cc, @Zethson).

I'm more than happy if someone steps in and finishes some PRs. Thanks @gitbenlewis !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Plans to add DESeqDataSetFromTximport? Add support for sample-/gene-dependent normalization factors (e.g., length offsets from pytximport)

5 participants