Skip to content

Commit

Permalink
new version
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexander Yuryatin authored Sep 4, 2022
1 parent 87f316e commit ae39b3c
Showing 1 changed file with 40 additions and 12 deletions.
52 changes: 40 additions & 12 deletions salesplansuccess/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,19 @@
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.colors import is_color_like
import statsmodels.api as sm
from scipy import stats
from datetime import date

class SalesPlanSuccess:
def __init__(self, data:pd.DataFrame, plan:int, sample_size:int = 50000):
def __init__(self, data:pd.DataFrame, plan:int):
if not isinstance(data, pd.DataFrame):
raise TypeError("The parameter 'data' of class SalesPlanSuccess can accept only pandas.DataFrame")
if not isinstance(plan, int) and not isinstance(plan, float):
raise TypeError("The parameter 'plan' of class SalesPlanSuccess can accept only regular Python integers and floats")
if not isinstance(sample_size, int):
raise TypeError("The parameter 'sample_size' of class SalesPlanSuccess can accept only regular Python integers")
self.tt = data.copy()
self.plan = plan
self.sample_size = sample_size
if (self.sample_size < 1000) or (self.sample_size > 10000000):
raise ValueError("The parameter 'sample_size' of class SalesPlanSuccess must be between 1000 and 10 000 000")
if self.plan <= 0:
raise ValueError("The parameter 'plan' of class SalesPlanSuccess must be a positive number")
if set(self.tt.columns) != set(['Year', 'Month', 'Sales']):
Expand All @@ -60,6 +56,23 @@ def __init__(self, data:pd.DataFrame, plan:int, sample_size:int = 50000):
raise ValueError("The column 'Sales' in parameter 'data' of Class SalesPlanSuccess must contain only positive (non-zero) numbers")
if self.tt.isnull().values.any():
raise ValueError("The pandas.DataFrame in parameter 'data' of Class SalesPlanSuccess must not contain any empty values")
self.tt.sort_values(by=['Year','Month'], inplace=True)
self.tt.reset_index(drop=True, inplace=True)
if self.tt.shape[0] < 7:
raise Error("The pandas.DataFrame in parameter 'data' of Class SalesPlanSuccess must contain at least 7 months of observations for modeling")
if self.tt[['Year','Month']].duplicated().any():
raise ValueError("The pandas.DataFrame in parameter 'data' of Class SalesPlanSuccess must not contain duplicate Year/Month pairs")
self.tt2 = self.tt.copy()
self.tt2['SOY'] = False
self.tt2.loc[self.tt2.Month == 1, 'SOY'] = True
self.tt2[['Year', 'Month']] = self.tt2[['Year', 'Month']].diff()
self.tt2.dropna(inplace=True)
if not ((self.tt2.loc[self.tt2.SOY, 'Year'] == 1.0).all() and
(self.tt2.loc[~self.tt2.SOY, 'Year'] == 0.0).all() and
(self.tt2.loc[self.tt2.SOY, 'Month'] == -11.0).all() and
(self.tt2.loc[~self.tt2.SOY, 'Month'] == 1.0).all()):
raise ValueError("The pandas.DataFrame in parameter 'data' of Class SalesPlanSuccess must contain consecutive Year/Month rows without omiting individual months")
del self.tt2
self.tt['qEnd'] = 0
self.tt.loc[np.int64(self.tt.Month.values) % 3 == 0, 'qEnd'] = 1
self.finalMonth = self.tt.Month.iloc[-1]
Expand All @@ -81,7 +94,12 @@ def fit(self) -> None:
self.monthsToForecast = 12 - self.finalMonth
self.m1 = np.dot(self.ARs, self.finalTwo)

def simulate(self) -> None:
def simulate(self, sample_size:int = 50000) -> None:
if not isinstance(sample_size, int):
raise TypeError("The parameter 'sample_size' of class SalesPlanSuccess can accept only regular Python integers")
self.sample_size = sample_size
if (self.sample_size < 1000) or (self.sample_size > 10000000):
raise ValueError("The parameter 'sample_size' of method simulate() in class SalesPlanSuccess must be between 1000 and 10 000 000")
if not hasattr(self, 'model_fit'):
raise ValueError("Before calling method 'simulate' of an object of class SalesPlanSuccess you first have to fit the ARIMA model by calling method 'fit' of the same object")
self.simul = np.random.normal(loc=self.model_fit.params[0], scale=np.sqrt(self.model_fit.params[4]), size=[self.monthsToForecast, self.sample_size])
Expand All @@ -97,24 +115,34 @@ def simulate(self) -> None:
self.dfPlot.loc[self.dfPlot.Sales < self.plan, 'Plan'] = 'Not achieved'

self.left_x = min(self.finalDistibution)
self.left_margin = self.left_x if self.left_x < self.plan else self.plan
self.right_x = np.quantile(self.finalDistibution, 0.99)
self.position = (self.plan - self.left_x) / (self.right_x - self.left_x)
self.right_margin = self.right_x if self.right_x > self.plan else self.plan
self.position = (self.plan - self.left_margin) / (self.right_margin - self.left_margin)
self.percent_not_achieved = 100*(self.finalDistibution < self.plan).mean()
self.density = stats.kde.gaussian_kde(self.dfPlot.Sales)
self.x1 = np.linspace(self.left_x, self.plan, 1000)
self.x2 = np.linspace(self.plan, self.right_x, 1000)

def plot(self) -> None:
def plot(self, failure_color:str = 'orange', success_color:str = 'green') -> None:
if not hasattr(self, 'model_fit'):
raise ValueError("Before calling method 'plot' of an object of class SalesPlanSuccess you first have to fit the ARIMA model by calling method 'fit' and then conduct a simulation by calling method 'simulate' of the same object")
if not hasattr(self, 'simul'):
raise ValueError("Before calling method 'plot' of an object of class SalesPlanSuccess you first have to conduct a simulation by calling method 'simulate' of the same object")
if not isinstance(failure_color, str):
raise TypeError("The parameter 'failure_color' of method plot() in class SalesPlanSuccess must be string")
if not isinstance(success_color, str):
raise TypeError("The parameter 'success_color' of method plot() in class SalesPlanSuccess must be string")
if not is_color_like(failure_color):
raise ValueError("The parameter 'failure_color' of method plot() in class SalesPlanSuccess must contain valid color")
if not is_color_like(success_color):
raise ValueError("The parameter 'success_color' of method plot() in class SalesPlanSuccess must contain valid color")
self.ax = plt.gca()
plt.fill_between(self.x1, self.density(self.x1), color='orange')
plt.fill_between(self.x2, self.density(self.x2), color='green')
plt.fill_between(self.x1, self.density(self.x1), color = failure_color)
plt.fill_between(self.x2, self.density(self.x2), color = success_color)
plt.text(self.position-0.01, 0.2, "Not achieved\n%.1f" % (self.percent_not_achieved,) + '%', color='black', fontsize = 15, transform=self.ax.transAxes, horizontalalignment='right')
plt.text(self.position+0.01, 0.7, "Achieved\n%.1f" % (100 - self.percent_not_achieved,) + '%', color='black', fontsize = 15, transform=self.ax.transAxes)
plt.xlim(self.left_x, self.right_x)
plt.xlim(self.left_margin, self.right_margin)
plt.ylim(0)
plt.box(False)
plt.yticks([])
Expand Down

0 comments on commit ae39b3c

Please sign in to comment.