# -*- coding: utf-8 -*-
import logging
logger = logging.getLogger("phuzzy")
import sys
is_py2 = sys.version_info.major == 2
import numpy as np
import pandas as pd
import copy
from scipy.integrate import cumtrapz
import scipy.stats
[docs]class FuzzyNumber(object):
"""convex fuzzy number"""
[docs] def __init__(self, **kwargs):
"""base fuzzy number
:param kwargs:
"""
self.name = kwargs.get("name", "x")
self._df = pd.DataFrame(columns=["alpha", "low", "high"])
self._number_of_alpha_levels = kwargs.get("number_of_alpha_levels", 11)
self.df = kwargs.get("df")
[docs] def update(self, alpha0=None, alpha1=None, alpha_levels=None):
if alpha0 is None:
alpha0 = self.alpha0[["l", "r"]].values
if alpha1 is None:
alpha1 = self.alpha1[["l", "r"]].values
if alpha_levels is None:
alpha_levels = self.number_of_alpha_levels
alpha1l = [max(alpha0[0], alpha1[0]), min(alpha0[1], alpha1[1])]
print("!", alpha0, alpha1, alpha1l)
self.discretize(alpha0=alpha0, alpha1=alpha1l, alpha_levels=self.number_of_alpha_levels)
[docs] def copy(self):
"""return a copy
:return: copy of fuzzy number
"""
return copy.deepcopy(self)
def _get_number_of_alpha_levels(self):
"""number of alpha levels
:rtype: int
:return: number of alpha levels
"""
return self._number_of_alpha_levels
def _set_number_of_alpha_levels(self, value):
self.convert_df(alpha_levels=int(value))
# self._number_of_alpha_levels = int(value)
number_of_alpha_levels = property(fget=_get_number_of_alpha_levels, fset=_set_number_of_alpha_levels,
doc="number of alpha levels")
def _get_df(self):
"""returns alpha levels
:rtype: pandas.Dataframe
:return: alpha level dataframe
"""
return self._df
def _set_df(self, value):
self._df = value
df = property(fget=_get_df, fset=_set_df, doc="number of alpha levels")
[docs] def discretize(self, alpha0, alpha1, alpha_levels):
"""discretize shape function
:param alpha0: range at alpha=0
:param alpha1: range at alpha=1
:param alpha_levels: number of alpha levels
:return: None
"""
raise NotImplementedError
[docs] def alpha(self, x):
"""get alpha from x"""
df = pd.concat([self.df[["l", "alpha"]].rename(columns={"l":"x"}), self.df[["alpha", "r"]].rename(columns={"r":"x"})]).sort_values(by=["x", "alpha"])
df = df.drop_duplicates()
return np.interp(x, df.x, df.alpha, left=0., right=0.)
[docs] def convert_df(self, alpha_levels=None, zero=0):
df = self.df.copy()
if alpha_levels is not None:
self._number_of_alpha_levels = int(alpha_levels)
df.sort_values(['alpha'], ascending=[True], inplace=True)
# print("!",df)
xs_l = df.l.values
xs_l[xs_l == 0] = zero
alphas_l = df["alpha"].values
xs_r = df.r.values[::-1]
xs_r[xs_r == 0] = zero
alphas_r = df["alpha"].values[::-1]
alphas_new = np.linspace(0., 1., int(self.number_of_alpha_levels))
xs_l_new = np.interp(alphas_new, alphas_l, xs_l)
xs_r_new = np.interp(alphas_new, alphas_r[::-1], xs_r[::-1])
#
# print((xs_l, alphas_l, xs_l_new))
# print((xs_r, alphas_r, xs_r_new))
#
# print((np.vstack((alphas_new, xs_l_new, xs_r_new[::-1]))))
data = np.vstack((alphas_new, xs_l_new, xs_r_new)).T
self.df = pd.DataFrame(columns=["alpha", "l", "r"], data=data, dtype=np.float)
def _unify(self, other):
"""equalize number of alpha levels
:param other:
:return: (fuzzy_number_1, fuzzy_number_2)
"""
old0 = copy.deepcopy(self)
old1 = copy.deepcopy(other)
levels = max(len(old0.df), len(old1.df))
old0.convert_df(levels)
old1.convert_df(levels)
return old0, old1
@staticmethod
def _get_cls(s, o):
"""get class after application of an operation
:param s:
:param o:
:return: cls
"""
if isinstance(s, (Uniform, Trapezoid, Triangle)) and isinstance(o, (int, float)):
return s.__class__
elif isinstance(o, (int, float)):
return FuzzyNumber
elif isinstance(s, Uniform) and isinstance(o, Uniform):
return Uniform
elif isinstance(s, (Triangle, Uniform)) and isinstance(o, (Triangle, Uniform)):
return Triangle
elif isinstance(s, (Triangle, Trapezoid, Uniform)) and isinstance(o, (Triangle, Trapezoid, Uniform)):
return Trapezoid
else:
return FuzzyNumber
[docs] def has_zero(self):
"""is zero in range
:rtype: bool
:return: True od False
"""
if self.min() <= 0 and self.max() >= 0:
return True
else:
return False
[docs] def __add__(self, other):
"""adds a fuzzy number
:param other: phuzzy.FuzzyNumber
:return: fuzzy number
"""
if isinstance(other, (int, float)):
cls = self.__class__
df = self.df.copy()
df.update(df[["l", "r"]] + other)
new = cls(alpha0=df.iloc[0][["l", "r"]].values,
alpha1=df.iloc[-1][["l", "r"]].values,
number_of_alpha_levels=len(df))
new.df = df
new.name = "{}+{}".format(self.name, other)
else:
old0, old1 = self._unify(other)
quotients = np.vstack([old0.df.l + old1.df.l,
old0.df.l + old1.df.r,
old0.df.r + old1.df.l,
old0.df.r + old1.df.r])
df = pd.DataFrame.from_dict({"alpha": old0.df.alpha,
"l" : np.nanmin(quotients, axis=0),
"r" : np.nanmax(quotients, axis=0)})
cls = self._get_cls(self, other)
new = cls(alpha0=df.iloc[0][["l", "r"]].values,
alpha1=df.iloc[-1][["l", "r"]].values,
number_of_alpha_levels=len(df))
new.df = df
new.name = "{}+{}".format(self.name, other.name)
return new
[docs] def __radd__(self, other):
return self.__add__(other)
[docs] def __sub__(self, other):
"""substract a fuzzy number
:param other: phuzzy.FuzzyNumber
:return: fuzzy number
"""
if isinstance(other, (int, float)):
cls = self.__class__
df = self.df.copy()
df.update(df[["l", "r"]] - other)
new = cls(alpha0=df.iloc[0][["l", "r"]].values,
alpha1=df.iloc[-1][["l", "r"]].values,
number_of_alpha_levels=len(df))
new.df = df
new.name = "{}-{}".format(self.name, other)
else:
cls = self._get_cls(self, other)
old0, old1 = self._unify(other)
quotients = np.vstack([old0.df.l - old1.df.l,
old0.df.l - old1.df.r,
old0.df.r - old1.df.l,
old0.df.r - old1.df.r])
df = pd.DataFrame.from_dict({"alpha": old0.df.alpha,
"l" : np.nanmin(quotients, axis=0),
"r" : np.nanmax(quotients, axis=0)})
new = cls(alpha0=df.iloc[0][["l", "r"]].values,
alpha1=df.iloc[-1][["l", "r"]].values,
number_of_alpha_levels=len(df))
new.df = df
new.name = "{}-{}".format(self.name, other.name)
return new
[docs] def __rsub__(self, other):
return self.__sub__(other)
[docs] def __mul__(self, other):
"""multiply with a fuzzy number
:param other: phuzzy.FuzzyNumber
:return: fuzzy number
"""
# fixme: zeros, infs, nans
cls = self._get_cls(self, other)
if isinstance(other, (int, float)):
df = self.df.copy()
df.update(df[["l", "r"]] * other)
new = cls(alpha0=df.iloc[0][["l", "r"]].values,
alpha1=df.iloc[-1][["l", "r"]].values,
number_of_alpha_levels=len(df))
new.df = df
new.name = "{}*{}".format(self.name, other)
else:
old0, old1 = self._unify(other)
quotients = np.vstack([old0.df.l * old1.df.l,
old0.df.l * old1.df.r,
old0.df.r * old1.df.l,
old0.df.r * old1.df.r])
df = pd.DataFrame.from_dict({"alpha": old0.df.alpha,
"l" : np.nanmin(quotients, axis=0),
"r" : np.nanmax(quotients, axis=0)})
cls = self._get_cls(self, other)
new = cls(alpha0=df.iloc[0][["l", "r"]].values,
alpha1=df.iloc[-1][["l", "r"]].values,
number_of_alpha_levels=len(df))
new.df = df
new.name = "{}*{}".format(self.name, other.name)
return new
[docs] def __rmul__(self, other):
return self.__mul__(other)
[docs] def __truediv__(self, other):
"""divide by a fuzzy number
:param other: phuzzy.FuzzyNumber
:return: fuzzy number
"""
# fixme: zeros, infs, nans
cls = self._get_cls(self, other)
if isinstance(other, (int, float)):
df = self.df.copy()
df.update(df[["l", "r"]] / other)
new = cls(alpha0=df.iloc[0][["l", "r"]].values,
alpha1=df.iloc[-1][["l", "r"]].values,
number_of_alpha_levels=len(df))
new.df = df
new.name = "{}/{}".format(self.name, other)
else:
old0, old1 = self._unify(other)
quotients = np.vstack([old0.df.l / old1.df.l,
old0.df.l / old1.df.r,
old0.df.r / old1.df.l,
old0.df.r / old1.df.r])
df = pd.DataFrame.from_dict({"alpha": old0.df.alpha,
"l" : np.nanmin(quotients, axis=0),
"r" : np.nanmax(quotients, axis=0)})
cls = self._get_cls(self, other)
new = cls(alpha0=df.iloc[0][["l", "r"]].values,
alpha1=df.iloc[-1][["l", "r"]].values,
number_of_alpha_levels=len(df))
new.df = df
new.name = "{}/{}".format(self.name, other.name)
return new
__div__ = __truediv__
# __floordiv__ = __truediv__
[docs] def __rdiv__(self, other):
return self.__div__(other)
[docs] def __pow__(self, other):
"""apply power of a fuzzy number
:param other: phuzzy.FuzzyNumber
:return: fuzzy number
"""
# fixme: zeros, infs, nans
cls = FuzzyNumber # self._get_cls(self, other)
if isinstance(other, (int, float)):
if isinstance(self, Uniform):
cls = Uniform
if self.has_zero() is False:
df = self.df.copy()
df.update(df[["l", "r"]] ** other)
new = cls(alpha0=df.iloc[0][["l", "r"]].values,
alpha1=df.iloc[-1][["l", "r"]].values,
number_of_alpha_levels=len(df))
else:
df = self.df.copy()
x = self._disretize_range()
_t = pd.DataFrame({"x": x, "res": x ** other})
for i, row in df.iterrows():
r = _t[(_t.x >= row.l) & (_t.x <= row.r)]
l = np.nanmin(r.res.values)
r = np.nanmax(r.res.values)
df.loc[i, "l"] = l
df.loc[i, "r"] = r
new = cls(alpha0=df.iloc[0][["l", "r"]].values,
alpha1=df.iloc[-1][["l", "r"]].values,
number_of_alpha_levels=len(df))
new.df = df
new.name = "{}^{}".format(self.name, other)
else:
if self.has_zero() is False and other.has_zero() is False:
old0, old1 = self._unify(other)
quotients = np.vstack([old0.df.l ** old1.df.l,
old0.df.l ** old1.df.r,
old0.df.r ** old1.df.l,
old0.df.r ** old1.df.r])
df = pd.DataFrame.from_dict({"alpha": old0.df.alpha,
"l" : np.nanmin(quotients, axis=0),
"r" : np.nanmax(quotients, axis=0)})
new = cls(alpha0=df.iloc[0][["l", "r"]].values,
alpha1=df.iloc[-1][["l", "r"]].values,
number_of_alpha_levels=len(df))
else:
# FIXME:
old0, old1 = self._unify(other)
df = old1.df.copy()
x = old0._disretize_range()
# print("old1.min", old1.min())
_t = pd.DataFrame({"x" : x,
"res_l": x ** old1.min(),
"res_r": x ** old1.max()
})
if old1.has_zero is True:
_t["res_0"] = 1
else:
_t["res_0"] = np.nan
for i, row in old0.df.iterrows():
df.loc[i, "l"] = np.nanmin(
_t[(_t.x >= row.l) & (_t.x <= row.r)][["res_l", "res_r", "res_0"]].values)
df.loc[i, "r"] = np.nanmax(
_t[(_t.x >= row.l) & (_t.x <= row.r)][["res_l", "res_r", "res_0"]].values)
new = cls(alpha0=df.iloc[0][["l", "r"]].values,
alpha1=df.iloc[-1][["l", "r"]].values,
number_of_alpha_levels=len(df))
new.df = df
new.name = "{}^{}".format(self.name, other)
new.make_convex()
return new
[docs] def __rpow__(self, other):
"""apply exponent of a fuzzy number
:param other: phuzzy.FuzzyNumber
:return: fuzzy number
"""
# fixme: zeros, infs, nans
cls = FuzzyNumber # self._get_cls(self, other)
if isinstance(other, (int, float)):
if isinstance(self, Uniform):
cls = Uniform
df = self.df.copy()
df.update(other ** df[["l", "r"]])
new = cls(alpha0=df.iloc[0][["l", "r"]].values,
alpha1=df.iloc[-1][["l", "r"]].values,
number_of_alpha_levels=len(df))
new.df = df
new.name = "{}^{}".format(self.name, other)
else:
old0, old1 = self._unify(other)
quotients = np.vstack([old0.df.l ** old1.df.l,
old0.df.l ** old1.df.r,
old0.df.r ** old1.df.l,
old0.df.r ** old1.df.r])
df = pd.DataFrame.from_dict({"alpha": old0.df.alpha,
"l" : np.nanmin(quotients, axis=0),
"r" : np.nanmax(quotients, axis=0)})
new = cls(alpha0=df.iloc[0][["l", "r"]].values,
alpha1=df.iloc[-1][["l", "r"]].values,
number_of_alpha_levels=len(df))
new.df = df
new.name = "{}^{}".format(self.name, other.name)
new.make_convex()
return new
[docs] def __neg__(self):
"""apply unary neg operator to a fuzzy number
:param other: phuzzy.FuzzyNumber
:return: fuzzy number
"""
cls = self.__class__
quotients = np.vstack([-self.df.l, -self.df.r])
df = pd.DataFrame.from_dict({"alpha": self.df.alpha,
"l" : np.nanmin(quotients, axis=0),
"r" : np.nanmax(quotients, axis=0)})
new = cls(alpha0=df.iloc[0][["l", "r"]].values,
alpha1=df.iloc[-1][["l", "r"]].values,
number_of_alpha_levels=len(df))
new.df = df
new.name = "-{}".format(self.name)
new.make_convex()
return new
[docs] def __abs__(self):
"""apply abs operator to a fuzzy number
:param other: phuzzy.FuzzyNumber
:return: fuzzy number
"""
dfl0 = self.df.l.copy()
dfl0[dfl0 <= 0] = 0
dfr0 = self.df.r.copy()
dfr0[dfr0 <= 0] = 0
if ((self.df[["l", "r"]].values <= 0).all()) or ((self.df[["l", "r"]].values >= 0).all()):
cls = self.__class__
quotients = np.vstack([abs(self.df.l), abs(self.df.r)])
else:
cls = FuzzyNumber
quotients = np.vstack([dfl0, dfr0, abs(self.df.l), abs(self.df.r)])
df = pd.DataFrame.from_dict({"alpha": self.df.alpha,
"l" : np.nanmin(quotients, axis=0),
"r" : np.nanmax(quotients, axis=0)})
new = cls(alpha0=df.iloc[0][["l", "r"]].values,
alpha1=df.iloc[-1][["l", "r"]].values,
number_of_alpha_levels=len(df))
new.df = df
new.name = "|{}|".format(self.name)
new.make_convex()
return new
[docs] def __lt__(self, other):
"""operation <
:return: True or False
"""
if isinstance(other, (int, float)):
return self.max() < other
else:
return self.max() < other.min()
# def __le__(self, other):
# """operation <=
#
# :rtype: bool
# :return: True or False
# """
#
# if isinstance(other, (int, float)):
# return self.max() <= other
# else:
# return self.max() <= other.max()
[docs] def __eq__(self, other):
"""operation ==
:rtype: bool
:return: True or False
"""
if isinstance(other, (int, float)):
return False # (self.min() >= other) and (self.max() <= other)
elif other.__class__ is self.__class__:
return np.allclose(self.df.values, other.df.values)
else:
return NotImplemented
__hash__ = None # https://www.youtube.com/watch?v=T-TwcmT6Rcw 26:00
[docs] def __ne__(self, other):
"""operation !=
:rtype: bool
:return: True or False
"""
return not self.__eq__(other)
# def __ge__(self, other):
# """operation >=
#
# :rtype: bool
# :return: True or False
# """
#
# if isinstance(other, (int, float)):
# return self.min() <= other
# else:
# return self.min() <= other.max()
[docs] def __gt__(self, other):
"""operation >
:rtype: bool
:return: True or False
"""
if isinstance(other, (int, float)):
return self.min() > other
else:
return self.min() > other.max()
[docs] def __contains__(self, other):
"""operator in
:rtype: bool
:return: True or False
"""
if isinstance(other, (int, float)):
return (self.min() <= other) and (self.max() >= other)
else:
return (self.min() <= other.min()) and (self.max() >= other.max())
def _disretize_range(self, n=0):
""" discretize range
:param n:
:return: x values within range
"""
x = np.linspace(self.df.loc[0].l, self.df.loc[0].r, int(n))
if self.has_zero():
# x = np.hstack([x, [0, -1e-10, 1e-10], self.df.l.values, self.df.r.values])
x = np.hstack([x, [0], self.df.l.values, self.df.r.values])
else:
x = np.hstack([x, self.df.l.values, self.df.r.values])
x = np.unique(x)
return x
[docs] def abs(self):
"""calculate absolute value
:return: FuzzyNumber
"""
return self.__abs__()
[docs] def min(self):
"""minimum
:rtype: float
:return: min value of df
"""
return self.df[["l", "r"]].values.min()
[docs] def max(self):
"""maximal
:rtype: float
:return: max value of df
"""
return self.df[["l", "r"]].values.max()
[docs] def mean(self):
"""mean value
:rtype: float
:return: mean value
"""
return self.ppf(.5)
[docs] def make_convex(self):
"""make fuzzy number convex
:return: None
"""
for i in self.df.index:
self.df.loc[i, "l"] = self.df.loc[i:, "l"].min()
self.df.loc[i, "r"] = self.df.loc[i:, "r"].max()
# self.df.loc[i, "l"] = np.nanmin(self.df.loc[i:, "l"])
# self.df.loc[i, "r"] = np.nanmax(self.df.loc[i:, "r"])
# print(i, self.df.loc[i:, "l"].min(), self.df.loc[i:, "r"].max())
@property
def alpha0(self):
"""row for alpha=0"""
if self.df is not None:
return self.df.iloc[0]
@property
def alpha1(self):
"""row for alpha=1"""
if self.df is not None:
return self.df.iloc[-1]
# @property
# def area(self):
# """integral of the fuzzy number
#
# :return: area
# """
# raise NotImplementedError
# # A = ((self.alpha0.r - self.alpha0.l) - (self.df.r.values - self.df.l.values)).sum()
# # return A
[docs] def import_csv(self, fh):
"""load alpha levels from csv
:param fh: csv file path or file handle
:return: alpha level dataframe
"""
if isinstance(fh, str):
fh = open(fh, "r")
# self.df = pd.DataFrame.from_csv(fh)
self.df = pd.read_csv(fh)
# print(self.df.head())
return self.df
[docs] def export_csv(self, filepath=None):
"""export alpha levels to csv
:param filepath: csv file path
:return:
"""
logger.info("export df '%s'" % filepath)
if self.df is not None:
res = self.df.to_csv(filepath)
return res
[docs] @classmethod
def from_data(cls, **kwargs):
"""instantiate fuzzy number from attributes
:param kwargs:
:rtype: phuzzy.FuzzyNumber or derived object
:return: fuzzy number
"""
data = kwargs.get("data")
data = np.asarray(data)
kwargs["alpha0"] = [data.min(), data.max()]
mean = data.mean()
kwargs["alpha1"] = [mean]
p = Triangle(**kwargs)
return p
[docs] def __str__(self):
return "{0.__class__.__name__}({0.name}:{0.get_01_str})".format(self)
[docs] def __repr__(self):
return "{0.__class__.__name__}({0.name}:{0.get_01_str})".format(self)
[docs] def to_str(self):
"""serialize fuzzy number to string
:return: fuzzy number string
"""
raise NotImplementedError
[docs] @classmethod
def from_str(cls, s):
"""deserialize fuzzy number to string
:return: fuzzy number string
"""
raise NotImplementedError
[docs] def pdf(self, x):
"""Probability density function
:param x: x values
:return:
"""
y_ = np.hstack((self.df.alpha, self.df.alpha[::-1]))
x_ = np.hstack((self.df.l, self.df.r[::-1]))
I = np.trapz(y_, x_)
y = np.interp(x, x_, y_ / I, left=0., right=0.)
return y
[docs] def cdf(self, x, **kwargs):
"""Cumulative distribution function
:param x: x values
:param n: number of integration points
:return: y
"""
n = kwargs.get("n", 1e6)
y_ = np.hstack((self.df.alpha, self.df.alpha[::-1]))
x_ = np.hstack((self.df.l, self.df.r[::-1]))
x__ = np.linspace(self.alpha0.l, self.alpha0.r, int(n))
y__ = np.interp(x__, x_, y_)
I = cumtrapz(y__, x__, initial=0)
I /= I[-1]
y = np.interp(x, x__, I, left=0., right=1.)
return y
[docs] def ppf(self, x, **kwargs):
"""Percent point function (inverse of cdf-percentiles).
:param x: x values
:param n: number of integration points
:return: y
"""
n = kwargs.get("n", 1e6)
y_ = np.hstack((self.df.alpha, self.df.alpha[::-1]))
x_ = np.hstack((self.df.l, self.df.r[::-1]))
x__ = np.linspace(float(self.alpha0.l), float(self.alpha0.r), int(n))
y__ = np.interp(x__, x_, y_)
I = cumtrapz(y__, x__, initial=0)
I /= I[-1]
y = np.interp(x, I, x__, left=0., right=1.)
return y
[docs] def rvs(self, size, seed=None):
"""Sample points according membership function
:param size: number of sample points
:return: sample points
"""
if seed is not None and isinstance(seed, int) and np.sign(seed) == 1:
np.random.seed(seed=seed)
if seed is not None and isinstance(seed, int) and np.sign(seed) == -1:
r = np.linspace(0, 1, int(size))
else:
r = scipy.stats.uniform.rvs(size=size)
return self.ppf(r)
@property
def get_01(self):
"""get alpha=0 and alpha=1 values
:return: [[a0_l, a0_r], [a1_l, a1_r]]
"""
if self.df is not None and len(self.df) > 1:
return self.df.iloc[[0, -1]][["l", "r"]].values.tolist()
else:
return []
@property
def get_01_str(self):
"""get alpha=0 and alpha=1 values
:return: [[a0_l, a0_r], [a1_l, a1_r]]
"""
if self.df is not None and len(self.df) > 1:
return np.array2string(self.df.iloc[[0, -1]][["l", "r"]].values,
separator=",",
formatter={'float_kind': lambda x: "%.3g" % x}).replace("\n", "")
else:
return "[]"
[docs] @classmethod
def from_results(cls, df_res, name=None, number_of_alpha_levels=11):
"""create FuzzyNumber from DataFrame("alpha", "res")
:param df: DataFrame with columns=["alpha", "res"]
:return: FuzzyNumber
"""
self = cls()
if name is not None:
self.name = name
df = df_res.sort_values(by="alpha", ascending=False)
df["r"] = df.res.cummax()
df["l"] = df.res.cummin()
df.sort_values(by=["alpha", "l", "r"], inplace=True, ascending=True)
# FIXME: reduce rows with equal alpha levels
bins = np.linspace(0., 1., int(number_of_alpha_levels + 1))
groups = df.groupby(np.digitize(df.alpha, bins))
bins_results = []
for _, dfb in groups:
# print(i, len(dfb), dfb.alpha.min(), dfb.alpha.max())
bins_results.append([dfb.alpha.min(), dfb.l.min(), dfb.r.max()])
self.df = pd.DataFrame(bins_results, columns=["alpha", "l", "r"])
# self.convert_df(alpha_levels=number_of_alpha_levels)
# print(self.df)
return self
[docs] def get_shape(self):
"""get shape dataframe
:return: pandas.DataFrame(columns=["alpha", "x"])
"""
x = np.hstack([self.df["l"].values, self.df["r"].values[::-1]])
alpha = np.hstack([self.df["alpha"].values, self.df["alpha"].values[::-1]])
df = pd.DataFrame({"x": x, "alpha": alpha})
df.drop_duplicates(inplace=True)
return df
[docs] def get_alpha_from_value(self, x):
"""get alpha values from given x values
:param x: x values
:return: alpha values
"""
shape = self.get_shape()
return np.interp(x, shape.x, shape.alpha)
[docs] def defuzzification_alpha_one(self):
"""defuzzification
.. math::
(alpha1_r + alpha1_l) / 2
"""
x = ((self.alpha1['l'] + self.alpha1['r']) / 2)
return x
[docs] def defuzzification_mean(self):
"""defuzzification mean with discrete values"""
return self.df[["l", "r"]].values.mean()
[docs] def defuzzification_p50(self):
"""defuzzification mean my means of ppf(0.5)"""
return self.ppf(.5)
[docs] def defuzzification_centroid2(self):
"""defuzzification center of gravity"""
# x = self.df[["l", "r"]].values.flatten()
x = np.linspace(self.min(), self.max(), 100001)
m = self.alpha(x)
cg = np.sum(x * m) / np.sum(m)
return cg
[docs] def defuzzification_centroid(self):
"""defuzzification center of gravity"""
df = pd.concat([self.df[["l", "alpha"]].rename(columns={"l":"x"}), self.df[["alpha", "r"]].rename(columns={"r":"x"})]).sort_values(by=["x", "alpha"])
df = df.drop_duplicates(subset="x", keep="last")
d = pd.DataFrame()
d["alpha"] = df.alpha.rolling(2).mean()
d["dx"] = (df.x.rolling(2).max() - df.x.rolling(2).min())
d["x"] = df.x.rolling(2).mean()
d["A"] = d.dx * d.alpha
d["Ax"] = d.x * d.A
return d.Ax.sum()/d.A.sum()
[docs] def defuzzification(self, method='centroid'):
# self.zmax_values = np.flip(self.zmax_values, 0)
if method == 'alpha_one':
return self.defuzzification_alpha_one()
elif method == 'mean':
return self.defuzzification_p50()
elif method == 'centroid':
return self.defuzzification_centroid()
[docs]class Triangle(FuzzyNumber):
"""triange fuzzy number"""
[docs] def __init__(self, **kwargs):
FuzzyNumber.__init__(self, **kwargs)
alpha0 = kwargs.get("alpha0")
alpha1 = kwargs.get("alpha1")
self.discretize(alpha0=alpha0, alpha1=alpha1, alpha_levels=self.number_of_alpha_levels)
[docs] def discretize(self, alpha0, alpha1, alpha_levels):
"""discretize shape function
:param alpha0: range at alpha=0
:param alpha1: range at alpha=1
:param alpha_levels: number of alpha levels
:return: None
"""
# assert isinstance(alpha0, collections.Sequence) and len(alpha0) == 2
# assert isinstance(alpha1, collections.Sequence) and len(alpha1) > 0
self._a = alpha0[0]
self._b = alpha0[1]
self._c = alpha1[0]
self.df = pd.DataFrame(columns=["alpha", "l", "r"],
data=[[0., alpha0[0], alpha0[1]], [1., alpha1[0], alpha1[0]]], dtype=np.float)
self.df.sort_values(['alpha'], ascending=[True], inplace=True)
self.convert_df(alpha_levels=alpha_levels)
[docs] @classmethod
def from_data(cls, **kwargs):
"""instantiate fuzzy number from attributes
:param kwargs:
:rtype: phuzzy.FuzzyNumber or derived object
:return: fuzzy number
"""
n = kwargs.get("n", 100)
data = kwargs.get("data")
data = np.asarray(data, float)
datamin = data.min()
datamax = data.max()
kwargs["alpha0"] = [datamin, datamax]
means = []
mean = 3 * data.mean() - datamin - datamax
means.append(mean)
for _ in range(n):
train_data = np.random.choice(data, int(len(data) * 50))
# mean = 3 * train_data.mean() - train_data.min() - train_data.max()
mean = 3 * train_data.mean() - (train_data.min() + datamin) / 2 - (train_data.max() + datamax) / 2
means.append(mean)
mean = np.array(means).mean()
kwargs["alpha1"] = [mean]
p = cls(**kwargs)
return p
[docs] def pdf(self, x):
"""https://en.wikipedia.org/wiki/Triangular_distribution"""
a = self._a
b = self._b
c = self._c
x = np.asarray(x)
condlist = [x <= self._a, x <= self._c, x == c, x < self._b, x >= self._b]
choicelist = [0.,
2. * (x - a) / (b - a) / (c - a),
2. / (b - a),
2. * (b - x) / (b - a) / (b - c),
0.]
return np.select(condlist, choicelist)
[docs] def cdf(self, x, **kwargs):
"""Cumulative distribution function
:param x: x values
:param n: number of integration points
:return: y
"""
a = self._a
b = self._b
c = self._c
x = np.asarray(x)
condlist = [x <= self._a, x <= self._c, x < self._b, x >= self._b]
choicelist = [0.,
(x - a) ** 2 / (c - a) / (b - a),
1. - (b - x) ** 2 / (b - a) / (b - c),
1.]
return np.select(condlist, choicelist)
[docs] def to_str(self):
if len(self.df) > 0:
return "tri[{:.3g}, {:.3g}, {:.3g}]".format(self.df.iloc[0].l, self.df.iloc[0].r, self.df.iloc[-1].l)
else:
return "tri[nan, nan, nan]"
[docs]class Trapezoid(FuzzyNumber):
"""triange fuzzy number"""
[docs] def __init__(self, **kwargs):
FuzzyNumber.__init__(self, **kwargs)
alpha0 = kwargs.get("alpha0")
alpha1 = kwargs.get("alpha1")
self.discretize(alpha0=alpha0, alpha1=alpha1, alpha_levels=self.number_of_alpha_levels)
[docs] def discretize(self, alpha0, alpha1, alpha_levels):
"""discretize shape function
:param alpha0: range at alpha=0
:param alpha1: range at alpha=1
:param alpha_levels: number of alpha levels
:return: None
"""
# assert isinstance(alpha0, collections.Sequence) and len(alpha0) == 2
# assert isinstance(alpha1, collections.Sequence) and len(alpha1) == 2
self._a = alpha0[0]
self._b = alpha1[0]
self._c = alpha1[1]
self._d = alpha0[1]
# todo: check a <= c <= d <= b
self.df = pd.DataFrame(columns=["alpha", "l", "r"],
data=[[0., alpha0[0], alpha0[1]], [1., alpha1[0], alpha1[1]]], dtype=np.float)
self.df.sort_values(['alpha'], ascending=[True], inplace=True)
self.convert_df(alpha_levels=alpha_levels)
[docs] def pdf(self, x):
"""pdf
:param x:
:return:
"""
# https://en.wikipedia.org/wiki/Trapezoidal_distribution
a = self._a
b = self._b
c = self._c
d = self._d
x = np.asarray(x)
condlist = [x <= self._a, x <= self._b, x <= self._c, x < self._d, x >= self._d]
choicelist = [0.,
2. / (c + d - a - b) * (x - a) / (b - a),
2. / (c + d - a - b),
2. / (c + d - a - b) * (d - x) / (d - c),
0.]
return np.select(condlist, choicelist)
[docs] def cdf(self, x, **kwargs):
"""Cumulative distribution function
:param x: x values
:param n: number of integration points
:return: y
"""
a = self._a
b = self._b
c = self._c
d = self._d
x = np.asarray(x)
condlist = [x <= self._a, x <= self._b, x <= self._c, x < self._d, x >= self._d]
choicelist = [0.,
(x - a) ** 2 / (c + d - a - b) / (b - a),
(2 * x - a - b) / (d + c - a - b),
1 - (d - x) ** 2 / (d - c) / (d + c - a - b),
1.]
return np.select(condlist, choicelist)
[docs] def to_str(self):
if len(self.df) > 0:
return "trap[{:.3g}, {:.3g}, {:.3g}, {:.3g}]".format(self.df.iloc[0].l, self.df.iloc[0].r,
self.df.iloc[-1].l, self.df.iloc[-1].r)
else:
return "trap[nan, nan, nan, nan]"
# @classmethod
# def from_str(cls, s):
# raise NotImplementedError