Total Complexity | 52 |
Total Lines | 234 |
Duplicated Lines | 0 % |
Complex classes like Orange.data.DiscreteVariable often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | from numbers import Real, Integral |
||
314 | class DiscreteVariable(Variable): |
||
315 | """ |
||
316 | Descriptor for symbolic, discrete variables. Values of discrete variables |
||
317 | are stored as floats; the numbers corresponds to indices in the list of |
||
318 | values. |
||
319 | |||
320 | .. attribute:: values |
||
321 | |||
322 | A list of variable's values. |
||
323 | |||
324 | .. attribute:: ordered |
||
325 | |||
326 | Some algorithms (and, in particular, visualizations) may |
||
327 | sometime reorder the values of the variable, e.g. alphabetically. |
||
328 | This flag hints that the given order of values is "natural" |
||
329 | (e.g. "small", "middle", "large") and should not be changed. |
||
330 | |||
331 | .. attribute:: base_value |
||
332 | |||
333 | The index of the base value, or -1 if there is none. The base value is |
||
334 | used in some methods like, for instance, when creating dummy variables |
||
335 | for regression. |
||
336 | """ |
||
337 | _all_vars = collections.defaultdict(list) |
||
338 | presorted_values = [] |
||
339 | |||
340 | def __init__(self, name="", values=(), ordered=False, base_value=-1, compute_value=None): |
||
341 | """ Construct a discrete variable descriptor with the given values. """ |
||
342 | super().__init__(name, compute_value) |
||
343 | self.ordered = ordered |
||
344 | self.values = list(values) |
||
345 | self.base_value = base_value |
||
346 | |||
347 | def __repr__(self): |
||
348 | """ |
||
349 | Give a string representation of the variable, for instance, |
||
350 | `"DiscreteVariable('Gender', values=['male', 'female'])"`. |
||
351 | """ |
||
352 | args = "values=[{}]".format( |
||
353 | ", ".join([repr(x) for x in self.values[:5]] + |
||
354 | ["..."] * (len(self.values) > 5))) |
||
355 | if self.ordered: |
||
356 | args += ", ordered=True" |
||
357 | if self.base_value >= 0: |
||
358 | args += ", base_value={}".format(self.base_value) |
||
359 | return "{}('{}', {})".format(self.__class__.__name__, self.name, args) |
||
360 | |||
361 | @staticmethod |
||
362 | def is_primitive(): |
||
363 | """ Return `True`: discrete variables are stored as floats. """ |
||
364 | return True |
||
365 | |||
366 | def to_val(self, s): |
||
367 | """ |
||
368 | Convert the given argument to a value of the variable (`float`). |
||
369 | If the argument is numeric, its value is returned without checking |
||
370 | whether it is integer and within bounds. `Unknown` is returned if the |
||
371 | argument is one of the representations for unknown values. Otherwise, |
||
372 | the argument must be a string and the method returns its index in |
||
373 | :obj:`values`. |
||
374 | |||
375 | :param s: values, represented as a number, string or `None` |
||
376 | :rtype: float |
||
377 | """ |
||
378 | if s is None: |
||
379 | return ValueUnknown |
||
380 | |||
381 | if isinstance(s, Integral): |
||
382 | return s |
||
383 | if isinstance(s, Real): |
||
384 | return s if isnan(s) else floor(s + 0.25) |
||
385 | if s in self.unknown_str: |
||
386 | return ValueUnknown |
||
387 | if not isinstance(s, str): |
||
388 | raise TypeError('Cannot convert {} to value of "{}"'.format( |
||
389 | type(s).__name__, self.name)) |
||
390 | return self.values.index(s) |
||
391 | |||
392 | def add_value(self, s): |
||
393 | """ Add a value `s` to the list of values. |
||
394 | """ |
||
395 | self.values.append(s) |
||
396 | |||
397 | def val_from_str_add(self, s): |
||
398 | """ |
||
399 | Similar to :obj:`to_val`, except that it accepts only strings and that |
||
400 | it adds the value to the list if it does not exist yet. |
||
401 | |||
402 | :param s: symbolic representation of the value |
||
403 | :type s: str |
||
404 | :rtype: float |
||
405 | """ |
||
406 | s = str(s) if s is not None else s |
||
407 | try: |
||
408 | return ValueUnknown if s in self.unknown_str \ |
||
409 | else self.values.index(s) |
||
410 | except ValueError: |
||
411 | self.add_value(s) |
||
412 | return len(self.values) - 1 |
||
413 | |||
414 | def repr_val(self, val): |
||
415 | """ |
||
416 | Return a textual representation of the value (`self.values[int(val)]`) |
||
417 | or "?" if the value is unknown. |
||
418 | |||
419 | :param val: value |
||
420 | :type val: float (should be whole number) |
||
421 | :rtype: str |
||
422 | """ |
||
423 | if isnan(val): |
||
424 | return "?" |
||
425 | return '{}'.format(self.values[int(val)]) |
||
426 | |||
427 | str_val = repr_val |
||
428 | |||
429 | def __reduce__(self): |
||
430 | if not self.name: |
||
431 | raise PickleError("Variables without names cannot be pickled") |
||
432 | return make_variable, (self.__class__, self._compute_value, self.name, |
||
433 | self.values, self.ordered, self.base_value), \ |
||
434 | self.__dict__ |
||
435 | |||
436 | @classmethod |
||
437 | def make(cls, name, values=(), ordered=False, base_value=-1): |
||
438 | """ |
||
439 | Return a variable with the given name and other properties. The method |
||
440 | first looks for a compatible existing variable: the existing |
||
441 | variable must have the same name and both variables must have either |
||
442 | ordered or unordered values. If values are ordered, the order must be |
||
443 | compatible: all common values must have the same order. If values are |
||
444 | unordered, the existing variable must have at least one common value |
||
445 | with the new one, except when any of the two lists of values is empty. |
||
446 | |||
447 | If a compatible variable is find, it is returned, with missing values |
||
448 | appended to the end of the list. If there is no explicit order, the |
||
449 | values are ordered using :obj:`ordered_values`. Otherwise, it |
||
450 | constructs and returns a new variable descriptor. |
||
451 | |||
452 | :param name: the name of the variable |
||
453 | :type name: str |
||
454 | :param values: symbolic values for the variable |
||
455 | :type values: list |
||
456 | :param ordered: tells whether the order of values is fixed |
||
457 | :type ordered: bool |
||
458 | :param base_value: the index of the base value, or -1 if there is none |
||
459 | :type base_value: int |
||
460 | :returns: an existing compatible variable or `None` |
||
461 | """ |
||
462 | if not name: |
||
463 | raise ValueError("Variables without names cannot be stored or made") |
||
464 | var = cls._find_compatible( |
||
465 | name, values, ordered, base_value) |
||
466 | if var: |
||
467 | return var |
||
468 | if not ordered: |
||
469 | base_value_rep = base_value != -1 and values[base_value] |
||
470 | values = cls.ordered_values(values) |
||
471 | if base_value != -1: |
||
472 | base_value = values.index(base_value_rep) |
||
473 | return cls(name, values, ordered, base_value) |
||
474 | |||
475 | @classmethod |
||
476 | def _find_compatible(cls, name, values=(), ordered=False, base_value=-1): |
||
477 | """ |
||
478 | Return a compatible existing value, or `None` if there is None. |
||
479 | See :obj:`make` for details; this function differs by returning `None` |
||
480 | instead of constructing a new descriptor. (Method :obj:`make` calls |
||
481 | this function.) |
||
482 | |||
483 | :param name: the name of the variable |
||
484 | :type name: str |
||
485 | :param values: symbolic values for the variable |
||
486 | :type values: list |
||
487 | :param ordered: tells whether the order of values is fixed |
||
488 | :type ordered: bool |
||
489 | :param base_value: the index of the base value, or -1 if there is none |
||
490 | :type base_value: int |
||
491 | :returns: an existing compatible variable or `None` |
||
492 | """ |
||
493 | base_rep = base_value != -1 and values[base_value] |
||
494 | existing = cls._all_vars.get(name) |
||
495 | if existing is None: |
||
496 | return None |
||
497 | if not ordered: |
||
498 | values = cls.ordered_values(values) |
||
499 | for var in existing: |
||
500 | if (var.ordered != ordered or |
||
501 | var.base_value != -1 |
||
502 | and var.values[var.base_value] != base_rep): |
||
503 | continue |
||
504 | if not values: |
||
505 | break # we have the variable - any existing values are OK |
||
506 | if not set(var.values) & set(values): |
||
507 | continue # empty intersection of values; not compatible |
||
508 | if ordered: |
||
509 | i = 0 |
||
510 | for val in var.values: |
||
511 | if values[i] == val: |
||
512 | i += 1 |
||
513 | if i == len(values): |
||
514 | break # we have all the values |
||
515 | else: # we have some remaining values: check them, add them |
||
516 | if set(values[i:]) & set(var.values): |
||
517 | continue # next var in existing |
||
518 | for val in values[i:]: |
||
519 | var.add_value(val) |
||
520 | break # we have the variable |
||
521 | else: # not ordered |
||
522 | vv = set(var.values) |
||
523 | for val in values: |
||
524 | if val not in vv: |
||
525 | var.add_value(val) |
||
526 | break # we have the variable |
||
527 | else: |
||
528 | return None |
||
529 | if base_value != -1 and var.base_value == -1: |
||
530 | var.base_value = var.values.index(base_rep) |
||
531 | return var |
||
532 | |||
533 | @staticmethod |
||
534 | def ordered_values(values): |
||
535 | """ |
||
536 | Return a sorted list of values. If there exists a prescribed order for |
||
537 | such set of values, it is returned. Otherwise, values are sorted |
||
538 | alphabetically. |
||
539 | """ |
||
540 | for presorted in DiscreteVariable.presorted_values: |
||
541 | if values == set(presorted): |
||
542 | return presorted |
||
543 | return sorted(values) |
||
544 | |||
545 | def copy(self, compute_value=None): |
||
546 | return DiscreteVariable(self.name, self.values, self.ordered, |
||
547 | self.base_value, compute_value) |
||
548 | |||
589 |
This can be caused by one of the following:
1. Missing Dependencies
This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.
2. Missing __init__.py files
This error could also result from missing
__init__.py
files in your module folders. Make sure that you place one file in each sub-folder.