<gh_stars>0
from typing import Iterable, Optional, Union
import datetime
import numpy
from .general import matching_indices


class Observations:
    def __init__(
        self,
        observation_values: Iterable,
        observation_timstamps: Optional[
            Iterable[Union[datetime.datetime, datetime.date, str]]
        ] = None,
    ):

        self.observation_timstamps = [
            str(interval) for interval in observation_timstamps or []
        ]
        self.observation_values = observation_values
        # moving range series
        self.mR_series = [
            abs(self.observation_values[i + 1] - self.observation_values[i])
            for i in range(len(self.observation_values) - 1)
        ]

    def moving_range_mean(self):
        return numpy.nanmean(self.mR_series)

    def moving_range_sigma(self):
        # mean x 3.267 = mean + 3 x sigma => sigma = (mean x 2.267) / 3
        mr_mean = self.moving_range_mean()
        sigma = (mr_mean * 2.267) / 3
        return sigma

    def moving_range_standard_deviation(self, number=1):
        mr_mean = self.moving_range_mean()
        sigma = self.moving_range_sigma()
        std_dev = mr_mean + (sigma * number)
        if std_dev < 0:
            std_dev = 0
        return std_dev

    # puts mRs into a bin corresponding to their displacement from the mean in standard deviations
    def moving_range_bins(self):
        bins = []
        mr_mean = self.moving_range_mean()
        sigma = self.moving_range_sigma()
        for value in range(len(self.mR_series)):
            if sigma == 0:
                bin = 0
            else:
                bin = binner((self.value_series[value] - mr_mean) / sigma)
            bins.append(bin)
        return bins

    # returns an array with items of 1 (increase), 0 (stable) or -1 (reduction)
    def moving_range_direction(self):
        directions = []
        for i in range(len(self.mR_series) - 1):
            delta = self.mR_series[i + 1] - self.mR_series[i]
            if delta == 0:
                direction = 0
            else:
                direction = delta / abs(delta)
            directions.append(direction)
        return directions

    def moving_range_sigma_line(self, number=1):
        value = self.moving_range_standard_deviation(number)
        return [value] * len(self.value_series)

    def individuals_mean(self):
        return numpy.nanmean(self.observation_values)

    def individuals_sigma(self):
        mr_mean = self.moving_range_mean()
        sigma = (2.66 * mr_mean) / 3
        return sigma

    def individuals_standard_deviation(self, number=1):
        i_mean = self.individuals_mean()
        sigma = self.individuals_sigma()
        std_dev = i_mean + (sigma * number)
        return std_dev

    # puts observations into a bin corresponding to their displacement from the mean in standard deviations
    def individuals_bins(self):
        bins = []
        i_mean = self.individuals_mean()
        sigma = self.individuals_sigma()
        for value in range(len(self.observation_values)):
            if sigma == 0:
                bin = 0
            else:
                bin = binner((self.observation_values[value] - i_mean) / sigma)
            bins.append(bin)
        return bins

    # returns an array with items of 1 (increase), 0 (stable) or -1 (reduction)
    def individuals_direction(self):
        directions = []
        for i in range(len(self.observation_values) - 1):
            delta = self.observation_values[i + 1] - self.observation_values[i]
            if delta == 0:
                direction = 0
            else:
                direction = delta / abs(delta)
            directions.append(direction)
        return directions

    def describe(self):
        i_mean = self.individuals_mean()
        sigma = self.individuals_sigma()
        bins = self.individuals_bins()

        description = f"""
Summary
===========================
Number of Samples = {len(self.observation_values)}
Minimum = {min(self.observation_values)}
Maximum = {max(self.observation_values)}
---------------------------
Mean = {i_mean:.3f}
Items Above Mean = {len(matching_indices(lambda t: t > 0, bins))}
Items Below Mean = {len(matching_indices(lambda t: t < 0, bins))}
Average Change = {numpy.nanmean(self.mR_series):.3f}
---------------------------
 3+ Sigma = -\t\t ({len(matching_indices(lambda t: t > 3, bins))} items)
 3  Sigma = {self.individuals_standard_deviation(3):.3f}\t ({len(matching_indices(lambda t: t == 3, bins))} items)
 2  Sigma = {self.individuals_standard_deviation(2):.3f}\t ({len(matching_indices(lambda t: t == 2, bins))} items)
 1  Sigma = {self.individuals_standard_deviation(1):.3f}\t ({len(matching_indices(lambda t: t == 1, bins))} items)
 0  Sigma = {self.individuals_standard_deviation(0):.3f}\t ({len(matching_indices(lambda t: t == i_mean, self.observation_values))} items)
-1  Sigma = {self.individuals_standard_deviation(1):.3f}\t ({len(matching_indices(lambda t: t == -1, bins))} items)
-2  Sigma = {self.individuals_standard_deviation(2):.3f}\t ({len(matching_indices(lambda t: t == -2, bins))} items)
-3  Sigma = {self.individuals_standard_deviation(3):.3f}\t ({len(matching_indices(lambda t: t == -3, bins))} items)
-3+ Sigma = -\t\t ({len(matching_indices(lambda t: t < -3, bins))} items)
"""
        print(description)

    def individuals_sigma_line(self, number=1):
        value = self.individuals_standard_deviation(number)
        return [value] * len(self.observation_values)


