Wald test applies lfc_null and alt_hypothesis to each coefficient instead of to the contrast
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 52/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Stack tecnológico
- numpy, python
- Área
- bioinformatics, data
Línea de trabajo
Start by reading wald_test() in glm.py and trace how the contrast, lfc_null, and alternative hypotheses are used to compute the statistic. The report includes a reproducible synthetic-data example comparing ~condition with ~0 + condition, plus a proposed parametrized test covering all five alternatives. Done means equivalent designs produce matching statistics and p-values for the same contrast.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Report
When lfc_null != 0 or alt_hypothesis is set, wald_test() in glm.py subtracts lfc_null from (and clips) every coefficient and only then applies the contrast vector:
stat = contrast @ np.fmax((lfc - lfc_null) / wald_se, 0) # "greater"
wald_statistic = float(contrast @ (lfc - lfc_null) / wald_se) # default
That is only right when the contrast has a single non-zero entry. As soon as it has two (a comparison between two non-reference levels, a design without intercept, or a user-supplied numeric contrast) the result tests a different hypothesis: for a contrast whose entries sum to zero, lfc_null cancels out entirely (contrast @ (lfc - lfc_null) = contrast @ lfc), and for the thresholded alternatives each coefficient is clipped separately, so the statistic can be large even when the contrasted LFC is below the threshold.
The same model written in two equivalent ways then gives different p-values. With the bundled synthetic data, contrast B vs A, lfc_null=0.5:
from pydeseq2.dds import DeseqDataSet
from pydeseq2.ds import DeseqStats
from pydeseq2.datasets import load_example_data
counts = load_example_data(modality="raw_counts", dataset="synthetic")
metadata = load_example_data(modality="metadata", dataset="synthetic")
for design in ["~condition", "~0 + condition"]:
dds = DeseqDataSet(counts=counts, metadata=metadata, design=design, quiet=True)
dds.deseq2()
ds = DeseqStats(dds, contrast=["condition", "B", "A"], lfc_null=0.5, quiet=True)
ds.summary()
r = ds.results_df.loc["gene1"]
print(design, ds.contrast_vector, r[["log2FoldChange", "lfcSE", "stat", "pvalue"]].round(4).tolist())
~condition [0. 1.] [0.6328, 0.2891, 0.4594, 0.646]
~0 + condition [-1 1] [0.6328, 0.2891, 2.1889, 0.0286]
Same estimate, same standard error, but the second statistic is 0.6328 / 0.2891 (null value dropped) instead of (0.6328 - 0.5) / 0.2891 = 0.459. Over the 10 genes, stat differs by up to 4.21 and pvalue by up to 0.89 between the two designs. With alt_hypothesis="greaterAbs" the ~0 + condition fit reports 3 genes at padj < 0.05 against 0 for ~condition. In DESeq2 (R) results(dds, contrast=..., lfcThreshold=0.5) gives identical numbers for both designs, because the threshold is applied to the contrast estimate.
The fix is to compute the scalar contrast @ lfc first and apply lfc_null and the alternative hypothesis to it, keeping the existing contrast standard error (sqrt(Hc' M Hc)). I have a branch with that change and a parametrized test (~condition vs ~0 + condition must agree for all five alternatives) that fails on main and passes with the change; I can open a PR if that is welcome. (A similar PR, #468, was closed in September without discussion; I am opening this issue first to check how you would like it handled.)
Versions
pydeseq2 0.5.4 (main 2ca3b74), pandas 3.0.6, numpy 2.5.4, scipy (current), Python 3.13.14, macOS 26.
- Lenguaje dominante
- Python
- Estrellas
- 768
- Forks
- 93
- Métricas de merge de PR
- Sin PR fusionados en 30 d
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de scverse/PyDESeq2
-
Dificultad 3/5 1-2 días Aptitud para principiantes 48/100
-
Dificultad 3/5 1-2 días Aptitud para principiantes 35/100
-
Dificultad 3/5 Medio día Aptitud para principiantes 18/100
-
Dificultad 3/5 1-2 días Aptitud para principiantes 45/100
-
bug
Dificultad 4/5 3-5 días Aptitud para principiantes 35/100
Todos los issues de scverse/PyDESeq2
Issues similares
-
Admin member of a research group cannot see the HX2 allocation until they've been added to itAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
Los mantenedores suelen responder en 2 días
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
Los mantenedores suelen responder en 2 días
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
developmentseed/titiler-cmr#278 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
Los mantenedores suelen responder en 2 días