1+ from itertools import repeat
2+
13import numpy as np
24
35from Orange .data import DiscreteVariable , Table , Domain
46from Orange .widgets import gui
5- from Orange .widgets .settings import ContextSetting , DomainContextHandler
7+ from Orange .widgets .settings import ContextSetting , DomainContextHandler , \
8+ Setting
69from Orange .widgets .utils .itemmodels import DomainModel
710from Orange .widgets .widget import Input , Output , OWWidget , Msg
811from orangecontrib .network import Graph
@@ -33,42 +36,70 @@ class Error(OWWidget.Error):
3336 resizing_enabled = False
3437 want_main_area = False
3538
39+ NoWeights , WeightByDegrees , WeightByWeights = range (3 )
40+ weight_labels = \
41+ ["No weights" , "Number of connections" , "Sum of connection weights" ]
42+
3643 settingsHandler = DomainContextHandler ()
3744 feature = ContextSetting (None )
45+ weighting = Setting (2 )
46+ normalize = Setting (True )
3847
3948 def __init__ (self ):
4049 super ().__init__ ()
4150 self .network = None
4251 self .data = None
4352 self .effective_data = None
44- self .output_network = None
53+ self .out_nodes = self . out_edges = None
4554
4655 info_box = gui .widgetBox (self .controlArea , "Info" )
4756 self .input_label = gui .widgetLabel (info_box , "" )
48- self .__set_input_label_text ()
49-
50- feature_box = gui .widgetBox (self .controlArea , "Group by" )
51- self .feature_model = DomainModel (valid_types = DiscreteVariable )
52- self .feature_combo = gui .comboBox (
53- feature_box , self , "feature" , contentsLength = 15 ,
54- callback = self .__feature_combo_changed , model = self .feature_model
57+ self .output_label = gui .widgetLabel (info_box , "" )
58+ self ._set_input_label_text ()
59+ self ._set_output_label_text (None )
60+
61+ gui .comboBox (
62+ self .controlArea , self , "feature" , box = "Group by" ,
63+ callback = self .__feature_combo_changed ,
64+ model = DomainModel (valid_types = DiscreteVariable )
65+ )
66+ radios = gui .radioButtons (
67+ self .controlArea , self , "weighting" , box = "Output weights" ,
68+ btnLabels = self .weight_labels , callback = self .__feature_combo_changed
69+ )
70+ gui .separator (radios )
71+ gui .checkBox (
72+ radios , self , "normalize" , "Normalize by geometric mean" ,
73+ callback = self .__feature_combo_changed
5574 )
5675
57- def __set_input_label_text (self ):
58- text = "No data on input."
59- if self .network is not None :
60- dir_ = "Directed" if self .network .is_directed () else "Undirected"
61- text = f"{ dir_ } graph\n { self .network .number_of_nodes ()} " \
62- f" nodes, { self .network .number_of_edges ()} edges"
63- self .input_label .setText (text )
76+ def _set_input_label_text (self ):
77+ if self .network is None :
78+ self .input_label .setText ("Input: no data" )
79+ else :
80+ self .input_label .setText (
81+ f"Input: "
82+ f"{ self .network .number_of_nodes ()} nodes, "
83+ f"{ self .network .number_of_edges ()} edges" )
84+
85+ def _set_output_label_text (self , output_network ):
86+ if output_network is None :
87+ self .output_label .setText ("Output: no data" )
88+ self .out_nodes = self .out_edges = None
89+ else :
90+ self .out_nodes = output_network .number_of_nodes ()
91+ self .out_edges = output_network .number_of_edges ()
92+ self .output_label .setText (
93+ f"Output: { self .out_nodes } nodes, { self .out_edges } edges"
94+ )
6495
6596 def __feature_combo_changed (self ):
6697 self .commit ()
6798
6899 @Inputs .network
69100 def set_network (self , network ):
70101 self .network = network
71- self .__set_input_label_text ()
102+ self ._set_input_label_text ()
72103
73104 @Inputs .data
74105 def set_data (self , data ):
@@ -79,7 +110,7 @@ def handleNewSignals(self):
79110 self .clear_messages ()
80111 self .set_effective_data ()
81112 self .set_feature_model ()
82- if self .feature_model :
113+ if self .controls . feature . model () :
83114 self .openContext (self .effective_data )
84115 self .commit ()
85116
@@ -101,56 +132,77 @@ def set_effective_data(self):
101132
102133 def set_feature_model (self ):
103134 data = self .effective_data
104- self .feature_model .set_domain (data and data .domain )
105- self .feature = self .feature_model [0 ] if self .feature_model else None
135+ feature_model = self .controls .feature .model ()
136+ feature_model .set_domain (data and data .domain )
137+ self .feature = feature_model [0 ] if feature_model else None
106138
107139 def commit (self ):
108140 if self .feature is None :
109- self .output_network = None
110- self .Outputs .network .send (None )
111- self .Outputs .data .send (None )
112- return
113-
114- self ._map_network ()
115- self .Outputs .network .send (self .output_network )
116- self .Outputs .data .send (self .output_network .items ())
141+ output_network = None
142+ else :
143+ output_network = self ._map_network ()
144+ self .Outputs .network .send (output_network )
145+ self .Outputs .data .send (output_network and output_network .items ())
146+ self ._set_output_label_text (output_network )
117147
118148 def _map_network (self ):
119- edges = self .network .edges (data = 'weight' )
120- row , col , weights = zip (* edges )
121- row , col = self ._map_into_feature_values (np .array (row ), np .array (col ))
122- edges = self ._construct_edges (row , col , np .array (weights ))
149+ if self .weighting == self .WeightByWeights :
150+ edges = self .network .edges (data = 'weight' )
151+ row , col , weights = map (np .array , zip (* edges ))
152+ else :
153+ edges = self .network .edges ()
154+ row , col = map (np .array , zip (* edges ))
155+ weights = None
156+ if self .normalize :
157+ self ._normalize_weights (row , col , weights )
158+ row , col = self ._map_into_feature_values (row , col )
159+ edges = self ._construct_edges (row , col , weights )
123160
124161 network = Graph ()
125162 network .add_nodes_from (range (len (self .feature .values )))
126163 network .add_weighted_edges_from (edges )
127164 network .set_items (self ._construct_items ())
128- self .output_network = network
165+ return network
166+
167+ def _normalize_weights (self , row , col , weights ):
168+ if weights is None :
169+ weights = np .ones ((len (row )), dtype = float )
170+ degs = np .array (sorted (self .network .degree ()))[:, 1 ]
171+ else :
172+ degs = np .array (sorted (self .network .degree (weight = "weight" )))[:, 1 ]
173+ weights /= np .sqrt (degs .T [row ] * degs .T [col ])
129174
130175 def _map_into_feature_values (self , row , col ):
131176 selected_column = self .effective_data .get_column_view (self .feature )[0 ]
132177 return (selected_column [row ].astype (np .float64 ),
133178 selected_column [col ].astype (np .float64 ))
134179
135- @staticmethod
136- def _construct_edges (col , row , weights ):
180+ def _construct_edges (self , col , row , weights ):
137181 # remove edges that connect to "unknown" group
138182 mask = ~ np .any (np .isnan (np .vstack ((row , col ))), axis = 0 )
139183 # remove edges within a node
140184 mask = np .logical_and ((row != col ), mask )
141- row , col , weights = row [mask ], col [mask ], weights [mask ]
185+ row , col = row [mask ], col [mask ]
186+ if weights is not None :
187+ weights = weights [mask ]
142188
143189 # find unique edges
144190 mask = row > col
145191 row [mask ], col [mask ] = col [mask ], row [mask ]
146192
147- array = np .vstack ((row , col ))
193+ array = np .vstack ((row . astype ( int ) , col . astype ( int ) ))
148194 (row , col ), inverse = np .unique (array , axis = 1 , return_inverse = True )
149195
150- # assign each edge the sum of weights of belonging original edges
151- weights = np .array (
152- [np .sum (weights [inverse == i ]) for i in range (len (row ))])
153- return [(int (u ), int (v ), w ) for u , v , w in zip (row , col , weights )]
196+ if self .weighting == self .NoWeights :
197+ return zip (row , col , repeat (1.0 ))
198+ elif self .weighting == self .WeightByDegrees :
199+ return zip (
200+ row , col ,
201+ (np .sum (inverse == i ).astype (float ) for i in range (len (row ))))
202+ else : # self.WeightByWeights
203+ return zip (
204+ row , col ,
205+ (np .sum (weights [inverse == i ]) for i in range (len (row ))))
154206
155207 def _construct_items (self ):
156208 domain = Domain ([self .feature ])
@@ -164,12 +216,15 @@ def send_report(self):
164216 ("Number of vertices" , self .network .number_of_nodes ()),
165217 ("Number of edges" , self .network .number_of_edges ())])
166218 self .report_data ("Input data" , self .effective_data )
167- self .report_items ("Group by" , [("Feature" , self .feature .name )])
168- if self .output_network :
219+ self .report_items ("Settings" , [
220+ ("Group by" , self .feature .name ),
221+ ("Weights" , self .weight_labels [self .weighting ].lower () +
222+ (", normalized by geometric mean" if self .normalize else "" ))
223+ ])
224+ if self .out_nodes is not None :
169225 self .report_items ("Output network" , [
170- ("Number of vertices" , self .output_network .number_of_nodes ()),
171- ("Number of edges" , self .output_network .number_of_edges ())])
172- self .report_data ("Output data" , self .output_network .items ())
226+ ("Number of vertices" , self .out_nodes ),
227+ ("Number of edges" , self .out_edges )])
173228
174229
175230def main ():
0 commit comments