为熊猫中的每个产品类别获得“销售窗口”的最佳方法?

如何解决为熊猫中的每个产品类别获得“销售窗口”的最佳方法?

因此,我的数据框具有多年以来许多产品的销售详细信息,并具有如下图所示的图形:

我正试图找出每种产品的销售窗口。

到目前为止我已经尝试过:

我想到的方法是获取每年六个月间隔的最小,中值和最大日期值,并将(最小至中值)声明为最差销售时段,将最大至中值声明为该产品的最佳销售时段。我现在使用了六个月的代码,但也希望在一年中使用它。哪个效果最好:

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%。当然,只有当我尝试添加一个以上全年数据的产品不止一个时,此方法才有效,因为它可以更好地解释我的数据中的季节性,而在较小的样本中则无法检测到。

链接:https://pern-my.sharepoint.com/:x:/g/personal/syed_8911255_talmeez_pk/EY_w794N49dGgWfYal90ZLUBt5TDB3asJEayuJHD1QdRog?e=Ih4Wo8

解决方法

这样的事情如何?

# 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))

enter image description here

最大/最小滚动位不是完美的,但是如果年度最大/最小年间有意义地变化,则很有必要。您还必须通过这种方法忽略第一年的数据。

此下一个方法通过首先分别拉出年度最大/分钟来解决这些问题:

# 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))

enter image description here

,

我认为这里首先要考虑的是要静态模型还是自更新模型。

我的建议是使用静态模型,因为使用至今为止积累的所有数据,以获取产品的最畅销和最不畅销产品窗口,并将其作为下一年的建议。发表您可以再次更新推荐的信息。

接下来,您需要确定要称呼的是好是坏。可能是这样,前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 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-