source: orange/Orange/OrangeWidgets/Unsupervised/OWNxAnalysis.py @ 10046:207f641d0485

Revision 10046:207f641d0485, 18.8 KB checked in by markotoplak, 2 years ago (diff)

Shortened hoerarchical clustering documentation. Some data.variable -> feature.

Line 
1"""
2<name>Net Analysis</name>
3<description>Statistical analysis of network data.</description>
4<icon>icons/NetworkFromDistances.png</icon>
5<contact>Miha Stajdohar (miha.stajdohar(@at@)gmail.com)</contact> 
6<priority>6425</priority>
7"""
8import numpy
9import networkx as nx
10
11import Orange
12import OWGUI
13
14from OWWidget import *
15
16NODELEVEL = 0
17GRAPHLEVEL = 1
18
19class WorkerThread(QThread):
20    def __init__(self, receiver, name, label, type, algorithm):
21        QThread.__init__(self)
22        self.receiver = receiver
23        self.name = name
24        self.label = label
25        self.type = type
26        self.algorithm = algorithm
27       
28        self.stopped = 0
29        self.result = None
30        self.error = None
31        self.is_terminated = False
32         
33    def run(self):
34        try:
35            self.result = self.algorithm(self.receiver.graph)
36        except Exception as ex:
37            self.result = None
38            self.error = ex
39       
40class OWNxAnalysis(OWWidget):
41    settingsList=[
42        "auto_commit", "tab_index", "degree", "in_degree", "out_degree", "average_neighbor_degree",
43        "clustering", "triangles", "square_clustering", "number_of_cliques",
44        "degree_centrality", "in_degree_centrality", "out_degree_centrality", 
45        "closeness_centrality", "betweenness_centrality", 
46        "current_flow_closeness_centrality", "current_flow_betweenness_centrality",
47        "approximate_current_flow_betweenness_centrality", 
48        "eigenvector_centrality", "eigenvector_centrality_numpy", "load_centrality", 
49        "core_number", "eccentricity", "closeness_vitality", 
50       
51        "number_of_nodes", "number_of_edges", "average_degree", "density",
52        "degree_assortativity_coefficient", "degree_pearson_correlation_coefficient", 
53        "degree_pearson_correlation_coefficient",
54        "estrada_index", "graph_clique_number", "graph_number_of_cliques", 
55        "transitivity", "average_clustering", "number_connected_components", 
56        "number_strongly_connected_components", "number_weakly_connected_components", 
57        "number_attracting_components", "diameter", "radius", "average_shortest_path_length"
58    ]
59   
60    def __init__(self, parent=None, signalManager=None):
61        OWWidget.__init__(self, parent, signalManager, "Nx Analysis", noReport=True, wantMainArea=False)
62       
63        self.inputs = [("Network", Orange.network.Graph, self.set_graph), 
64                        ("Items", Orange.data.Table, self.set_items)]
65       
66        self.outputs = [("Network", Orange.network.Graph), 
67                        ("Items", Orange.data.Table)]
68   
69        self.methods = [
70            ("number_of_nodes", True, "Number of nodes", GRAPHLEVEL, lambda G: G.number_of_nodes()),
71            ("number_of_edges", True, "Number of edges", GRAPHLEVEL, lambda G: G.number_of_edges()),
72            ("average_degree", True, "Average degree", GRAPHLEVEL, lambda G: numpy.average(G.degree().values())),
73            ("diameter", False, "Diameter", GRAPHLEVEL, nx.diameter),
74            ("radius", False, "Radius", GRAPHLEVEL, nx.radius),
75            ("average_shortest_path_length", False, "Average shortest path length", GRAPHLEVEL, nx.average_shortest_path_length),
76            ("density", True, "Density", GRAPHLEVEL, nx.density),
77            ("degree_assortativity_coefficient", False, "Degree assortativity coefficient", GRAPHLEVEL, nx.degree_assortativity_coefficient),
78            # additional attr needed
79            #("attribute_assortativity_coefficient", False, "Attribute assortativity coefficient", GRAPHLEVEL, nx.attribute_assortativity_coefficient),
80            #("numeric_assortativity_coefficient", False, "Numeric assortativity coefficient", GRAPHLEVEL, nx.numeric_assortativity_coefficient),
81            ("degree_pearson_correlation_coefficient", False, "Degree pearson correlation coefficient", GRAPHLEVEL, nx.degree_pearson_correlation_coefficient),
82            ("estrada_index", False, "Estrada index", GRAPHLEVEL, nx.estrada_index),
83            ("graph_clique_number", False, "Graph clique number", GRAPHLEVEL, nx.graph_clique_number),
84            ("graph_number_of_cliques", False, "Graph number of cliques", GRAPHLEVEL, nx.graph_number_of_cliques),
85            ("transitivity", False, "Graph transitivity", GRAPHLEVEL, nx.transitivity),
86            ("average_clustering", False, "Average clustering coefficient", GRAPHLEVEL, nx.average_clustering),
87            ("number_connected_components", False, "Number of connected components", GRAPHLEVEL, nx.number_connected_components),
88            ("number_strongly_connected_components", False, "Number of strongly connected components", GRAPHLEVEL, nx.number_strongly_connected_components),
89            ("number_weakly_connected_components", False, "Number of weakly connected components", GRAPHLEVEL, nx.number_weakly_connected_components),
90            ("number_attracting_components", False, "Number of attracting components", GRAPHLEVEL, nx.number_attracting_components),
91            # TODO: input parameters
92            #("max_flow", False, "Maximum flow", GRAPHLEVEL, nx.max_flow),
93            #("min_cut", False, "Minimum cut", GRAPHLEVEL, nx.min_cut),
94            #("ford_fulkerson", False, "Maximum single-commodity flow (Ford-Fulkerson)", GRAPHLEVEL, nx.ford_fulkerson),
95            #("min_cost_flow_cost", False, "min_cost_flow_cost", GRAPHLEVEL, nx.min_cost_flow_cost),
96            # returns dict of dict
97            #("shortest_path_length", False, "Shortest path length", GRAPHLEVEL, nx.shortest_path_length),
98           
99            ("degree", False, "Degree", NODELEVEL, nx.degree),
100            ("in_degree", False, "In-degree", NODELEVEL, lambda G: G.in_degree()),
101            ("out_degree", False, "Out-degree", NODELEVEL, lambda G: G.out_degree()),
102            ("average_neighbor_degree", False, "Average neighbor degree", NODELEVEL, nx.average_neighbor_degree),
103            ("clustering", False, "Clustering coefficient", NODELEVEL, nx.clustering),
104            ("triangles", False, "Number of triangles", NODELEVEL, nx.triangles),
105            ("square_clustering", False, "Squares clustering coefficient", NODELEVEL, nx.square_clustering),
106            ("number_of_cliques", False, "Number of cliques", NODELEVEL, nx.number_of_cliques),
107            ("degree_centrality", False, "Degree centrality", NODELEVEL, nx.degree_centrality),
108            ("in_degree_centrality", False, "In-egree centrality", NODELEVEL, nx.in_degree_centrality),
109            ("out_degree_centrality", False, "Out-degree centrality", NODELEVEL, nx.out_degree_centrality),
110            ("closeness_centrality", False, "Closeness centrality", NODELEVEL, nx.closeness_centrality),
111            ("betweenness_centrality", False, "Betweenness centrality", NODELEVEL, nx.betweenness_centrality),
112            ("current_flow_closeness_centrality", False, "Information centrality", NODELEVEL, nx.current_flow_closeness_centrality),
113            ("current_flow_betweenness_centrality", False, "Random-walk betweenness centrality", NODELEVEL, nx.current_flow_betweenness_centrality),
114            ("approximate_current_flow_betweenness_centrality", False, "Approx. random-walk betweenness centrality", NODELEVEL, nx.approximate_current_flow_betweenness_centrality),
115            ("eigenvector_centrality", False, "Eigenvector centrality", NODELEVEL, nx.eigenvector_centrality),
116            ("eigenvector_centrality_numpy", False, "Eigenvector centrality (NumPy)", NODELEVEL, nx.eigenvector_centrality_numpy),
117            ("load_centrality", False, "Load centrality", NODELEVEL, nx.load_centrality),
118            ("core_number", False, "Core number", NODELEVEL, nx.core_number),
119            ("eccentricity", False, "Eccentricity", NODELEVEL, nx.eccentricity),
120            ("closeness_vitality", False, "Closeness vitality", NODELEVEL, nx.closeness_vitality),                   
121        ]
122       
123        self.auto_commit = False
124        self.tab_index = 0
125        self.mutex = QMutex()
126       
127        self.graph = None
128        self.items = None          # items set by Items signal
129        self.items_graph = None    # items set by graph.items by Network signal
130        self.items_analysis = None # items to output and merge with analysis result
131       
132        self.job_queue = []
133        self.job_working = []
134        self.analfeatures = [] 
135        self.analdata = {}
136       
137        for method in self.methods:
138            setattr(self, method[0], method[1])
139            setattr(self, "lbl_" + method[0], "")
140   
141        self.loadSettings()
142       
143        self.tabs = OWGUI.tabWidget(self.controlArea)
144        self.tabs.setMinimumWidth(450)
145        self.graphIndices = OWGUI.createTabPage(self.tabs, "Graph-level indices")   
146        self.nodeIndices = OWGUI.createTabPage(self.tabs, "Node-level indices")
147        self.tabs.setCurrentIndex(self.tab_index)
148        self.connect(self.tabs, SIGNAL("currentChanged(int)"), lambda index: setattr(self, 'tab_index', index))
149       
150        for name, default, label, type, algorithm in self.methods:
151            if type == NODELEVEL:
152                box = OWGUI.widgetBox(self.nodeIndices, orientation="horizontal")
153            elif type == GRAPHLEVEL:
154                box = OWGUI.widgetBox(self.graphIndices, orientation="horizontal")
155           
156            OWGUI.checkBox(box, self, name, label=label, callback=lambda n=name: self.method_clicked(n))
157            box.layout().addStretch(1)
158            lbl = OWGUI.label(box, self, "%(lbl_" + name + ")s")
159            setattr(self, "tool_" + name, lbl)
160           
161        self.graphIndices.layout().addStretch(1)
162        self.nodeIndices.layout().addStretch(1)
163       
164        OWGUI.checkBox(self.controlArea, self, "auto_commit", label="Commit automatically")
165       
166        hb = OWGUI.widgetBox(self.controlArea, None, orientation='horizontal')
167        self.btnCommit = OWGUI.button(hb, self, "Commit", callback=self.analyze, toggleButton=1)
168        self.btnStopC = OWGUI.button(hb, self, "Stop current", callback=lambda current=True: self.stop_job(current))
169        self.btnStopA = OWGUI.button(hb, self, "Stop all", callback=lambda current=False: self.stop_job(current))
170        self.btnStopC.setEnabled(False)
171        self.btnStopA.setEnabled(False)
172       
173        self.reportButton = OWGUI.button(hb, self, "&Report", self.reportAndFinish, debuggingEnabled=0)
174        self.reportButton.setAutoDefault(0)
175       
176    def set_graph(self, graph):
177        if graph is None:
178            return
179       
180        self.stop_job(current=False)
181       
182        self.mutex.lock()
183       
184        self.graph = graph
185        self.items_graph = graph.items()
186        self.items_analysis = graph.items()
187       
188        if self.items is not None:
189            self.items_analysis =  self.items
190       
191        self.clear_results()
192        self.clear_labels()
193        # only clear computed statistics on new graph
194        self.analdata.clear()
195       
196        self.mutex.unlock()
197       
198        if self.auto_commit:
199            self.btnCommit.setChecked(True)
200            self.analyze()
201       
202       
203    def set_items(self, items):
204        self.mutex.lock()
205       
206        if items is None and self.items_graph is not None:
207            self.items_analysis = self.items_graph
208           
209        elif items is not None:
210            self.items_analysis = items
211       
212        self.items = items
213       
214        self.mutex.unlock()
215       
216    def analyze(self):
217        if self.graph is None or not self.btnCommit.isChecked():
218            return
219       
220        if len(self.job_queue) > 0 or len(self.job_working) > 0:
221            return
222       
223        self.btnStopC.setEnabled(True)
224        self.btnStopA.setEnabled(True)
225        self.clear_labels()
226        qApp.processEvents()
227       
228        self.clear_results()
229       
230        for method in self.methods:
231            self.add_job(method)
232           
233        if len(self.job_queue) > 0:         
234            self.start_job()
235        else:
236            self.send_data()
237   
238    def add_job(self, method):
239        name, default, label, type, algorithm = method
240       
241        is_method_enabled = getattr(self, name)
242           
243        if not is_method_enabled:
244            return
245       
246        #if type == NODELEVEL:
247        job = WorkerThread(self, name, label, type, algorithm)
248        self.connect(job, SIGNAL("terminated()"), lambda j=job: self.job_terminated(j))
249        self.connect(job, SIGNAL("finished()"), lambda j=job: self.job_finished(j))
250        self.job_queue.insert(0, job)
251        setattr(self, "lbl_" + job.name, "   waiting")
252   
253    def start_job(self):
254        max_jobs = QThread.idealThreadCount() - 1
255       
256        self.mutex.lock()
257        if len(self.job_queue) > 0 and len(self.job_working) < max_jobs:
258            job = self.job_queue.pop()
259            setattr(self, "lbl_" + job.name, "   started")
260           
261            # if data for this job already computed
262            if job.name in self.analdata:
263                if job.type == NODELEVEL:
264                    self.analfeatures.append((job.name, \
265                                Orange.feature.Continuous(job.label)))
266                    setattr(self, "lbl_" + job.name, "  finished")
267                   
268                elif job.type == GRAPHLEVEL:
269                    setattr(self, "lbl_" + job.name,("%.4f" % \
270                            self.analdata[job.name]).rstrip('0').rstrip('.'))                     
271               
272                job.quit()
273                self.send_data()
274            else:
275                self.job_working.append(job)
276                job.start()
277        self.mutex.unlock()
278       
279        if len(self.job_queue) > 0 and len(self.job_working) < max_jobs:
280            self.start_job()
281           
282    def job_terminated(self, job):
283        self.mutex.lock()
284        job.is_terminated = True
285        self.mutex.unlock()
286       
287    def job_finished(self, job):
288        self.mutex.lock()
289        if job.is_terminated:
290            setattr(self, "lbl_" + job.name, "terminated")
291        else:
292            setattr(self, "lbl_" + job.name, "  finished")
293       
294            if job.error is not None:
295                setattr(self, "lbl_" + job.name, "     error")
296                tooltop = getattr(self, "tool_" + job.name)
297                tooltop.setToolTip(QString(job.error.message))
298               
299            elif job.result is not None:
300                if job.type == NODELEVEL:
301                    self.analfeatures.append((job.name, Orange.feature.Continuous(job.label)))
302                    self.analdata[job.name] = [job.result[node] for node in sorted(job.result.iterkeys())]
303                   
304                elif job.type == GRAPHLEVEL:
305                    self.analdata[job.name] = job.result
306                    setattr(self, "lbl_" + job.name, ("%.4f" % job.result).rstrip('0').rstrip('.'))
307       
308        if job in self.job_working:
309            self.job_working.remove(job)
310
311        self.send_data()
312        self.mutex.unlock()
313       
314        if len(self.job_queue) > 0:
315            self.start_job()
316           
317    def stop_job(self, current=True, name=None):
318        self.mutex.lock()
319       
320        if name is not None:
321            for i in range(len(self.job_queue) - 1, -1, -1 ):
322                job = self.job_queue[i]
323                if name == job.name:
324                   
325                    job.is_terminated = True
326                    job.quit()
327                    job.wait()
328                    self.job_queue.remove(job)
329                    setattr(self, "lbl_" + name, "terminated")                           
330                   
331            for job in self.job_working:
332                if name == job.name:
333                    job.is_terminated = True
334                    job.terminate()
335        else:
336            if not current:
337                while len(self.job_queue) > 0:
338                    job = self.job_queue.pop()
339                    job.is_terminated = True
340                    job.quit()
341                    job.wait()
342                    setattr(self, "lbl_" + job.name, "terminated")
343   
344            for job in self.job_working:
345                job.is_terminated = True
346                job.terminate()
347
348        self.mutex.unlock()
349       
350    def send_data(self):
351        if len(self.job_queue) <= 0 and len(self.job_working) <= 0:
352            self.btnCommit.setChecked(False)
353            self.btnStopC.setEnabled(False)
354            self.btnStopA.setEnabled(False)
355           
356            if self.analdata is not None and len(self.analdata) > 0 and \
357                                                    len(self.analfeatures) > 0:
358                vars = [] 
359                analdata = []
360                for name, var in self.analfeatures:
361                    analdata.append(self.analdata[name])
362                    vars.append(var)
363                   
364                analitems  = Orange.data.Table(Orange.data.Domain(vars, False), [list(t) for t in zip(*analdata)])
365                self.graph.set_items(Orange.data.Table([analitems, self.items_analysis]))
366           
367            self.send("Network", self.graph)
368            self.send("Items", self.graph.items())
369           
370            self.clear_results()
371           
372    def method_clicked(self, name):
373        if self.auto_commit:
374            self.mutex.lock()
375            if len(self.job_queue) <= 0 and len(self.job_working) <= 0:
376                self.btnCommit.setChecked(True)
377                self.mutex.unlock()
378                self.analyze()
379            else:
380                is_method_enabled = getattr(self, name)
381                if is_method_enabled:
382                    for method in self.methods:
383                        if name == method[0]:
384                            self.add_job(method)
385                    self.mutex.unlock()
386                else:
387                    self.mutex.unlock()
388                    self.stop_job(name=name)
389           
390    def clear_results(self):
391        del self.job_queue[:]
392        del self.job_working[:]
393        del self.analfeatures[:]
394       
395    def clear_labels(self):
396        for method in self.methods:
397            setattr(self, "lbl_" + method[0], "")
398           
399    def sendReport(self):
400        report = []
401       
402        for name, default, label, type, algorithm in self.methods:
403            if type == GRAPHLEVEL:
404                value = getattr(self, "lbl_" + name)
405                value = str(value).strip().lower()
406                if value != "" and value != "error"  and value != "waiting" \
407                            and value != "terminated" and value != "finished":
408                    report.append((label, value))
409       
410        self.reportSettings("Graph statistics", report)
411           
412       
413if __name__ == "__main__":   
414    a=QApplication(sys.argv)
415    ow=OWNxAnalysis()
416    ow.show()
417    def setNetwork(signal, data, id=None):
418        if signal == 'Network':
419            ow.set_graph(data)
420        #if signal == 'Items':
421        #    ow.set_items(data)
422       
423    import OWNxFile
424    owFile = OWNxFile.OWNxFile()
425    owFile.send = setNetwork
426    owFile.show()
427    owFile.selectNetFile(0)
428   
429    a.exec_()
430    ow.saveSettings()
431    owFile.saveSettings()
Note: See TracBrowser for help on using the repository browser.