1616from scipy .stats import f_oneway , chisquare
1717
1818import Orange .data
19+ from Orange .data .filter import FilterDiscrete , FilterContinuous , Values
1920from Orange .statistics import contingency , distribution
2021
2122from Orange .widgets import widget , gui
2223from Orange .widgets .settings import (Setting , DomainContextHandler ,
2324 ContextSetting )
2425from Orange .widgets .utils .itemmodels import VariableListModel
26+ from Orange .widgets .utils .annotated_data import (create_annotated_table ,
27+ ANNOTATED_DATA_SIGNAL_NAME )
28+ from Orange .widgets .widget import Default
2529
2630
2731def compute_scale (min_ , max_ ):
@@ -41,7 +45,7 @@ def compute_scale(min_, max_):
4145
4246
4347class BoxData :
44- def __init__ (self , dist ):
48+ def __init__ (self , dist , attr , group_val_index = None , group_var = None ):
4549 self .dist = dist
4650 self .n = n = np .sum (dist [1 ])
4751 if n == 0 :
@@ -69,6 +73,17 @@ def __init__(self, dist):
6973 else :
7074 self .q25 = self .q75 = None
7175 self .median = q [1 ] if len (q ) == 2 else None
76+ self .conditions = [FilterContinuous (attr , FilterContinuous .Between ,
77+ self .q25 , self .q75 )]
78+ if group_val_index is not None :
79+ self .conditions .append (FilterDiscrete (group_var , [group_val_index ]))
80+
81+
82+ class FilterGraphicsRectItem (QGraphicsRectItem ):
83+ def __init__ (self , conditions , * args ):
84+ super ().__init__ (* args )
85+ self .filter = Values (conditions ) if conditions else None
86+ self .setFlag (QGraphicsItem .ItemIsSelectable )
7287
7388
7489class OWBoxPlot (widget .OWWidget ):
@@ -103,11 +118,14 @@ class OWBoxPlot(widget.OWWidget):
103118 icon = "icons/BoxPlot.svg"
104119 priority = 100
105120 inputs = [("Data" , Orange .data .Table , "set_data" )]
121+ outputs = [("Selected Data" , Orange .data .Table , Default ),
122+ (ANNOTATED_DATA_SIGNAL_NAME , Orange .data .Table )]
106123
107124 #: Comparison types for continuous variables
108125 CompareNone , CompareMedians , CompareMeans = 0 , 1 , 2
109126
110127 settingsHandler = DomainContextHandler ()
128+ conditions = ContextSetting ([])
111129
112130 attribute = ContextSetting (None )
113131 order_by_importance = Setting (False )
@@ -117,6 +135,7 @@ class OWBoxPlot(widget.OWWidget):
117135 stattest = Setting (0 )
118136 sig_threshold = Setting (0.05 )
119137 stretched = Setting (True )
138+ auto_commit = Setting (True )
120139
121140 _sorting_criteria_attrs = {
122141 CompareNone : "" , CompareMedians : "median" , CompareMeans : "mean"
@@ -201,8 +220,12 @@ def __init__(self):
201220 callback = self .display_changed ,
202221 sizePolicy = (QSizePolicy .Minimum , QSizePolicy .Maximum )).box
203222
223+ gui .auto_commit (self .controlArea , self , "auto_commit" ,
224+ "Send Selection" , "Send Automatically" )
225+
204226 gui .vBox (self .mainArea , addSpace = True )
205227 self .box_scene = QGraphicsScene ()
228+ self .box_scene .selectionChanged .connect (self .commit )
206229 self .box_view = QGraphicsView (self .box_scene )
207230 self .box_view .setRenderHints (QPainter .Antialiasing |
208231 QPainter .TextAntialiasing |
@@ -258,6 +281,7 @@ def set_data(self, dataset):
258281 self .grouping_changed ()
259282 else :
260283 self .reset_all_data ()
284+ self .commit ()
261285
262286 def apply_sorting (self ):
263287 def compute_score (attr ):
@@ -319,6 +343,13 @@ def grouping_changed(self):
319343 self .apply_sorting ()
320344 self .attr_changed ()
321345
346+ def select_box_items (self ):
347+ temp_cond = self .conditions .copy ()
348+ for box in self .box_scene .items ():
349+ if isinstance (box , FilterGraphicsRectItem ):
350+ box .setSelected (box .filter .conditions in
351+ [c .conditions for c in temp_cond ])
352+
322353 def attr_changed (self ):
323354 self .compute_box_data ()
324355 self .update_display_box ()
@@ -346,13 +377,14 @@ def compute_box_data(self):
346377 self .conts = contingency .get_contingency (
347378 dataset , attr , self .group_var )
348379 if self .is_continuous :
349- self .stats = [BoxData (cont ) for cont in self .conts ]
380+ self .stats = [BoxData (cont , attr , i , self .group_var )
381+ for i , cont in enumerate (self .conts )]
350382 self .label_txts_all = self .group_var .values
351383 else :
352384 self .dist = distribution .get_distribution (dataset , attr )
353385 self .conts = []
354386 if self .is_continuous :
355- self .stats = [BoxData (self .dist )]
387+ self .stats = [BoxData (self .dist , attr , None )]
356388 self .label_txts_all = ["" ]
357389 self .label_txts = [txts for stat , txts in zip (self .stats ,
358390 self .label_txts_all )
@@ -369,12 +401,15 @@ def update_display_box(self):
369401 self .display_box .hide ()
370402
371403 def clear_scene (self ):
404+ self .closeContext ()
405+ self .box_scene .clearSelection ()
372406 self .box_scene .clear ()
373407 self .attr_labels = []
374408 self .labels = []
375409 self .boxes = []
376410 self .mean_labels = []
377411 self .posthoc_lines = []
412+ self .openContext (self .dataset )
378413
379414 def layout_changed (self ):
380415 attr = self .attribute
@@ -395,7 +430,7 @@ def layout_changed(self):
395430 for stat , mean_lab in zip (self .stats , self .mean_labels )]
396431 self .attr_labels = [QGraphicsSimpleTextItem (lab )
397432 for lab in self .label_txts ]
398- for it in chain (self .labels , self .boxes , self . attr_labels ):
433+ for it in chain (self .labels , self .attr_labels ):
399434 self .box_scene .addItem (it )
400435 self .display_changed ()
401436
@@ -416,7 +451,9 @@ def display_changed(self):
416451
417452 for row , box_index in enumerate (self .order ):
418453 y = (- len (self .stats ) + row ) * heights + 10
419- self .boxes [box_index ].setY (y )
454+ for item in self .boxes [box_index ].childItems ():
455+ self .box_scene .addItem (item )
456+ item .setY (y )
420457 labels = self .labels [box_index ]
421458
422459 if self .show_annotations :
@@ -448,6 +485,7 @@ def display_changed(self):
448485
449486 self .compute_tests ()
450487 self .show_posthoc ()
488+ self .select_box_items ()
451489
452490 def display_changed_disc (self ):
453491 self .clear_scene ()
@@ -465,7 +503,7 @@ def display_changed_disc(self):
465503
466504 self .draw_axis_disc ()
467505 if self .group_var :
468- self .boxes = [self .strudel (cont ) for cont in self .conts ]
506+ self .boxes = [self .strudel (cont , i ) for i , cont in enumerate ( self .conts ) ]
469507 else :
470508 self .boxes = [self .strudel (self .dist )]
471509
@@ -496,12 +534,14 @@ def display_changed_disc(self):
496534 self .box_scene .addItem (label )
497535 for text_item in box .childItems ()[1 ::2 ]:
498536 box .removeFromGroup (text_item )
499- self .box_scene .addItem (box )
500- box .setPos (0 , y )
537+ for item in box .childItems ():
538+ self .box_scene .addItem (item )
539+ item .setPos (0 , y )
501540 self .box_scene .setSceneRect (- self .label_width - 5 ,
502541 - 30 - len (self .boxes ) * 40 ,
503542 self .scene_width , len (self .boxes * 40 ) + 90 )
504543 self .infot1 .setText ("" )
544+ self .select_box_items ()
505545
506546 # noinspection PyPep8Naming
507547 def compute_tests (self ):
@@ -584,7 +624,7 @@ def mean_label(self, stat, attr, val_name):
584624 def draw_axis (self ):
585625 """Draw the horizontal axis and sets self.scale_x"""
586626 misssing_stats = not self .stats
587- stats = self .stats or [BoxData (np .array ([[0. ], [1. ]]))]
627+ stats = self .stats or [BoxData (np .array ([[0. ], [1. ]]), self . attribute )]
588628 mean_labels = self .mean_labels or [self .mean_label (stats [0 ], self .attribute , "" )]
589629 bottom = min (stat .a_min for stat in stats )
590630 top = max (stat .a_max for stat in stats )
@@ -763,9 +803,9 @@ def line(x0, y0, x1, y1, *args):
763803 var_line .setPen (self ._pen_paramet )
764804
765805 if stat .q25 is not None and stat .q75 is not None :
766- mbox = QGraphicsRectItem ( stat . q25 * scale_x , - height / 2 ,
767- ( stat .q75 - stat .q25 ) * scale_x , height ,
768- box )
806+ mbox = FilterGraphicsRectItem (
807+ stat .conditions , stat .q25 * scale_x , - height / 2 ,
808+ ( stat . q75 - stat . q25 ) * scale_x , height , box )
769809 mbox .setBrush (self ._box_brush )
770810 mbox .setPen (QPen (Qt .NoPen ))
771811 mbox .setZValue (- 200 )
@@ -778,20 +818,26 @@ def line(x0, y0, x1, y1, *args):
778818
779819 return box
780820
781- def strudel (self , dist ):
821+ def strudel (self , dist , group_val_index = None ):
782822 attr = self .attribute
783823 ss = np .sum (dist )
784824 box = QGraphicsItemGroup ()
785825 if ss < 1e-6 :
786- QGraphicsRectItem (0 , - 10 , 1 , 10 , box )
826+ cond = [FilterDiscrete (attr , None )]
827+ if group_val_index is not None :
828+ cond .append (FilterDiscrete (self .group_var , [group_val_index ]))
829+ FilterGraphicsRectItem (cond , 0 , - 10 , 1 , 10 , box )
787830 cum = 0
788831 for i , v in enumerate (dist ):
789832 if v < 1e-6 :
790833 continue
791834 if self .stretched :
792835 v /= ss
793836 v *= self .scale_x
794- rect = QGraphicsRectItem (cum + 1 , - 6 , v - 2 , 12 , box )
837+ cond = [FilterDiscrete (attr , [i ])]
838+ if group_val_index is not None :
839+ cond .append (FilterDiscrete (self .group_var , [group_val_index ]))
840+ rect = FilterGraphicsRectItem (cond , cum + 1 , - 6 , v - 2 , 12 , box )
795841 rect .setBrush (QBrush (QColor (* attr .colors [i ])))
796842 rect .setPen (QPen (Qt .NoPen ))
797843 if self .stretched :
@@ -805,6 +851,18 @@ def strudel(self, dist):
805851 cum += v
806852 return box
807853
854+ def commit (self ):
855+ self .conditions = [item .filter for item in
856+ self .box_scene .selectedItems () if item .filter ]
857+ selected , selection = None , []
858+ if self .conditions :
859+ selected = Values (self .conditions , conjunction = False )(self .dataset )
860+ selection = [i for i , inst in enumerate (self .dataset )
861+ if inst in selected ]
862+ self .send ("Selected Data" , selected )
863+ self .send (ANNOTATED_DATA_SIGNAL_NAME ,
864+ create_annotated_table (self .dataset , selection ))
865+
808866 def show_posthoc (self ):
809867 def line (y0 , y1 ):
810868 it = self .box_scene .addLine (x , y0 , x , y1 , self ._post_line_pen )
0 commit comments