We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Word Enrichment: sort by p-value
<!--
This is an issue template. Please fill in the relevant details in the
sections below.
-->

##### Text version
<!-- From menu _Options→Add-ons→Orange3-Text_ or code `orangecontrib.text.version.full_version` -->
0.8.0

##### Orange version
<!-- From menu _Help→About→Version_ or code `Orange.version.full_version` -->
3.26.0.dev

##### Expected behavior
Word Enrichment sorts by p-value by default.


##### Actual behavior
It sorts by words (alphabetically).




--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `orangecontrib/text/widgets/owwordenrichment.py`
Content:
```
1 from types import SimpleNamespace
2 from typing import List, Optional, Any
3 
4 import numpy as np
5 from AnyQt.QtWidgets import QTreeWidget, QTreeView, QTreeWidgetItem
6 
7 from Orange.data import Table, Domain
8 from Orange.widgets import gui
9 from Orange.widgets.settings import Setting
10 from Orange.widgets.utils.concurrent import ConcurrentWidgetMixin, TaskState
11 from Orange.widgets.widget import OWWidget, Msg, Input
12 from Orange.statistics.util import FDR
13 from PyQt5.QtCore import QSize
14 from orangecontrib.text import Corpus
15 from orangecontrib.text.util import np_sp_sum
16 from orangecontrib.text.stats import hypergeom_p_values
17 
18 
19 class Result(SimpleNamespace):
20     words: Optional[List[str]] = None
21     p_values: Optional[List[float]] = None
22     fdr_values: Optional[List[float]] = None
23 
24 
25 class Runner:
26     @staticmethod
27     def run(
28             selected_data_transformed: Table,
29             data: Table,
30             result: Result,
31             state: TaskState
32     ) -> None:
33         state.set_status("Listing words")
34         result.words = [
35             i.name for i in selected_data_transformed.domain.attributes]
36         state.set_status("Computing p-values")
37         result.p_values = hypergeom_p_values(
38             data.X, selected_data_transformed.X,
39             callback=state.set_progress_value
40         )
41         state.set_status("Computing FDR values")
42         result.fdr_values = FDR(result.p_values)
43 
44 
45 class OWWordEnrichment(OWWidget, ConcurrentWidgetMixin):
46     # Basic widget info
47     name = "Word Enrichment"
48     description = "Word enrichment analysis for selected documents."
49     icon = "icons/SetEnrichment.svg"
50     priority = 600
51 
52     # Input/output
53     class Inputs:
54         selected_data = Input("Selected Data", Table)
55         data = Input("Data", Table)
56 
57     want_main_area = True
58 
59     class Error(OWWidget.Error):
60         no_bow_features = Msg('No bag-of-words features!')
61         no_words_overlap = Msg('No words overlap!')
62         empty_selection = Msg('Selected data is empty!')
63         all_selected = Msg('All examples can not be selected!')
64 
65     # Settings
66     filter_by_p: bool = Setting(False)
67     filter_p_value: float = Setting(0.01)
68     filter_by_fdr: bool = Setting(True)
69     filter_fdr_value: float = Setting(0.2)
70 
71     def __init__(self):
72         OWWidget.__init__(self)
73         ConcurrentWidgetMixin.__init__(self)
74 
75         # Init data
76         self.data = None
77         self.selected_data = None
78         # used for transforming the 'selected data' into the 'data' domain
79         self.selected_data_transformed = None
80 
81         self.results = Result()
82 
83         # info box
84         fbox = gui.widgetBox(self.controlArea, "Info")
85         self.info_fil = gui.label(fbox, self, 'Words displayed: 0')
86 
87         # Filtering settings
88         fbox = gui.widgetBox(self.controlArea, "Filter")
89         hbox = gui.widgetBox(fbox, orientation=0)
90 
91         self.chb_p = gui.checkBox(hbox, self, "filter_by_p", "p-value",
92                                   callback=self.filter_and_display,
93                                   tooltip="Filter by word p-value")
94         self.spin_p = gui.doubleSpin(hbox, self, 'filter_p_value',
95                                      1e-4, 1, step=1e-4, labelWidth=15,
96                                      callback=self.filter_and_display,
97                                      tooltip="Max p-value for word")
98         self.spin_p.setEnabled(self.filter_by_p)
99 
100         hbox = gui.widgetBox(fbox, orientation=0)
101         self.chb_fdr = gui.checkBox(hbox, self, "filter_by_fdr", "FDR",
102                                     callback=self.filter_and_display,
103                                     tooltip="Filter by word FDR")
104         self.spin_fdr = gui.doubleSpin(hbox, self, 'filter_fdr_value',
105                                        1e-4, 1, step=1e-4, labelWidth=15,
106                                        callback=self.filter_and_display,
107                                        tooltip="Max p-value for word")
108         self.spin_fdr.setEnabled(self.filter_by_fdr)
109         gui.rubber(self.controlArea)
110 
111         # Word's list view
112         self.cols = ['Word', 'p-value', 'FDR']
113         self.sig_words = QTreeWidget()
114         self.sig_words.setColumnCount(len(self.cols))
115         self.sig_words.setHeaderLabels(self.cols)
116         self.sig_words.setSortingEnabled(True)
117         self.sig_words.setSelectionMode(QTreeView.NoSelection)
118         self.sig_words.sortByColumn(2, 0)   # 0 is ascending order
119         for i in range(len(self.cols)):
120             self.sig_words.resizeColumnToContents(i)
121         self.mainArea.layout().addWidget(self.sig_words)
122 
123     def sizeHint(self):
124         return QSize(450, 240)
125 
126     @Inputs.data
127     def set_data(self, data=None):
128         self.data = data
129         # selected data transformed depends on data domain
130         self.selected_data_transformed = None
131 
132     @Inputs.selected_data
133     def set_data_selected(self, data=None):
134         self.selected_data = data
135 
136     def handleNewSignals(self):
137         self.check_data()
138 
139     def get_bow_domain(self):
140         domain = self.data.domain
141         return Domain(
142             attributes=[a for a in domain.attributes
143                         if a.attributes.get('bow-feature', False)],
144             class_vars=domain.class_vars,
145             metas=domain.metas,
146             source=domain)
147 
148     def check_data(self):
149         self.Error.clear()
150         if isinstance(self.data, Table) and \
151                 isinstance(self.selected_data, Table):
152             if len(self.selected_data) == 0:
153                 self.Error.empty_selection()
154                 self.clear()
155                 return
156 
157             # keep only BoW features
158             bow_domain = self.get_bow_domain()
159             if len(bow_domain.attributes) == 0:
160                 self.Error.no_bow_features()
161                 self.clear()
162                 return
163             self.data = Corpus.from_table(bow_domain, self.data)
164             self.selected_data_transformed = Corpus.from_table(
165                 bow_domain, self.selected_data)
166 
167             if np_sp_sum(self.selected_data_transformed.X) == 0:
168                 self.Error.no_words_overlap()
169                 self.clear()
170             elif len(self.data) == len(self.selected_data):
171                 self.Error.all_selected()
172                 self.clear()
173             else:
174                 self.set_input_info()
175                 self.apply()
176         else:
177             self.clear()
178 
179     def clear(self):
180         self.sig_words.clear()
181         self.info.set_input_summary(self.info.NoInput)
182         self.set_displayed_info(0)
183 
184     def filter_enabled(self, b):
185         self.chb_p.setEnabled(b)
186         self.chb_fdr.setEnabled(b)
187         self.spin_p.setEnabled(b)
188         self.spin_fdr.setEnabled(b)
189 
190     def filter_and_display(self):
191         self.spin_p.setEnabled(self.filter_by_p)
192         self.spin_fdr.setEnabled(self.filter_by_fdr)
193         self.sig_words.clear()
194 
195         if self.selected_data_transformed is None:  # do nothing when no Data
196             return
197 
198         if self.results.words:
199             count = self.build_tree()
200         else:
201             count = 0
202 
203         for i in range(len(self.cols)):
204             self.sig_words.resizeColumnToContents(i)
205         self.set_displayed_info(count)
206 
207     def build_tree(self) -> int:
208         count = 0
209         for word, pval, fval in zip(
210                 self.results.words,
211                 self.results.p_values,
212                 self.results.fdr_values
213         ):
214             if ((not self.filter_by_p or pval <= self.filter_p_value) and
215                     (not self.filter_by_fdr or fval <= self.filter_fdr_value)):
216                 it = EATreeWidgetItem(word, pval, fval, self.sig_words)
217                 self.sig_words.addTopLevelItem(it)
218                 count += 1
219         return count
220 
221     def set_input_info(self) -> None:
222         cluster_words = len(self.selected_data_transformed.domain.attributes)
223         selected_words = np.count_nonzero(np_sp_sum(
224             self.selected_data_transformed.X, axis=0))
225 
226         self.info.set_input_summary(
227             f"{cluster_words}|{selected_words}",
228             f"Total words: {cluster_words}\n"
229             f"Words in subset: {selected_words}")
230 
231     def set_displayed_info(self, count: int) -> None:
232         self.info_fil.setText(f"Words displayed: {count}")
233 
234     def apply(self):
235         self.sig_words.clear()
236         self.filter_enabled(False)
237         self.start(
238             Runner.run,
239             self.selected_data_transformed,
240             self.data,
241             self.results
242         )
243 
244     def on_done(self, result: Result) -> None:
245         self.filter_and_display()
246         self.filter_enabled(True)
247 
248     def on_exception(self, ex: Exception) -> None:
249         self.filter_enabled(True)
250 
251     def tree_to_table(self):
252         view = [self.cols]
253         items = self.sig_words.topLevelItemCount()
254         for i in range(items):
255             line = []
256             for j in range(3):
257                 line.append(self.sig_words.topLevelItem(i).text(j))
258             view.append(line)
259         return view
260 
261     def send_report(self):
262         if self.results.words:
263             self.report_table("Enriched words", self.tree_to_table())
264 
265 
266 fp = lambda score: "%0.5f" % score if score > 10e-3 else "%0.1e" % score
267 fpt = lambda score: "%0.9f" % score if score > 10e-3 else "%0.5e" % score
268 
269 
270 class EATreeWidgetItem(QTreeWidgetItem):
271     def __init__(self, word, p_value, f_value, parent):
272         super().__init__(parent)
273         self.data = [word, p_value, f_value]
274         self.setText(0, word)
275         self.setText(1, fp(p_value))
276         self.setToolTip(1, fpt(p_value))
277         self.setText(2, fp(f_value))
278         self.setToolTip(2, fpt(f_value))
279 
280     def __lt__(self, other):
281         col = self.treeWidget().sortColumn()
282         return self.data[col] < other.data[col]
283 
284 
285 if __name__ == '__main__':
286     from orangewidget.utils.widgetpreview import WidgetPreview
287     from orangecontrib.text.vectorization import BowVectorizer
288 
289     corpus = Corpus.from_file('book-excerpts')
290     vect = BowVectorizer()
291     corpus_vect = vect.transform(corpus)
292     WidgetPreview(OWWordEnrichment).run(
293         set_data_selected=corpus_vect[:10], set_data=corpus_vect)
294
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/orangecontrib/text/widgets/owwordenrichment.py b/orangecontrib/text/widgets/owwordenrichment.py
--- a/orangecontrib/text/widgets/owwordenrichment.py
+++ b/orangecontrib/text/widgets/owwordenrichment.py
@@ -115,7 +115,7 @@
         self.sig_words.setHeaderLabels(self.cols)
         self.sig_words.setSortingEnabled(True)
         self.sig_words.setSelectionMode(QTreeView.NoSelection)
-        self.sig_words.sortByColumn(2, 0)   # 0 is ascending order
+        self.sig_words.sortByColumn(1, 0)   # 0 is ascending order
         for i in range(len(self.cols)):
             self.sig_words.resizeColumnToContents(i)
         self.mainArea.layout().addWidget(self.sig_words)

{"golden_diff": "diff --git a/orangecontrib/text/widgets/owwordenrichment.py b/orangecontrib/text/widgets/owwordenrichment.py\n--- a/orangecontrib/text/widgets/owwordenrichment.py\n+++ b/orangecontrib/text/widgets/owwordenrichment.py\n@@ -115,7 +115,7 @@\n         self.sig_words.setHeaderLabels(self.cols)\n         self.sig_words.setSortingEnabled(True)\n         self.sig_words.setSelectionMode(QTreeView.NoSelection)\n-        self.sig_words.sortByColumn(2, 0)   # 0 is ascending order\n+        self.sig_words.sortByColumn(1, 0)   # 0 is ascending order\n         for i in range(len(self.cols)):\n             self.sig_words.resizeColumnToContents(i)\n         self.mainArea.layout().addWidget(self.sig_words)\n", "issue": "Word Enrichment: sort by p-value\n<!--\r\nThis is an issue template. Please fill in the relevant details in the\r\nsections below.\r\n-->\r\n\r\n##### Text version\r\n<!-- From menu _Options\u2192Add-ons\u2192Orange3-Text_ or code `orangecontrib.text.version.full_version` -->\r\n0.8.0\r\n\r\n##### Orange version\r\n<!-- From menu _Help\u2192About\u2192Version_ or code `Orange.version.full_version` -->\r\n3.26.0.dev\r\n\r\n##### Expected behavior\r\nWord Enrichment sorts by p-value by default.\r\n\r\n\r\n##### Actual behavior\r\nIt sorts by words (alphabetically).\r\n\r\n\r\n\n", "before_files": [{"content": "from types import SimpleNamespace\nfrom typing import List, Optional, Any\n\nimport numpy as np\nfrom AnyQt.QtWidgets import QTreeWidget, QTreeView, QTreeWidgetItem\n\nfrom Orange.data import Table, Domain\nfrom Orange.widgets import gui\nfrom Orange.widgets.settings import Setting\nfrom Orange.widgets.utils.concurrent import ConcurrentWidgetMixin, TaskState\nfrom Orange.widgets.widget import OWWidget, Msg, Input\nfrom Orange.statistics.util import FDR\nfrom PyQt5.QtCore import QSize\nfrom orangecontrib.text import Corpus\nfrom orangecontrib.text.util import np_sp_sum\nfrom orangecontrib.text.stats import hypergeom_p_values\n\n\nclass Result(SimpleNamespace):\n    words: Optional[List[str]] = None\n    p_values: Optional[List[float]] = None\n    fdr_values: Optional[List[float]] = None\n\n\nclass Runner:\n    @staticmethod\n    def run(\n            selected_data_transformed: Table,\n            data: Table,\n            result: Result,\n            state: TaskState\n    ) -> None:\n        state.set_status(\"Listing words\")\n        result.words = [\n            i.name for i in selected_data_transformed.domain.attributes]\n        state.set_status(\"Computing p-values\")\n        result.p_values = hypergeom_p_values(\n            data.X, selected_data_transformed.X,\n            callback=state.set_progress_value\n        )\n        state.set_status(\"Computing FDR values\")\n        result.fdr_values = FDR(result.p_values)\n\n\nclass OWWordEnrichment(OWWidget, ConcurrentWidgetMixin):\n    # Basic widget info\n    name = \"Word Enrichment\"\n    description = \"Word enrichment analysis for selected documents.\"\n    icon = \"icons/SetEnrichment.svg\"\n    priority = 600\n\n    # Input/output\n    class Inputs:\n        selected_data = Input(\"Selected Data\", Table)\n        data = Input(\"Data\", Table)\n\n    want_main_area = True\n\n    class Error(OWWidget.Error):\n        no_bow_features = Msg('No bag-of-words features!')\n        no_words_overlap = Msg('No words overlap!')\n        empty_selection = Msg('Selected data is empty!')\n        all_selected = Msg('All examples can not be selected!')\n\n    # Settings\n    filter_by_p: bool = Setting(False)\n    filter_p_value: float = Setting(0.01)\n    filter_by_fdr: bool = Setting(True)\n    filter_fdr_value: float = Setting(0.2)\n\n    def __init__(self):\n        OWWidget.__init__(self)\n        ConcurrentWidgetMixin.__init__(self)\n\n        # Init data\n        self.data = None\n        self.selected_data = None\n        # used for transforming the 'selected data' into the 'data' domain\n        self.selected_data_transformed = None\n\n        self.results = Result()\n\n        # info box\n        fbox = gui.widgetBox(self.controlArea, \"Info\")\n        self.info_fil = gui.label(fbox, self, 'Words displayed: 0')\n\n        # Filtering settings\n        fbox = gui.widgetBox(self.controlArea, \"Filter\")\n        hbox = gui.widgetBox(fbox, orientation=0)\n\n        self.chb_p = gui.checkBox(hbox, self, \"filter_by_p\", \"p-value\",\n                                  callback=self.filter_and_display,\n                                  tooltip=\"Filter by word p-value\")\n        self.spin_p = gui.doubleSpin(hbox, self, 'filter_p_value',\n                                     1e-4, 1, step=1e-4, labelWidth=15,\n                                     callback=self.filter_and_display,\n                                     tooltip=\"Max p-value for word\")\n        self.spin_p.setEnabled(self.filter_by_p)\n\n        hbox = gui.widgetBox(fbox, orientation=0)\n        self.chb_fdr = gui.checkBox(hbox, self, \"filter_by_fdr\", \"FDR\",\n                                    callback=self.filter_and_display,\n                                    tooltip=\"Filter by word FDR\")\n        self.spin_fdr = gui.doubleSpin(hbox, self, 'filter_fdr_value',\n                                       1e-4, 1, step=1e-4, labelWidth=15,\n                                       callback=self.filter_and_display,\n                                       tooltip=\"Max p-value for word\")\n        self.spin_fdr.setEnabled(self.filter_by_fdr)\n        gui.rubber(self.controlArea)\n\n        # Word's list view\n        self.cols = ['Word', 'p-value', 'FDR']\n        self.sig_words = QTreeWidget()\n        self.sig_words.setColumnCount(len(self.cols))\n        self.sig_words.setHeaderLabels(self.cols)\n        self.sig_words.setSortingEnabled(True)\n        self.sig_words.setSelectionMode(QTreeView.NoSelection)\n        self.sig_words.sortByColumn(2, 0)   # 0 is ascending order\n        for i in range(len(self.cols)):\n            self.sig_words.resizeColumnToContents(i)\n        self.mainArea.layout().addWidget(self.sig_words)\n\n    def sizeHint(self):\n        return QSize(450, 240)\n\n    @Inputs.data\n    def set_data(self, data=None):\n        self.data = data\n        # selected data transformed depends on data domain\n        self.selected_data_transformed = None\n\n    @Inputs.selected_data\n    def set_data_selected(self, data=None):\n        self.selected_data = data\n\n    def handleNewSignals(self):\n        self.check_data()\n\n    def get_bow_domain(self):\n        domain = self.data.domain\n        return Domain(\n            attributes=[a for a in domain.attributes\n                        if a.attributes.get('bow-feature', False)],\n            class_vars=domain.class_vars,\n            metas=domain.metas,\n            source=domain)\n\n    def check_data(self):\n        self.Error.clear()\n        if isinstance(self.data, Table) and \\\n                isinstance(self.selected_data, Table):\n            if len(self.selected_data) == 0:\n                self.Error.empty_selection()\n                self.clear()\n                return\n\n            # keep only BoW features\n            bow_domain = self.get_bow_domain()\n            if len(bow_domain.attributes) == 0:\n                self.Error.no_bow_features()\n                self.clear()\n                return\n            self.data = Corpus.from_table(bow_domain, self.data)\n            self.selected_data_transformed = Corpus.from_table(\n                bow_domain, self.selected_data)\n\n            if np_sp_sum(self.selected_data_transformed.X) == 0:\n                self.Error.no_words_overlap()\n                self.clear()\n            elif len(self.data) == len(self.selected_data):\n                self.Error.all_selected()\n                self.clear()\n            else:\n                self.set_input_info()\n                self.apply()\n        else:\n            self.clear()\n\n    def clear(self):\n        self.sig_words.clear()\n        self.info.set_input_summary(self.info.NoInput)\n        self.set_displayed_info(0)\n\n    def filter_enabled(self, b):\n        self.chb_p.setEnabled(b)\n        self.chb_fdr.setEnabled(b)\n        self.spin_p.setEnabled(b)\n        self.spin_fdr.setEnabled(b)\n\n    def filter_and_display(self):\n        self.spin_p.setEnabled(self.filter_by_p)\n        self.spin_fdr.setEnabled(self.filter_by_fdr)\n        self.sig_words.clear()\n\n        if self.selected_data_transformed is None:  # do nothing when no Data\n            return\n\n        if self.results.words:\n            count = self.build_tree()\n        else:\n            count = 0\n\n        for i in range(len(self.cols)):\n            self.sig_words.resizeColumnToContents(i)\n        self.set_displayed_info(count)\n\n    def build_tree(self) -> int:\n        count = 0\n        for word, pval, fval in zip(\n                self.results.words,\n                self.results.p_values,\n                self.results.fdr_values\n        ):\n            if ((not self.filter_by_p or pval <= self.filter_p_value) and\n                    (not self.filter_by_fdr or fval <= self.filter_fdr_value)):\n                it = EATreeWidgetItem(word, pval, fval, self.sig_words)\n                self.sig_words.addTopLevelItem(it)\n                count += 1\n        return count\n\n    def set_input_info(self) -> None:\n        cluster_words = len(self.selected_data_transformed.domain.attributes)\n        selected_words = np.count_nonzero(np_sp_sum(\n            self.selected_data_transformed.X, axis=0))\n\n        self.info.set_input_summary(\n            f\"{cluster_words}|{selected_words}\",\n            f\"Total words: {cluster_words}\\n\"\n            f\"Words in subset: {selected_words}\")\n\n    def set_displayed_info(self, count: int) -> None:\n        self.info_fil.setText(f\"Words displayed: {count}\")\n\n    def apply(self):\n        self.sig_words.clear()\n        self.filter_enabled(False)\n        self.start(\n            Runner.run,\n            self.selected_data_transformed,\n            self.data,\n            self.results\n        )\n\n    def on_done(self, result: Result) -> None:\n        self.filter_and_display()\n        self.filter_enabled(True)\n\n    def on_exception(self, ex: Exception) -> None:\n        self.filter_enabled(True)\n\n    def tree_to_table(self):\n        view = [self.cols]\n        items = self.sig_words.topLevelItemCount()\n        for i in range(items):\n            line = []\n            for j in range(3):\n                line.append(self.sig_words.topLevelItem(i).text(j))\n            view.append(line)\n        return view\n\n    def send_report(self):\n        if self.results.words:\n            self.report_table(\"Enriched words\", self.tree_to_table())\n\n\nfp = lambda score: \"%0.5f\" % score if score > 10e-3 else \"%0.1e\" % score\nfpt = lambda score: \"%0.9f\" % score if score > 10e-3 else \"%0.5e\" % score\n\n\nclass EATreeWidgetItem(QTreeWidgetItem):\n    def __init__(self, word, p_value, f_value, parent):\n        super().__init__(parent)\n        self.data = [word, p_value, f_value]\n        self.setText(0, word)\n        self.setText(1, fp(p_value))\n        self.setToolTip(1, fpt(p_value))\n        self.setText(2, fp(f_value))\n        self.setToolTip(2, fpt(f_value))\n\n    def __lt__(self, other):\n        col = self.treeWidget().sortColumn()\n        return self.data[col] < other.data[col]\n\n\nif __name__ == '__main__':\n    from orangewidget.utils.widgetpreview import WidgetPreview\n    from orangecontrib.text.vectorization import BowVectorizer\n\n    corpus = Corpus.from_file('book-excerpts')\n    vect = BowVectorizer()\n    corpus_vect = vect.transform(corpus)\n    WidgetPreview(OWWordEnrichment).run(\n        set_data_selected=corpus_vect[:10], set_data=corpus_vect)\n", "path": "orangecontrib/text/widgets/owwordenrichment.py"}], "after_files": [{"content": "from types import SimpleNamespace\nfrom typing import List, Optional, Any\n\nimport numpy as np\nfrom AnyQt.QtWidgets import QTreeWidget, QTreeView, QTreeWidgetItem\n\nfrom Orange.data import Table, Domain\nfrom Orange.widgets import gui\nfrom Orange.widgets.settings import Setting\nfrom Orange.widgets.utils.concurrent import ConcurrentWidgetMixin, TaskState\nfrom Orange.widgets.widget import OWWidget, Msg, Input\nfrom Orange.statistics.util import FDR\nfrom PyQt5.QtCore import QSize\nfrom orangecontrib.text import Corpus\nfrom orangecontrib.text.util import np_sp_sum\nfrom orangecontrib.text.stats import hypergeom_p_values\n\n\nclass Result(SimpleNamespace):\n    words: Optional[List[str]] = None\n    p_values: Optional[List[float]] = None\n    fdr_values: Optional[List[float]] = None\n\n\nclass Runner:\n    @staticmethod\n    def run(\n            selected_data_transformed: Table,\n            data: Table,\n            result: Result,\n            state: TaskState\n    ) -> None:\n        state.set_status(\"Listing words\")\n        result.words = [\n            i.name for i in selected_data_transformed.domain.attributes]\n        state.set_status(\"Computing p-values\")\n        result.p_values = hypergeom_p_values(\n            data.X, selected_data_transformed.X,\n            callback=state.set_progress_value\n        )\n        state.set_status(\"Computing FDR values\")\n        result.fdr_values = FDR(result.p_values)\n\n\nclass OWWordEnrichment(OWWidget, ConcurrentWidgetMixin):\n    # Basic widget info\n    name = \"Word Enrichment\"\n    description = \"Word enrichment analysis for selected documents.\"\n    icon = \"icons/SetEnrichment.svg\"\n    priority = 600\n\n    # Input/output\n    class Inputs:\n        selected_data = Input(\"Selected Data\", Table)\n        data = Input(\"Data\", Table)\n\n    want_main_area = True\n\n    class Error(OWWidget.Error):\n        no_bow_features = Msg('No bag-of-words features!')\n        no_words_overlap = Msg('No words overlap!')\n        empty_selection = Msg('Selected data is empty!')\n        all_selected = Msg('All examples can not be selected!')\n\n    # Settings\n    filter_by_p: bool = Setting(False)\n    filter_p_value: float = Setting(0.01)\n    filter_by_fdr: bool = Setting(True)\n    filter_fdr_value: float = Setting(0.2)\n\n    def __init__(self):\n        OWWidget.__init__(self)\n        ConcurrentWidgetMixin.__init__(self)\n\n        # Init data\n        self.data = None\n        self.selected_data = None\n        # used for transforming the 'selected data' into the 'data' domain\n        self.selected_data_transformed = None\n\n        self.results = Result()\n\n        # info box\n        fbox = gui.widgetBox(self.controlArea, \"Info\")\n        self.info_fil = gui.label(fbox, self, 'Words displayed: 0')\n\n        # Filtering settings\n        fbox = gui.widgetBox(self.controlArea, \"Filter\")\n        hbox = gui.widgetBox(fbox, orientation=0)\n\n        self.chb_p = gui.checkBox(hbox, self, \"filter_by_p\", \"p-value\",\n                                  callback=self.filter_and_display,\n                                  tooltip=\"Filter by word p-value\")\n        self.spin_p = gui.doubleSpin(hbox, self, 'filter_p_value',\n                                     1e-4, 1, step=1e-4, labelWidth=15,\n                                     callback=self.filter_and_display,\n                                     tooltip=\"Max p-value for word\")\n        self.spin_p.setEnabled(self.filter_by_p)\n\n        hbox = gui.widgetBox(fbox, orientation=0)\n        self.chb_fdr = gui.checkBox(hbox, self, \"filter_by_fdr\", \"FDR\",\n                                    callback=self.filter_and_display,\n                                    tooltip=\"Filter by word FDR\")\n        self.spin_fdr = gui.doubleSpin(hbox, self, 'filter_fdr_value',\n                                       1e-4, 1, step=1e-4, labelWidth=15,\n                                       callback=self.filter_and_display,\n                                       tooltip=\"Max p-value for word\")\n        self.spin_fdr.setEnabled(self.filter_by_fdr)\n        gui.rubber(self.controlArea)\n\n        # Word's list view\n        self.cols = ['Word', 'p-value', 'FDR']\n        self.sig_words = QTreeWidget()\n        self.sig_words.setColumnCount(len(self.cols))\n        self.sig_words.setHeaderLabels(self.cols)\n        self.sig_words.setSortingEnabled(True)\n        self.sig_words.setSelectionMode(QTreeView.NoSelection)\n        self.sig_words.sortByColumn(1, 0)   # 0 is ascending order\n        for i in range(len(self.cols)):\n            self.sig_words.resizeColumnToContents(i)\n        self.mainArea.layout().addWidget(self.sig_words)\n\n    def sizeHint(self):\n        return QSize(450, 240)\n\n    @Inputs.data\n    def set_data(self, data=None):\n        self.data = data\n        # selected data transformed depends on data domain\n        self.selected_data_transformed = None\n\n    @Inputs.selected_data\n    def set_data_selected(self, data=None):\n        self.selected_data = data\n\n    def handleNewSignals(self):\n        self.check_data()\n\n    def get_bow_domain(self):\n        domain = self.data.domain\n        return Domain(\n            attributes=[a for a in domain.attributes\n                        if a.attributes.get('bow-feature', False)],\n            class_vars=domain.class_vars,\n            metas=domain.metas,\n            source=domain)\n\n    def check_data(self):\n        self.Error.clear()\n        if isinstance(self.data, Table) and \\\n                isinstance(self.selected_data, Table):\n            if len(self.selected_data) == 0:\n                self.Error.empty_selection()\n                self.clear()\n                return\n\n            # keep only BoW features\n            bow_domain = self.get_bow_domain()\n            if len(bow_domain.attributes) == 0:\n                self.Error.no_bow_features()\n                self.clear()\n                return\n            self.data = Corpus.from_table(bow_domain, self.data)\n            self.selected_data_transformed = Corpus.from_table(\n                bow_domain, self.selected_data)\n\n            if np_sp_sum(self.selected_data_transformed.X) == 0:\n                self.Error.no_words_overlap()\n                self.clear()\n            elif len(self.data) == len(self.selected_data):\n                self.Error.all_selected()\n                self.clear()\n            else:\n                self.set_input_info()\n                self.apply()\n        else:\n            self.clear()\n\n    def clear(self):\n        self.sig_words.clear()\n        self.info.set_input_summary(self.info.NoInput)\n        self.set_displayed_info(0)\n\n    def filter_enabled(self, b):\n        self.chb_p.setEnabled(b)\n        self.chb_fdr.setEnabled(b)\n        self.spin_p.setEnabled(b)\n        self.spin_fdr.setEnabled(b)\n\n    def filter_and_display(self):\n        self.spin_p.setEnabled(self.filter_by_p)\n        self.spin_fdr.setEnabled(self.filter_by_fdr)\n        self.sig_words.clear()\n\n        if self.selected_data_transformed is None:  # do nothing when no Data\n            return\n\n        if self.results.words:\n            count = self.build_tree()\n        else:\n            count = 0\n\n        for i in range(len(self.cols)):\n            self.sig_words.resizeColumnToContents(i)\n        self.set_displayed_info(count)\n\n    def build_tree(self) -> int:\n        count = 0\n        for word, pval, fval in zip(\n                self.results.words,\n                self.results.p_values,\n                self.results.fdr_values\n        ):\n            if ((not self.filter_by_p or pval <= self.filter_p_value) and\n                    (not self.filter_by_fdr or fval <= self.filter_fdr_value)):\n                it = EATreeWidgetItem(word, pval, fval, self.sig_words)\n                self.sig_words.addTopLevelItem(it)\n                count += 1\n        return count\n\n    def set_input_info(self) -> None:\n        cluster_words = len(self.selected_data_transformed.domain.attributes)\n        selected_words = np.count_nonzero(np_sp_sum(\n            self.selected_data_transformed.X, axis=0))\n\n        self.info.set_input_summary(\n            f\"{cluster_words}|{selected_words}\",\n            f\"Total words: {cluster_words}\\n\"\n            f\"Words in subset: {selected_words}\")\n\n    def set_displayed_info(self, count: int) -> None:\n        self.info_fil.setText(f\"Words displayed: {count}\")\n\n    def apply(self):\n        self.sig_words.clear()\n        self.filter_enabled(False)\n        self.start(\n            Runner.run,\n            self.selected_data_transformed,\n            self.data,\n            self.results\n        )\n\n    def on_done(self, result: Result) -> None:\n        self.filter_and_display()\n        self.filter_enabled(True)\n\n    def on_exception(self, ex: Exception) -> None:\n        self.filter_enabled(True)\n\n    def tree_to_table(self):\n        view = [self.cols]\n        items = self.sig_words.topLevelItemCount()\n        for i in range(items):\n            line = []\n            for j in range(3):\n                line.append(self.sig_words.topLevelItem(i).text(j))\n            view.append(line)\n        return view\n\n    def send_report(self):\n        if self.results.words:\n            self.report_table(\"Enriched words\", self.tree_to_table())\n\n\nfp = lambda score: \"%0.5f\" % score if score > 10e-3 else \"%0.1e\" % score\nfpt = lambda score: \"%0.9f\" % score if score > 10e-3 else \"%0.5e\" % score\n\n\nclass EATreeWidgetItem(QTreeWidgetItem):\n    def __init__(self, word, p_value, f_value, parent):\n        super().__init__(parent)\n        self.data = [word, p_value, f_value]\n        self.setText(0, word)\n        self.setText(1, fp(p_value))\n        self.setToolTip(1, fpt(p_value))\n        self.setText(2, fp(f_value))\n        self.setToolTip(2, fpt(f_value))\n\n    def __lt__(self, other):\n        col = self.treeWidget().sortColumn()\n        return self.data[col] < other.data[col]\n\n\nif __name__ == '__main__':\n    from orangewidget.utils.widgetpreview import WidgetPreview\n    from orangecontrib.text.vectorization import BowVectorizer\n\n    corpus = Corpus.from_file('book-excerpts')\n    vect = BowVectorizer()\n    corpus_vect = vect.transform(corpus)\n    WidgetPreview(OWWordEnrichment).run(\n        set_data_selected=corpus_vect[:10], set_data=corpus_vect)\n", "path": "orangecontrib/text/widgets/owwordenrichment.py"}]}