a b/tests/deseq2/test_results.py
1
import unittest
2
3
import numpy as np
4
5
from inmoose.deseq2 import DESeq, makeExampleDESeqDataSet
6
from inmoose.utils import Factor
7
8
9
class Test(unittest.TestCase):
10
    def test_results(self):
11
        """test that results work as expected and throw errors"""
12
        # test contrasts
13
        dds = makeExampleDESeqDataSet(n=200, m=12, seed=42)
14
        dds.obs["condition"] = Factor(np.repeat([1, 2, 3], 4))
15
        dds.obs["group"] = Factor(np.repeat([[1, 2]], 6, axis=0).flatten())
16
        dds.obs["foo"] = np.repeat(["lo", "hi"], 6)
17
        dds.counts()[:, 0] = np.repeat([100, 200, 800], 4)
18
19
        dds.design = "~ group + condition"
20
21
        # calling results too early
22
        with self.assertRaisesRegex(
23
            ValueError,
24
            expected_regex="could not find results in obj. first run DESeq()",
25
        ):
26
            dds.results()
27
28
        dds.sizeFactors = np.ones(dds.n_obs)
29
        dds = DESeq(dds)
30
        res = dds.results()
31
        # TODO
32
        # show_res = res.show()
33
        summary = res.summary()
34
        print(summary)
35
        summary_ref = """
36
out of 200 with nonzero total read count
37
adjusted p-value < 0.1
38
LFC > 0 (up)       : 1, 0.50%
39
LFC < 0 (down)     : 0, 0.00%
40
outliers [1]       : 0, 0.00%
41
low counts [2]     : 0, 0.00%
42
(mean count < 0)
43
[1] see 'cooksCutoff' argument of results()
44
[2] see 'independentFiltering' argument of results()
45
"""
46
        self.assertEqual(summary, summary_ref)
47
48
        # various results error checking
49
        with self.assertRaisesRegex(
50
            ValueError,
51
            expected_regex="the LRT requires the user to run nbinomLRT or DESeq",
52
        ):
53
            dds.results(test="LRT")
54
        with self.assertRaisesRegex(
55
            ValueError,
56
            expected_regex="when testing altHypothesis='lessAbs', set the argument lfcThreshold to a positive value",
57
        ):
58
            dds.results(altHypothesis="lessAbs")
59
        with self.assertRaisesRegex(
60
            ValueError, expected_regex="'name' should be a string"
61
        ):
62
            dds.results(name=["Intercept", "group1"])
63
        with self.assertRaisesRegex(ValueError, expected_regex="foo is not a factor"):
64
            dds.results(contrast=["foo", "B", "A"])
65
        with self.assertRaisesRegex(
66
            ValueError,
67
            expected_regex="as 1 is the reference level, was expecting condition_4_vs_1 to be present in",
68
        ):
69
            dds.results(contrast=["condition", "4", "1"])
70
        with self.assertRaisesRegex(
71
            ValueError, expected_regex="invalid value for test: foo"
72
        ):
73
            dds.results(test="foo")
74
        with self.assertRaisesRegex(
75
            ValueError,
76
            expected_regex="numeric contrast vector should have one element for every element of",
77
        ):
78
            dds.results(contrast=False)
79
        with self.assertRaisesRegex(
80
            ValueError,
81
            expected_regex="'contrast', as a pair of lists, should have length 2",
82
        ):
83
            dds.results(contrast=["a", "b", "c", "d"])
84
        with self.assertRaisesRegex(
85
            ValueError, expected_regex="1 and 1 should be different level names"
86
        ):
87
            dds.results(contrast=["condition", "1", "1"])
88
89
        dds.results(independentFiltering=False)
90
        dds.results(contrast=["condition_2_vs_1"])
91
92
        with self.assertRaisesRegex(
93
            ValueError,
94
            expected_regex="condition_3_vs_1 and condition_3_vs_1 should be different level names",
95
        ):
96
            dds.results(
97
                contrast=["condition_2_vs_1", "condition_3_vs_1", "condition_3_vs_1"]
98
            )
99
        with self.assertRaisesRegex(
100
            ValueError,
101
            expected_regex="'contrast', as a pair of lists, should have lists of strings as elements",
102
        ):
103
            dds.results(contrast=["condition_2_vs_1", 1])
104
        with self.assertRaisesRegex(
105
            ValueError,
106
            expected_regex="all elements of the 2-element contrast should be elements of",
107
        ):
108
            dds.results(contrast=["condition_2_vs_1", "foo"])