def binner(number):
    result = number // 1
    if result >= 0:
        result = result + 1
    return result

from typing import Iterable, Optional, Union
import datetime
import numpy
from .general import matching_indices


class Observations:
    def __init__(
        self,
        observation_values: Iterable,
        observation_timstamps: Optional[
            Iterable[Union[datetime.datetime, datetime.date, str]]
        ] = None,
    ):

        self.observation_timstamps = [
            str(interval) for interval in observation_timstamps or []
        ]
        self.observation_values = observation_values
        # moving range series
        self.mR_series = [
            abs(self.observation_values[i + 1] - self.observation_values[i])
            for i in range(len(self.observation_values) - 1)
        ]

    def moving_range_mean(self):
        return numpy.nanmean(self.mR_series)

    def moving_range_sigma(self):
        # mean x 3.267 = mean + 3 x sigma => sigma = (mean x 2.267) / 3
        mr_mean = self.moving_range_mean()
        sigma = (mr_mean * 2.267) / 3
        return sigma

    def moving_range_standard_deviation(self, number=1):
        mr_mean = self.moving_range_mean()
        sigma = self.moving_range_sigma()
        std_dev = mr_mean + (sigma * number)
        if std_dev < 0:
            std_dev = 0
        return std_dev

    # puts mRs into a bin corresponding to their displacement from the mean in standard deviations
    def moving_range_bins(self):
        bins = []
        mr_mean = self.moving_range_mean()
        sigma = self.moving_range_sigma()
        for value in range(len(self.mR_series)):
            if sigma == 0:
                bin = 0
            else:
                bin = binner((self.value_series[value] - mr_mean) / sigma)
            bins.append(bin)
        return bins

    # returns an array with items of 1 (increase), 0 (stable) or -1 (reduction)
    def moving_range_direction(self):
        directions = []
        for i in range(len(self.mR_series) - 1):
            delta = self.mR_series[i + 1] - self.mR_series[i]
            if delta == 0:
                direction = 0
            else:
                direction = delta / abs(delta)
            directions.append(direction)
        return directions

    def moving_range_sigma_line(self, number=1):
        value = self.moving_range_standard_deviation(number)
        return [value] * len(self.value_series)

    def individuals_mean(self):
        return numpy.nanmean(self.observation_values)

    def individuals_sigma(self):
        mr_mean = self.moving_range_mean()
        sigma = (2.66 * mr_mean) / 3
        return sigma

    def individuals_standard_deviation(self, number=1):
        i_mean = self.individuals_mean()
        sigma = self.individuals_sigma()
        std_dev = i_mean + (sigma * number)
        return std_dev

    # puts observations into a bin corresponding to their displacement from the mean in standard deviations
    def individuals_bins(self):
        bins = []
        i_mean = self.individuals_mean()
        sigma = self.individuals_sigma()
        for value in range(len(self.observation_values)):
            if sigma == 0:
                bin = 0
            else:
                bin = binner((self.observation_values[value] - i_mean) / sigma)
            bins.append(bin)
        return bins

    # returns an array with items of 1 (increase), 0 (stable) or -1 (reduction)
    def individuals_direction(self):
        directions = []
        for i in range(len(self.observation_values) - 1):
            delta = self.observation_values[i + 1] - self.observation_values[i]
            if delta == 0:
                direction = 0
            else:
                direction = delta / abs(delta)
            directions.append(direction)
        return directions

    def describe(self):
        i_mean = self.individuals_mean()
        sigma = self.individuals_sigma()
        bins = self.individuals_bins()

        description = f"""
Summary
===========================
Number of Samples = {len(self.observation_values)}
Minimum = {min(self.observation_values)}
Maximum = {max(self.observation_values)}
---------------------------
Mean = {i_mean:.3f}
Items Above Mean = {len(matching_indices(lambda t: t > 0, bins))}
Items Below Mean = {len(matching_indices(lambda t: t < 0, bins))}
Average Change = {numpy.nanmean(self.mR_series):.3f}
---------------------------
 3+ Sigma = -\t\t ({len(matching_indices(lambda t: t > 3, bins))} items)
 3  Sigma = {self.individuals_standard_deviation(3):.3f}\t ({len(matching_indices(lambda t: t == 3, bins))} items)
 2  Sigma = {self.individuals_standard_deviation(2):.3f}\t ({len(matching_indices(lambda t: t == 2, bins))} items)
 1  Sigma = {self.individuals_standard_deviation(1):.3f}\t ({len(matching_indices(lambda t: t == 1, bins))} items)
 0  Sigma = {self.individuals_standard_deviation(0):.3f}\t ({len(matching_indices(lambda t: t == i_mean, self.observation_values))} items)
-1  Sigma = {self.individuals_standard_deviation(1):.3f}\t ({len(matching_indices(lambda t: t == -1, bins))} items)
-2  Sigma = {self.individuals_standard_deviation(2):.3f}\t ({len(matching_indices(lambda t: t == -2, bins))} items)
-3  Sigma = {self.individuals_standard_deviation(3):.3f}\t ({len(matching_indices(lambda t: t == -3, bins))} items)
-3+ Sigma = -\t\t ({len(matching_indices(lambda t: t < -3, bins))} items)
"""
        print(description)

    def individuals_sigma_line(self, number=1):
        value = self.individuals_standard_deviation(number)
        return [value] * len(self.observation_values)


