import random import warnings import matplotlib.pyplot as plt from collections import OrderedDict import numpy as np from cached_property import cached_property from experiment.trend_analysis.mann_kendall_test import mann_kendall_test from experiment.trend_analysis.abstract_score import MannKendall class AbstractUnivariateTest(object): SIGNIFICATIVE = 'significative' # 5 possible types of trends NO_TREND = 'no trend' ALL_TREND = 'all trend' POSITIVE_TREND = 'positive trend' NEGATIVE_TREND = 'negative trend' SIGNIFICATIVE_ALL_TREND = SIGNIFICATIVE + ' ' + ALL_TREND SIGNIFICATIVE_POSITIVE_TREND = SIGNIFICATIVE + ' ' + POSITIVE_TREND SIGNIFICATIVE_NEGATIVE_TREND = SIGNIFICATIVE + ' ' + NEGATIVE_TREND NON_SIGNIFICATIVE_TREND = 'non ' + SIGNIFICATIVE + ' trend' # this is the most common level of significance SIGNIFICANCE_LEVEL = 0.05 def __init__(self, years, maxima, starting_year): self.years = years self.maxima = maxima self.starting_year = starting_year assert len(self.years) == len(self.maxima) @cached_property def idx_for_starting_year(self): return self.years.index(self.starting_year) @property def years_after_starting_year(self): return self.years[self.idx_for_starting_year:] @property def maxima_after_starting_year(self): return self.maxima[self.idx_for_starting_year:] @classmethod def real_trend_types(cls): return [cls.POSITIVE_TREND, cls.NEGATIVE_TREND, cls.SIGNIFICATIVE_POSITIVE_TREND, cls.SIGNIFICATIVE_NEGATIVE_TREND, cls.NO_TREND] @classmethod def three_main_trend_types(cls): return [cls.SIGNIFICATIVE_NEGATIVE_TREND, cls.NON_SIGNIFICATIVE_TREND, cls.SIGNIFICATIVE_POSITIVE_TREND] @classmethod def display_trend_type_to_style(cls): d = OrderedDict() d[cls.POSITIVE_TREND] = 'lightgreen-' d[cls.NEGATIVE_TREND] = 'lightcoral-' d[cls.ALL_TREND] = 'k-' d[cls.NON_SIGNIFICATIVE_TREND] = 'b-' # d[cls.SIGNIFICATIVE_ALL_TREND] = 'k-' d[cls.SIGNIFICATIVE_POSITIVE_TREND] = 'g-' d[cls.SIGNIFICATIVE_NEGATIVE_TREND] = 'r-' # d[cls.NO_TREND] = 'k--' return d @classmethod def get_display_trend_type(cls, real_trend_type): if cls.SIGNIFICATIVE in real_trend_type: return real_trend_type else: return cls.NON_SIGNIFICATIVE_TREND @classmethod def get_real_trend_types(cls, display_trend_type): if display_trend_type is cls.ALL_TREND: return cls.real_trend_types() elif display_trend_type is cls.SIGNIFICATIVE_ALL_TREND: return [cls.SIGNIFICATIVE_POSITIVE_TREND, cls.SIGNIFICATIVE_NEGATIVE_TREND] if display_trend_type is cls.POSITIVE_TREND: return [cls.POSITIVE_TREND, cls.SIGNIFICATIVE_POSITIVE_TREND] elif display_trend_type is cls.NEGATIVE_TREND: return [cls.NEGATIVE_TREND, cls.SIGNIFICATIVE_NEGATIVE_TREND] elif display_trend_type is cls.NON_SIGNIFICATIVE_TREND: return [cls.POSITIVE_TREND, cls.NEGATIVE_TREND, cls.NO_TREND] else: return [display_trend_type] @classmethod def get_cmap_from_trend_type(cls, trend_type): if 'positive' in trend_type: return plt.cm.Greens elif 'negative' in trend_type: return plt.cm.Reds elif 'non' in trend_type: return plt.cm.Blues else: return plt.cm.binary @property def n(self): return len(self.years) @property def test_trend_strength(self): return 0.0 @property def test_trend_type(self) -> str: test_sign = self.test_sign assert test_sign in [-1, 0, 1] if test_sign == 0: trend_type = self.NO_TREND else: trend_type = self.POSITIVE_TREND if test_sign > 0 else self.NEGATIVE_TREND if self.is_significant: trend_type = self.SIGNIFICATIVE + ' ' + trend_type assert trend_type in self.real_trend_types(), trend_type return trend_type @property def test_sign(self) -> int: raise NotImplementedError @property def is_significant(self) -> bool: raise NotImplementedError class ExampleRandomTrendTest(AbstractUnivariateTest): @property def test_sign(self) -> int: return random.randint(0, 2) - 1 @property def is_significant(self) -> bool: return random.randint(1, 10) == 10 class WarningScoreValue(Warning): pass