109
        with self.assertRaisesRegex(
110
            ValueError,
111
            expected_regex="elements in the 2-element contrast should only appear in the numerator",
112
        ):
113
            dds.results(contrast=["condition_2_vs_1", "condition_2_vs_1"])
114
        with self.assertRaisesRegex(
115
            ValueError,
116
            expected_regex="all elements of the 2-element contrast should be elements of",
117
        ):
118
            dds.results(contrast=["", ""])
119
        with self.assertRaisesRegex(
120
            ValueError,
121
            expected_regex="numeric contrast vector should have one element for every element of",
122
        ):
123
            dds.results(contrast=np.repeat(0, 6))
124
        with self.assertRaisesRegex(ValueError, expected_regex="foo is not a factor"):
125
            dds.results(contrast=["foo", "lo", "hi"])
126
127
        self.assertAlmostEqual(
128
            dds.results(contrast=["condition", "1", "3"]).log2FoldChange.iloc[0],
129
            -3,
130
            delta=1e-6,
131
        )
132
        self.assertAlmostEqual(
133
            dds.results(contrast=["condition", "1", "2"]).log2FoldChange.iloc[0],
134
            -1,
135
            delta=1e-6,
136
        )
137
        self.assertAlmostEqual(
138
            dds.results(contrast=["condition", "2", "3"]).log2FoldChange.iloc[0],
139
            -2,
140
            delta=1e-6,
141
        )
142
143
        # test a number of contrast as list options
144
        self.assertAlmostEqual(
145
            dds.results(
146
                contrast=["condition_3_vs_1", "condition_2_vs_1"]
147
            ).log2FoldChange.iloc[0],
148
            2,
149
            delta=1e-6,
150
        )
151
        dds.results(
152
            contrast=["condition_3_vs_1", "condition_2_vs_1"], listValues=[0.5, -0.5]
153
        )
154
        dds.results(contrast=["condition_3_vs_1", []])
155
        dds.results(contrast=["condition_3_vs_1", []], listValues=[0.5, -0.5])
156
        dds.results(contrast=[[], "condition_2_vs_1"])
157
        dds.results(contrast=[[], "condition_2_vs_1"], listValues=[0.5, -0.5])
158
159
        # test no prior on intercept
160
        self.assertTrue(np.array_equal(dds.betaPriorVar, np.repeat(1e6, 4)))
161
162
        # test thresholding
163
        dds.results(lfcThreshold=np.log2(1.5))
164
        dds.results(lfcThreshold=1, altHypothesis="lessAbs")
165
        dds.results(lfcThreshold=1, altHypothesis="greater")
166
        dds.results(lfcThreshold=1, altHypothesis="less")
167
168
        dds3 = DESeq(dds, betaPrior=True)
169
        with self.assertRaisesRegex(
170
            ValueError,
171
            expected_regex="testing altHypothesis='lessAbs' requires setting the DESeq\(\) argument betaPrior=False",
172
        ):
173
            dds3.results(lfcThreshold=1, altHypothesis="lessAbs")
174
175
    def test_results_zero_intercept(self):
176
        """test results on designs with zero intercept"""
177
        dds = makeExampleDESeqDataSet(n=100, m=12, seed=42)
178
        dds.obs["condition"] = Factor(np.repeat([1, 2, 3], 4))
179
        dds.obs["group"] = Factor(np.repeat([[1, 2]], 6, axis=0).flatten())
180
181
        dds.X[:, 0] = np.repeat([100, 200, 400], 4)
182
183
        dds.design = "~ 0 + condition"
184
        dds = DESeq(dds, betaPrior=False)
185
186
        self.assertAlmostEqual(dds.results().log2FoldChange.iloc[0], 2, delta=0.1)
187
        self.assertAlmostEqual(
188
            dds.results(contrast=["condition", "2", "1"]).log2FoldChange.iloc[0],
189
            1.25,
190
            delta=0.1,
191
        )
192
        self.assertAlmostEqual(
193
            dds.results(contrast=["condition", "3", "2"]).log2FoldChange.iloc[0],
194
            0.68,
195
            delta=0.1,
196
        )
197
        self.assertAlmostEqual(
198
            dds.results(contrast=["condition", "1", "3"]).log2FoldChange.iloc[0],
199
            -2,
200
            delta=0.1,
201
        )
202
        self.assertAlmostEqual(
203
            dds.results(contrast=["condition", "1", "2"]).log2FoldChange.iloc[0],
204
            -1.25,
205
            delta=0.1,
206
        )