def binner(number):
    result = number // 1
    if result >= 0:
        result = result + 1
    return result
# moving range series # mean x 3.267 = mean + 3 x sigma => sigma = (mean x 2.267) / 3 # puts mRs into a bin corresponding to their displacement from the mean in standard deviations # returns an array with items of 1 (increase), 0 (stable) or -1 (reduction) # puts observations into a bin corresponding to their displacement from the mean in standard deviations # returns an array with items of 1 (increase), 0 (stable) or -1 (reduction) Summary =========================== Number of Samples = {len(self.observation_values)} Minimum = {min(self.observation_values)} Maximum = {max(self.observation_values)} --------------------------- Mean = {i_mean:.3f} Items Above Mean = {len(matching_indices(lambda t: t > 0, bins))} Items Below Mean = {len(matching_indices(lambda t: t < 0, bins))} Average Change = {numpy.nanmean(self.mR_series):.3f} ---------------------------  3+ Sigma = -\t\t ({len(matching_indices(lambda t: t > 3, bins))} items)  3  Sigma = {self.individuals_standard_deviation(3):.3f}\t ({len(matching_indices(lambda t: t == 3, bins))} items)  2  Sigma = {self.individuals_standard_deviation(2):.3f}\t ({len(matching_indices(lambda t: t == 2, bins))} items)  1  Sigma = {self.individuals_standard_deviation(1):.3f}\t ({len(matching_indices(lambda t: t == 1, bins))} items)  0  Sigma = {self.individuals_standard_deviation(0):.3f}\t ({len(matching_indices(lambda t: t == i_mean, self.observation_values))} items) -1  Sigma = {self.individuals_standard_deviation(1):.3f}\t ({len(matching_indices(lambda t: t == -1, bins))} items) -2  Sigma = {self.individuals_standard_deviation(2):.3f}\t ({len(matching_indices(lambda t: t == -2, bins))} items) -3  Sigma = {self.individuals_standard_deviation(3):.3f}\t ({len(matching_indices(lambda t: t == -3, bins))} items) -3+ Sigma = -\t\t ({len(matching_indices(lambda t: t < -3, bins))} items)