1010
1111from Orange .data import DiscreteVariable , ContinuousVariable , StringVariable , \
1212 TimeVariable , Domain
13+ from Orange .data .util import get_unique_names_duplicates
1314from Orange .statistics .util import unique
1415from Orange .widgets import gui
1516from Orange .widgets .gui import HorizontalGridDelegate
@@ -61,13 +62,14 @@ def set_variables(self, variables):
6162 def rowCount (self , parent ):
6263 return 0 if parent .isValid () else len (self .variables )
6364
64- def columnCount (self , parent ):
65+ @staticmethod
66+ def columnCount (parent ):
6567 return 0 if parent .isValid () else Column .not_valid
6668
6769 def data (self , index , role ):
6870 row , col = index .row (), index .column ()
6971 val = self .variables [row ][col ]
70- if role == Qt .DisplayRole or role == Qt .EditRole :
72+ if role in ( Qt .DisplayRole , Qt .EditRole ) :
7173 if col == Column .tpe :
7274 return self .type2name [val ]
7375 if col == Column .place :
@@ -90,8 +92,9 @@ def data(self, index, role):
9092 font = QFont ()
9193 font .setBold (True )
9294 return font
95+ return None
9396
94- def setData (self , index , value , role ):
97+ def setData (self , index , value , role = Qt . EditRole ):
9598 row , col = index .row (), index .column ()
9699 row_data = self .variables [row ]
97100 if role == Qt .EditRole :
@@ -110,6 +113,7 @@ def setData(self, index, value, role):
110113 # Settings may change background colors
111114 self .dataChanged .emit (index .sibling (row , 0 ), index .sibling (row , 3 ))
112115 return True
116+ return False
113117
114118 def headerData (self , i , orientation , role = Qt .DisplayRole ):
115119 if orientation == Qt .Horizontal and role == Qt .DisplayRole and i < 4 :
@@ -130,7 +134,7 @@ def __init__(self, view, items):
130134 self .view = view
131135 self .items = items
132136
133- def createEditor (self , parent , option , index ):
137+ def createEditor (self , parent , _option , index ):
134138 # This ugly hack closes the combo when the user selects an item
135139 class Combo (QComboBox ):
136140 def __init__ (self , * args ):
@@ -145,6 +149,8 @@ def showPopup(self, *args):
145149 super ().showPopup (* args )
146150 self .popup_shown = True
147151
152+ # Here, we need `self` from the closure
153+ # pylint: disable=no-self-argument,attribute-defined-outside-init
148154 def hidePopup (me ):
149155 if me .popup_shown :
150156 self .view .model ().setData (
@@ -250,21 +256,27 @@ def _merge(cols, force_dense=False):
250256 sparse_cols = [c if sp .issparse (c ) else sp .csc_matrix (c ) for c in cols ]
251257 return sp .hstack (sparse_cols ).tocsr ()
252258
253- def get_domain (self , domain , data ):
254- """Create domain (and dataset) from changes made in the widget.
255-
256- Parameters
257- ----------
258- domain : old domain
259- data : source data
259+ def get_domain (self , domain , data , deduplicate = False ):
260+ """
261+ Create domain (and dataset) from changes made in the widget.
260262
261263 Returns
262264 -------
263- (new_domain, [attribute_columns, class_var_columns, meta_columns])
265+
266+ Args:
267+ domain (Domain): original domain
268+ data (Table): original data
269+ deduplicate (bool): if True, variable names are deduplicated and
270+ the result contains an additional list with names of renamed
271+ variables
272+
273+ Returns:
274+ (new_domain, [attribute_columns, class_var_columns, meta_columns])
275+ or
276+ (new_domain, [attribute_columns, class_var_columns, meta_columns], renamed)
264277 """
265278 # Allow type-checking with type() instead of isinstance() for exact comparison
266279 # pylint: disable=unidiomatic-typecheck
267-
268280 variables = self .model ().variables
269281 places = [[], [], []] # attributes, class_vars, metas
270282 cols = [[], [], []] # Xcols, Ycols, Mcols
@@ -283,8 +295,17 @@ def numbers_are_round(var, col_data):
283295 chain (((at , Place .feature ) for at in domain .attributes ),
284296 ((cl , Place .class_var ) for cl in domain .class_vars ),
285297 ((mt , Place .meta ) for mt in domain .metas )))):
286- return domain , [data .X , data .Y , data .metas ]
298+ if deduplicate :
299+ return domain , [data .X , data .Y , data .metas ], []
300+ else :
301+ return domain , [data .X , data .Y , data .metas ]
287302
303+ relevant_names = [var [0 ] for var in variables if var [2 ] != Place .skip ]
304+ if deduplicate :
305+ renamed_iter = iter (get_unique_names_duplicates (relevant_names ))
306+ else :
307+ renamed_iter = iter (relevant_names )
308+ renamed = []
288309 for (name , tpe , place , _ , may_be_numeric ), (orig_var , orig_plc ) in \
289310 zip (variables ,
290311 chain ([(at , Place .feature ) for at in domain .attributes ],
@@ -293,24 +314,28 @@ def numbers_are_round(var, col_data):
293314 if place == Place .skip :
294315 continue
295316
317+ new_name = next (renamed_iter )
318+ if new_name != name and name not in renamed :
319+ renamed .append (name )
320+
296321 col_data = self ._get_column (data , orig_var , orig_plc )
297322 is_sparse = sp .issparse (col_data )
298323
299- if name == orig_var .name and tpe == type (orig_var ):
324+ if new_name == orig_var .name and tpe == type (orig_var ):
300325 var = orig_var
301326 elif tpe == type (orig_var ):
302- var = orig_var .copy (name = name )
327+ var = orig_var .copy (name = new_name )
303328 elif tpe == DiscreteVariable :
304329 values = list (str (i ) for i in unique (col_data ) if not self ._is_missing (i ))
305330 round_numbers = numbers_are_round (orig_var , col_data )
306331 col_data = [np .nan if self ._is_missing (x ) else values .index (str (x ))
307332 for x in self ._iter_vals (col_data )]
308333 if round_numbers :
309334 values = [str (int (float (v ))) for v in values ]
310- var = tpe (name , values )
335+ var = tpe (new_name , values )
311336 col_data = self ._to_column (col_data , is_sparse )
312337 elif tpe == StringVariable :
313- var = tpe .make (name )
338+ var = tpe .make (new_name )
314339 if type (orig_var ) in [DiscreteVariable , TimeVariable ]:
315340 col_data = [orig_var .repr_val (x ) if not np .isnan (x ) else ""
316341 for x in self ._iter_vals (col_data )]
@@ -324,25 +349,29 @@ def numbers_are_round(var, col_data):
324349 # in metas which are transformed to dense below
325350 col_data = self ._to_column (col_data , False , dtype = object )
326351 elif tpe == ContinuousVariable and type (orig_var ) == DiscreteVariable :
327- var = tpe .make (name )
352+ var = tpe .make (new_name )
328353 if may_be_numeric :
329354 col_data = [np .nan if self ._is_missing (x ) else float (orig_var .values [int (x )])
330355 for x in self ._iter_vals (col_data )]
331356 col_data = self ._to_column (col_data , is_sparse )
332357 else :
333- var = tpe (name )
358+ var = tpe (new_name )
334359 places [place ].append (var )
335360 cols [place ].append (col_data )
336361
337362 # merge columns for X, Y and metas
338363 feats = cols [Place .feature ]
339- X = self ._merge (feats ) if len ( feats ) else np .empty ((len (data ), 0 ))
364+ X = self ._merge (feats ) if feats else np .empty ((len (data ), 0 ))
340365 Y = self ._merge (cols [Place .class_var ], force_dense = True )
341366 m = self ._merge (cols [Place .meta ], force_dense = True )
342367 domain = Domain (* places )
343- return domain , [X , Y , m ]
368+ if deduplicate :
369+ return domain , [X , Y , m ], renamed
370+ else :
371+ return domain , [X , Y , m ]
344372
345- def _get_column (self , data , source_var , source_place ):
373+ @staticmethod
374+ def _get_column (data , source_var , source_place ):
346375 """ Extract column from data and preserve sparsity. """
347376 if source_place == Place .meta :
348377 col_data = data [:, source_var ].metas
0 commit comments