207
        self.assertAlmostEqual(
208
            dds.results(contrast=["condition", "2", "3"]).log2FoldChange.iloc[0],
209
            -0.68,
210
            delta=0.1,
211
        )
212
        with self.assertRaisesRegex(
213
            ValueError,
214
            expected_regex="condition\[4\] and condition\[1\] are expected to be in",
215
        ):
216
            dds.results(contrast=["condition", "4", "1"])
217
218
        dds.design = "~ 0 + group + condition"
219
        dds = DESeq(dds, betaPrior=False)
220
221
        self.assertAlmostEqual(dds.results().log2FoldChange.iloc[0], 2, delta=0.1)
222
        self.assertAlmostEqual(
223
            dds.results(contrast=["condition", "3", "1"]).log2FoldChange.iloc[0],
224
            2,
225
            delta=0.1,
226
        )
227
        self.assertAlmostEqual(
228
            dds.results(contrast=["condition", "2", "1"]).log2FoldChange.iloc[0],
229
            1.25,
230
            delta=0.1,
231
        )
232
        self.assertAlmostEqual(
233
            dds.results(contrast=["condition", "3", "2"]).log2FoldChange.iloc[0],
234
            0.68,
235
            delta=0.1,
236
        )
237
        self.assertAlmostEqual(
238
            dds.results(contrast=["condition", "1", "3"]).log2FoldChange.iloc[0],
239
            -2,
240
            delta=0.1,
241
        )
242
        self.assertAlmostEqual(
243
            dds.results(contrast=["condition", "1", "2"]).log2FoldChange.iloc[0],
244
            -1.25,
245
            delta=0.1,
246
        )
247
        self.assertAlmostEqual(
248
            dds.results(contrast=["condition", "2", "3"]).log2FoldChange.iloc[0],
249
            -0.68,
250
            delta=0.1,
251
        )
252
253
    @unittest.skip("LRT is not implemented yet")
254
    def test_results_likelihood_ratio_test(self):
255
        """test results with likelihood ratio test"""
256
        dds = makeExampleDESeqDataSet(n=100)
257
        dds.obs["group"] = Factor([1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2])
258
        dds.design = "~ group + condition"
259
        dds = DESeq(dds, test="LRT", reduced="~group")
260
261
        self.assertFalse(
262
            np.all(
263
                dds.results(name="condition_B_vs_A").stat
264
                == dds.results(name="condition_B_vs_A", test="Wald").stat
265
            )
266
        )
267
268
        # LFC are already MLE
269
        with self.assertRaisesRegex(
270
            ValueError,
271
            expected_regex="addMLE=TRUE is only for when a beta prior was used",
272
        ):
273
            dds.results(addMLE=True)
274
        with self.assertRaisesRegex(
275
            ValueError,
276
            expected_regex="tests of log fold change above or below a theshold must be Wald tests",
277
        ):
278
            dds.results(lfcThreshold=1, test="LRT")
279
280
        self.assertTrue(
281
            np.all(
282
                dds.results(test="LRT", contrast=["group", "1", "2"]).log2FoldChange
283
                == -dds.results(test="LRT", contrast=["group", "2", "1"]).log2FoldChange
284
            )
285
        )
286
287
    def test_results_basics(self):
288
        """test that results basics regarding format, saveCols, tidy, MLE, remove are working"""
289
        dds = makeExampleDESeqDataSet(n=100)
290
        dds.var["score"] = np.arange(1, 101)
291
        dds = DESeq(dds)
292
293
        # try saving metadata columns
294
        res = dds.results(saveCols="score")  # string
295
296
        # check tidy-ness (unimplemented)
297
        with self.assertRaises(NotImplementedError):
298
            res = dds.results(tidy=True)
299
            self.assertTrue(res.columns[0] == "rows")
300
301
        # test MLE and 'name'
302
        dds2 = DESeq(dds, betaPrior=True)
303
        dds2.results(addMLE=True)
304
        with self.assertRaises(ValueError):
305
            dds2.results(name="condition_B_vs_A", addMLE=True)
306
307
        # test remove results
308
        dds = dds.removeResults()
309
        self.assertTrue(dds.var.description.filter("results").empty)
310
311
    @unittest.skip("not sure what to test")
312
    def test_results_custom_filters(self):
313
        """test that custom filters can be provided to results()"""
314
        dds = makeExampleDESeqDataSet(n=200, m=4, betaSD=np.repeat([0, 2], [150, 50]))
315
        dds = DESeq(dds)
316
        _res = dds.results()
317
        _method = "BH"
318
        _alpha = 0.1
319
320
        raise NotImplementedError()