如何解决为熊猫中的每个产品类别获得“销售窗口”的最佳方法?
因此,我的数据框具有多年以来许多产品的销售详细信息,并具有如下图所示的图形:
我正试图找出每种产品的销售窗口。
到目前为止我已经尝试过:
我想到的方法是获取每年六个月间隔的最小,中值和最大日期值,并将(最小至中值)声明为最差销售时段,将最大至中值声明为该产品的最佳销售时段。我现在使用了六个月的代码,但也希望在一年中使用它。哪个效果最好:
def dater(date):
print(date)
if type(date)==float:
return '-'
months = ['','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
period = ['Start','Mid','End','End']
return months[date.month]+' '+period[date.day//10]
def grpRes(grp):
return pd.Series([grp.Date.min(),grp.Date.max(),grp.Amount.mean()],index=['start','end','value'])
best_windows = pd.DataFrame(columns = df.select_dtypes(exclude='object').columns)
for col in df.select_dtypes(exclude='object').columns:
for year in ['2017','2018','2019','2020']:
print(f'For year {year} and category {col}')
temp = df.loc[year,col][df[col]>=df[col].quantile(0.7)]
print('temp created')
if len(temp)>0:
du = temp.reset_index().rename(columns = {'order_start_date': 'Date',col:'Amount'})
res = du.groupby(du.Date.diff().dt.days.fillna(1,downcast='infer')
.gt(20).cumsum()).apply(grpRes)
res.index.name = 'chunk'
for row in res.iterrows():
print(row)
best_windows.loc[year+' Window: '+str(row[0]+1)+' start',col] = row[1].start.date().strftime('%d-%m-%Y')
然后,我根据所有年份的值定义窗口作为窗口的起始范围和结束范围。但是,这似乎是一种可怕的方法。尽管这给了我不同年份的日期范围,如下所示:
2017 Window: 1 end 2017 Window: 1 start 2017 Window: 2 end 2017 Window: 2 start 2018 Window: 1 end 2018 Window: 1 start 2018 Window: 2 end 2018 Window: 2 start 2018 Window: 3 end 2018 Window: 3 start 2019 Window: 1 end 2019 Window: 1 start 2019 Window: 2 end 2019 Window: 2 start 2019 Window: 3 end 2019 Window: 3 start 2020 Window: 1 end 2020 Window: 1 start 2020 Window: 2 end 2020 Window: 2 start 2020 Window: 3 end 2020 Window: 3 start 2020 Window: 4 end 2020 Window: 4 start
B 31-12-2019 08-11-2019 09-01-2020 01-01-2020 31-07-2020 11-02-2020
D 12-06-2017 13-05-2017 14-10-2017 16-08-2017 13-06-2018 24-05-2018 20-08-2018 11-07-2018 03-11-2018 27-09-2018 10-11-2019 22-10-2019 31-12-2019 28-12-2019 31-07-2020 01-01-2020
H 06-04-2018 23-03-2018 09-08-2018 27-06-2018 16-11-2018 02-11-2018 25-05-2019 21-04-2019 15-08-2019 12-07-2019 31-12-2019 30-10-2019 31-07-2020 01-01-2020
J 12-02-2017 15-01-2017 31-12-2017 25-10-2017 11-02-2018 01-01-2018 31-12-2018 12-10-2018 24-02-2019 01-01-2019 31-12-2019 10-10-2019 04-02-2020 01-01-2020
L 08-11-2018 03-11-2018 31-12-2018 06-12-2018 07-03-2019 01-01-2019 01-05-2019 24-04-2019 31-12-2019 02-09-2019 06-03-2020 01-01-2020 19-04-2020 10-04-2020 14-05-2020 10-05-2020 31-07-2020 26-07-2020
LO 31-12-2017 06-09-2017 03-01-2018 01-01-2018 31-12-2018 23-09-2018 10-02-2019 01-01-2019 31-12-2019 25-09-2019 11-02-2020 01-01-2020
M 11-09-2017 15-01-2017 15-10-2018 03-07-2018 02-05-2019 22-04-2019 24-11-2019 18-11-2019 13-05-2020 28-03-2020 23-07-2020 21-06-2020
P 03-05-2017 21-01-2017 19-10-2017 11-08-2017 23-04-2018 31-01-2018 10-10-2018 02-08-2018 23-04-2019 23-02-2019 06-10-2019 04-09-2019 04-04-2020 29-02-2020
S 26-07-2017 24-03-2017 01-07-2018 25-03-2018 01-05-2019 18-04-2019 10-08-2019 23-05-2019 31-07-2020 01-04-2020
SH 12-08-2017 07-05-2017 11-08-2018 05-05-2018 10-08-2019 01-05-2019 31-07-2020 29-04-2020
SK 31-12-2019 12-12-2019 01-01-2020 01-01-2020 31-07-2020 24-05-2020
SKO 26-09-2017 01-05-2017 19-09-2018 03-05-2018 25-07-2019 09-07-2019 31-07-2020 04-05-2020
SL 10-06-2017 24-05-2017 06-05-2018 06-05-2018 16-07-2018 31-05-2018 01-08-2019 12-03-2019 31-07-2020 16-02-2020
U 17-05-2019 18-04-2019 24-06-2019 10-06-2019 01-06-2020 27-03-2020 31-07-2020 25-06-2020
V 13-02-2017 15-01-2017 31-12-2017 14-09-2017 05-03-2018 01-01-2018 31-12-2018 25-09-2018 19-02-2019 01-01-2019 31-12-2019 22-10-2019 22-01-2020 01-01-2020
现在,我可以使用编写的dater函数将其转换为月份和精确的月份窗口:
best_windows = best_windows.transpose().applymap(dater)
但这为我提供了年度解决方案,而不是一个单一的销售窗口。
理想情况是我想要实现的目标
该产品在一年中的每个年度中最畅销和最不畅销的窗口,我可以说嘿,该产品很受欢迎(例如,像产品A在3月底到6月中旬卖得最好)。图片中所示的%销售额曲线的波峰/波谷,理想情况下,也是过渡期,以便更好地了解每种产品的销售窗口。
数据样本:
我的数据如下。请注意,这些是基于每个类别代表的总销售额的%s。我的意思是说占总销售额的百分比。假设总销售额为10美元。其中产品A的售价为$ 5,B的售价为$ 3,C的售价为$ 2。然后,%值如下:A = 50%,B = 30%,C = 20%。当然,只有当我尝试添加一个以上全年数据的产品不止一个时,此方法才有效,因为它可以更好地解释我的数据中的季节性,而在较小的样本中则无法检测到。
解决方法
这样的事情如何?
# usng sin to generate seasonal data
period = 365 * 4
dates = pd.date_range('2016-01-01',periods=period)
np.random.seed(42)
pure = np.sin(np.linspace(6,30,period))
noise = np.random.normal(0,1,period)
signal = pure + 20 + noise
df = pd.DataFrame({'date': dates,'signal': signal}).set_index('date')
df['smoothed'] = df['signal'].rolling(30).mean()
# get best/worst selling months
# rolling max/min method
threshold = 0.97
window = 320
df['best'] = df['smoothed'].where( df['smoothed'] > df['smoothed'].rolling(window).max() * threshold,other=np.nan)
df['worst'] = df['smoothed'].where( df['smoothed'] < df['smoothed'].rolling(window).min() / threshold,other=np.nan)
df.iloc[365:,1:].plot(figsize=(14,10))
最大/最小滚动位不是完美的,但是如果年度最大/最小年间有意义地变化,则很有必要。您还必须通过这种方法忽略第一年的数据。
此下一个方法通过首先分别拉出年度最大/分钟来解决这些问题:
# annual max/min method
threshold = 0.97
df['max'],df['min'] = df['smoothed'].max(),df['smoothed'].min()
df['best'] = df['smoothed'].where( df['smoothed'] > df['max'] * threshold,other=np.nan)
df['worst'] = df['smoothed'].where( df['smoothed'] < df['min'] / threshold,1:-2].plot(figsize=(14,10))
,
我认为这里首先要考虑的是要静态模型还是自更新模型。
我的建议是使用静态模型,因为使用至今为止积累的所有数据,以获取产品的最畅销和最不畅销产品窗口,并将其作为下一年的建议。发表您可以再次更新推荐的信息。
接下来,您需要确定要称呼的是好是坏。可能是这样,前20%的百分点是好的销售额,而后20个百分点是不好的。我们将此阈值称为T百分位数。
现在到主要部分,因此您的假设是,当产品的销售百分比每年高(T上方)或低(T下方)时,固定的窗口是固定的。 因此,首先,我们需要获取一年中每一天的平均值(您也可以拟合回归模型而不是进行平均值计算,这样可以使事情变得平滑,并使您的预测更可靠)。
df["datetime"] = pd.to_datetime(df["datetime"])
df["dayofyear"] = df["datetime"].dt.dayofyear
df["year"] = df["datetime"].dt.year
dfg = df.groupby("dayofyear").mean()
然后,无论平均/预测销售曲线是否超过T个百分位数,我们都将开始该区间,并在该区间再次交叉时停止。
def get_thresh_crossing_intervals(arr):
crossings = np.diff(np.sign(arr))
# You might also want to wrap arrays to cover spans around end of year
ends = np.where(crossings == -2)[0]
starts = np.where(crossings == 2)[0][:len(ends)]
return list(zip(starts,ends))
def post_process_intervals(intervals):
return [(p,q) for p,q in intervals if q-p>=7]
def get_col_intervals(df,col,top_thresh=0.2,bot_thresh=0.2):
# Get quantile based thresholds
top_qnt = df[col].quantile(1 - top_thresh)
bot_qnt = df[col].quantile(bot_thresh)
# Make threshold as zero line
top_df = df[col] - top_qnt
bot_df = df[col] - bot_qnt
# Get top crossings and intervals
top_intervals = get_thresh_crossing_intervals(top_df)
bot_intervals = get_thresh_crossing_intervals(bot_df)
# Some post processings (e.g. only keep intervals with more than a week)
top_intervals = post_process_intervals(top_intervals)
bot_intervals = post_process_intervals(bot_intervals)
return {'top_intervals': top_intervals,'bot_intervals': bot_intervals}
product_intervals = {}
for col in ["A","B"]:
product_intervals[col] = get_col_intervals(dfg,col)
product_intervals
Sample Output:
{'A': {'top_intervals': [(15,60),(117,136)],'bot_intervals': [(1,85),(103,236),(273,286),(287,320)]},'B': {'top_intervals': [(120,140),(198,209),(306,339)],'bot_intervals': [(36,61),(80,262)]}}
另外,我们只保留大于一定长度的间隔,否则我们将其删除,或者我们可以根据曲线下的长度/面积对间隔进行排序,并保持前n个。
注意: 如果您的时间序列不稳定,则需要先使它们稳定(只需检查产品年销量是否与去年同期相比有显着变化)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。