2023国赛C题
蔬菜类商品的自动定价与补货决策(上)
符号说明:
问题1
问题1主要的代码和思路在上一篇文章"数学建模实战块速入门"中已经进行了较为详细的展示,在问题一种要求我们从蔬菜单品和品类两个维度去分析各自之间的关系。我们采用的方法便是计算对应单品或者品类之间的相关系数;主要的相关系数有Spearmna相关系数 或者**(Pearson)皮尔逊相关系数**等。(注意:能够适用皮尔逊相关的场合当然是优先使用皮尔逊相关,当数据展现的是非线性关系,或者不是正态分布的,或者存在较为明显的异常值考虑到斯皮尔曼相关系数对异常值不敏感可以选择斯皮尔曼相关系数 ;而在分析各个品类在销量在时间上的分布后,我们发现其具有季节性起伏,因此两个单品或者品类之间难以建立线性的相关关系,因此选择反应单调变化趋势的斯皮尔曼相关系数就成为了更优的选择)。
使用python对原始数据进行处理和计算
python
#加载需要的python库,如果没有提前安装,使用pip install XXX进行安装,如果加载过慢超时,可以使用清华源等镜像下载。
import pandas as pd
import numpy as np
import cvxpy as cp
import random
import math
from prophet import Prophet
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
from prophet.diagnostics import cross_validation, performance_metrics
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, mean_absolute_error
import statsmodels.api as sm
原始数据的处理示意图以及对应代码:
python
# 创建按品类划分的数据表
df_items = pd.read_excel('附件1.xlsx', engine='openpyxl') # 从附件1读取蔬菜单品的数据
df_sales = pd.read_excel('附件2.xlsx', engine='openpyxl') # 从附件2读取购买流水数据
df_merged = pd.merge(df_sales, df_items, on='单品编码', how='left') # 通过蔬菜编码连接两个数据表
df_merged['金额'] = df_merged['销量(千克)'] * df_merged['销售单价(元/千克)'] # 计算每笔订单的销售金额(考虑退货,所以使用负值)
df_merged.loc[df_merged['销售类型'] == '退货', '金额'] *= -1
df_merged.loc[df_merged['销售类型'] == '退货', '销量(千克)'] *= -1
# 获取蔬菜单品每日平均价格
daily_price = df_merged.groupby(['单品编码', '单品名称', '销售日期'])['销售单价(元/千克)'].mean().reset_index()
# 按照蔬菜单品和销售日期来计算每日销售总销量、总金额
daily_vol = df_merged.groupby(['单品编码', '单品名称', '销售日期'])['销量(千克)'].sum().reset_index()
daily_sales = df_merged.groupby(['单品编码', '单品名称', '销售日期'])['金额'].sum().reset_index()
python
# 根据退货和销售金额计算退货率
total_returned = df_merged[df_merged['销售类型'] == '退货'].groupby('单品编码')['金额'].sum()
total_sales = df_merged[df_merged['销售类型'] == '销售'].groupby('单品编码')['金额'].sum()
return_rate = (total_returned / total_sales).reset_index(name='退货率')
# 合并数据获取每日平均价格
pivot_price = daily_price.pivot_table(index=['单品编码', '单品名称'], columns='销售日期', values='销售单价(元/千克)', fill_value=0).reset_index()
finalprice_df = pd.merge(df_items[['单品编码', '单品名称', '分类名称']], return_rate, on='单品编码', how='left')
finalprice_df = pd.merge(finalprice_df, pivot_price, on=['单品编码', '单品名称'], how='left')
# 合并数据获取每日销量
pivot_vol = daily_vol.pivot_table(index=['单品编码', '单品名称'], columns='销售日期', values='销量(千克)', fill_value=0).reset_index()
finalvol_df = pd.merge(df_items[['单品编码', '单品名称', '分类名称']], return_rate, on='单品编码', how='left')
finalvol_df = pd.merge(finalvol_df, pivot_vol, on=['单品编码', '单品名称'], how='left')
# 合并数据以获取每日销售额
pivot_sales = daily_sales.pivot_table(index=['单品编码', '单品名称'], columns='销售日期', values='金额', fill_value=0).reset_index()
finalsales_df = pd.merge(df_items[['单品编码', '单品名称', '分类名称']], return_rate, on='单品编码', how='left')
finalsales_df = pd.merge(finalsales_df, pivot_sales, on=['单品编码', '单品名称'], how='left')
# 保存为新的Excel文件
finalprice_df.to_excel('output_price.xlsx', index=False, engine='openpyxl')
finalvol_df.to_excel('output_vol.xlsx', index=False, engine='openpyxl')
finalsales_df.to_excel('output_sales.xlsx', index=False, engine='openpyxl')
问题2
考虑商超以品类为单位做补货计划,请分析各蔬菜品类的销售总量与成本加成定价的关系,并给出各蔬菜品类未来一周(2023年7月1-7日)的日补货总量和定价策略, 使得商超收益最大。
问题分析: 问题二首先要求我们分析各蔬菜品类的销售总量和成本加定价的关系;从常识角度,蔬菜卖的越贵,销量就会减少,反之则会销量增加。因此我们可以判断出蔬菜品类的销售总量和成本加定价之间应该存在线性关系。
如何从数据中判断是否存在线性相关性? 我么可以考虑计算皮尔逊相关系数来判定。
皮尔逊相关系数可以用来度量两个变量之间的相关程度,其计算公式为:
ρ ( x , y ) = ∑ i = 1 n ( X i − X ˉ ) ( Y i − Y ˉ ) ∑ i = 1 n ( X i − X ˉ ) 2 ∑ i = 1 n ( Y i − Y ˉ ) 2 \rho\left(x,y\right)=\frac{\sum_{i=1}^n(X_i-\bar{X})(Y_i-\bar{Y})}{\sqrt{\sum_{i=1}^n(X_i-\bar{X})^2}\sqrt{\sum_{i=1}^n(Y_i-\bar{Y})^2}} ρ(x,y)=∑i=1n(Xi−Xˉ)2 ∑i=1n(Yi−Yˉ)2 ∑i=1n(Xi−Xˉ)(Yi−Yˉ)
如果判定结果为存在线性相关关系,我们则可以采用最小二乘 等方法得到二者之间的线性回归方程。
数据处理:
我们想要计算皮尔逊相关系数,那就要想办法得到各品类的销量数据以及各品类的成本加成定价。
销售数据在前面计算过,可以直接导入到Excel文件中:
python
# 每日销售量
pivot_class_vol.to_excel('daily_class_vol.xlsx', index=False, engine='openpyxl')
某一时间段内(时间段可以为日、月、季度)的销售价格和批发价格分别由下式确定:
即:sum(每单单价*每单销量)/sum(每单销量)
P i ( t ) = ∑ p j ( t ) ⋅ q j ( t ) ∑ q j ( t ) , j ∈ 品类 i C i ( t ) = ∑ c j ( t ) ⋅ q j ( t ) ∑ q j ( t ) , j ∈ 品类 i P_{i}(t)=\frac{\sum p_j\left(t\right)\cdot q_j\left(t\right)}{\sum q_j\left(t\right)} , j\in\text{品类}i\\C_{i}(t)=\frac{\sum c_j\left(t\right)\cdot q_j\left(t\right)}{\sum q_j\left(t\right)} , j\in\text{品类}i Pi(t)=∑qj(t)∑pj(t)⋅qj(t),j∈品类iCi(t)=∑qj(t)∑cj(t)⋅qj(t),j∈品类i
python
"""
df_items = pd.read_excel('附件1.xlsx', engine='openpyxl') # 从附件1读取蔬菜单品的数据
df_sales = pd.read_excel('附件2.xlsx', engine='openpyxl') # 从附件2读取购买流水数据
df_merged = pd.merge(df_sales, df_items, on='单品编码', how='left') # 通过蔬菜编码连接两个数据表
df_merged['金额'] = df_merged['销量(千克)'] * df_merged['销售单价(元/千克)'] # 计算每笔订单的销售金额(考虑退货,所以使用负值)
df_merged.loc[df_merged['销售类型'] == '退货', '金额'] *= -1
df_merged.loc[df_merged['销售类型'] == '退货', '销量(千克)'] *= -1
"""
# 按照蔬菜品类和销售日期来计算每日销售总销量、总金额
daily_class_vol = df_merged.groupby(['分类编码', '分类名称', '销售日期'])['销量(千克)'].sum().reset_index()
daily_class_sales = df_merged.groupby(['分类编码', '分类名称', '销售日期'])['金额'].sum().reset_index()
--
计算daily_class_vol 、daily_class_sales 使用了分组聚合的函数即groupby函数,我们对所有行按照分类编码和销售日期进行分组,并对每组元素进行对应的求和。我们便得到了各个品类在每一天的销量和销售金额。
下面是我们处理得到的结果:
我们希望得到的处理结果是时间作为行标签,蔬菜品类作为列标签,因此我们对数据进行了如下处理:
python
# 合并数据获取每日销量
pivot_class_vol = daily_class_vol.pivot_table(index=['分类编码', '分类名称'], columns='销售日期', values='销量(千克)', fill_value=0).reset_index()
# 合并数据以获取每日销售额
pivot_class_sales = daily_class_sales.pivot_table(index=['分类编码', '分类名称'], columns='销售日期', values='金额', fill_value=0).reset_index()
pivot_class_vol = pivot_class_vol.T
#pivot_class_vol.set_index('销售日期', inplace=True) # 把第一列日期设定为标签
dates = pivot_class_vol.index.tolist()[2:]
columns = pivot_class_vol.iloc[1] # 取出第二行作为列名
pivot_class_vol = pivot_class_vol.iloc[2:].reset_index(drop=True) # 去除前两行,并重置索引
pivot_class_vol.columns = columns # 设置新的列名
pivot_class_vol.index = dates
pivot_class_sales = pivot_class_sales.T
columns = pivot_class_sales.iloc[1] # 取出第二行作为列名
pivot_class_sales = pivot_class_sales.iloc[2:].reset_index(drop=True) # 去除前两行,并重置索引
pivot_class_sales.columns = columns # 设置新的列名
pivot_class_sales.index = dates
"""
分别计算日销售价格和日批发价格
"""
这个是我们希望得到的结果
下面我们按照前面给出的公式计算日成本加成定价,我们首先定义了存储计算结果的数据结构,然后带入公式计算。
python
import numpy as np
rows,cols = pivot_class_sales.shape
daily_class_price = np.zeros((rows, cols), dtype=int)
daily_class_price = pd.DataFrame(daily_class_price)
daily_class_price.columns = columns
daily_class_price.index = dates
python
# 日销售价格
for i in pivot_class_sales.columns.tolist():
for j in pivot_class_vol.index.tolist():
if pivot_class_vol[i][j] == 0:
daily_class_price[i][j] = 0 # 如果销量为0,则价格也为0
else:
daily_class_price[i][j] = pivot_class_sales[i][j] / pivot_class_vol[i][j] # 计算日销售价格
#daily_class_price[i] = pivot_class_sales[i] / pivot_class_vol[i] # 计算日销售价格
daily_class_price.to_excel('daily_class_price.xlsx', index=False, engine='openpyxl')
采取同样的方式我们也可以计算日批发价格。
python
# 日批发价格
import pandas as pd
#df_items = pd.read_excel('附件1.xlsx', engine='openpyxl') # 从附件1读取蔬菜单品的数据
df_wholesale = pd.read_excel('附件3.xlsx', engine='openpyxl') # 从附件3读取蔬菜单品的批发数据
df_merged_wholesale = pd.merge(df_wholesale,df_items, on='单品编码', how='left') # 通过蔬菜编码连接两个数据表
#df_merged_wholesale = pd.merge(df_wholesale,df_items, on='单品编码', how='left') # 通过蔬菜编码连接两个数据表
daily_vol = df_merged.groupby(['单品编码', '单品名称', '销售日期'])['销量(千克)'].sum().reset_index()
daily_vol = daily_vol.rename(columns={'销售日期': '日期'})
###
merged_wholesale = pd.merge(daily_vol, df_merged_wholesale, on=['单品编码', '单品名称', '日期'], how='left')
# merged_wholesale = df_merged_wholesale.groupby(['单品编码', '单品名称', '日期'])['批发价格(元/千克)'].sum().reset_index()
python
merged_wholesale['批发金额'] = merged_wholesale['销量(千克)'] * merged_wholesale['批发价格(元/千克)']
# 计算日批发价格需要使用销量*批发价格得到批发金额,计算每日批发金额/每日销量总和
# ****************************************************** #
daily_class_wholesale = merged_wholesale.groupby(['分类编码', '分类名称', '日期'])['批发金额'].sum().reset_index()
pivot_class_wholesale = daily_class_wholesale.pivot_table(index=['分类编码', '分类名称'], columns='日期', values='批发金额', fill_value=0).reset_index()
python
pivot_class_wholesale = pivot_class_wholesale.T
#pivot_class_vol.set_index('销售日期', inplace=True) # 把第一列日期设定为标签
dates = pivot_class_wholesale.index.tolist()[2:]
columns = pivot_class_wholesale.iloc[1] # 取出第二行作为列名
pivot_class_wholesale = pivot_class_wholesale.iloc[2:].reset_index(drop=True) # 去除前两行,并重置索引
pivot_class_wholesale.columns = columns # 设置新的列名
pivot_class_wholesale.index = dates
python
import numpy as np
#rows,cols = pivot_class_sales.shape
daily_class_price = pd.read_excel('daily_class_price.xlsx', engine='openpyxl')
rows,cols = daily_class_price.shape
daily_class_wholesaleprice = np.zeros((rows, cols), dtype=int)
daily_class_wholesaleprice = pd.DataFrame(daily_class_price)
daily_class_wholesaleprice.columns = columns
daily_class_wholesaleprice.index = dates
python
# 这里的pivot_class_vol在计算每日价格时计算过,这边直接拿过来.
for i in pivot_class_wholesale.columns.tolist():
for j in pivot_class_vol.index.tolist():
if pivot_class_vol[i][j] == 0:
daily_class_wholesaleprice[i][j] = 0 # 如果销量为0,则价格也为0
else:
daily_class_wholesaleprice[i][j] = pivot_class_wholesale[i][j] / pivot_class_vol[i][j] # 计算日销售价格
#daily_class_price[i] = pivot_class_sales[i] / pivot_class_vol[i] # 计算日销售价格
daily_class_wholesaleprice.to_excel('daily_class_wholesaleprice.xlsx', index=False, engine='openpyxl')
下面我们对成本加成定价与日销量计算皮尔逊相关系数 ,并进行最小二乘线性回归:
python
import pandas as pd
daily_class_price = pd.read_excel('daily_class_price.xlsx', engine='openpyxl')
daily_class_wholesaleprice = pd.read_excel('daily_class_wholesaleprice.xlsx', engine='openpyxl')
daily_class_vol = pd.read_excel('daily_class_vol.xlsx', engine='openpyxl')
python
# 计算花叶类 日销量和成本加成定价 之间的皮尔逊相关系数
# daily_class_price['花叶类']
pearson_xy = daily_class_wholesaleprice['花叶类'].corr(daily_class_vol['花叶类'], method='pearson')
print(f"变量X和Y之间的皮尔逊相关系数: {pearson_xy}")
进行最小二乘线性回归,并进行可视化展示:
python
import numpy as np
import matplotlib.pyplot as plt
# 准备数据
#x = np.array([1, 2, 3, 4, 5])
#y = np.array([2, 4, 5, 4, 5]) # 注意:这些数据不是完美线性的,以展示拟合过程
x = np.array(daily_class_price['花叶类'].tolist())
y = np.array(daily_class_vol['花叶类'].tolist())
# 使用numpy的polyfit函数进行线性拟合(1表示一次多项式,即线性)
# 返回拟合系数,其中第一个是斜率m,第二个是截距b
coefficients = np.polyfit(x, y, 1)
m, b = coefficients
# 输出回归方程的参数
print(f"回归方程为: y = {m:.2f}x + {b:.2f}")
# 使用拟合得到的参数进行预测
x_predict = np.linspace(min(x), max(x), 100) # 生成一系列预测点
y_predict = m * x_predict + b
# 可视化结果
plt.scatter(x, y, color='red', label='Data Points')
plt.plot(x_predict, y_predict, color='blue', label='Fitted Line')
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Linear Least Squares Fitting')
plt.legend()
plt.show()
我们已经计算得到了一些关键行的指标,之后我们需要使用时间序列模型或者其他模型对未来一周的销量进行预测,并且开始建立以销售额的最大化为最优目标的优化求解模型,并使用一些求解算法来求解得到最终结果,这也是在下一篇文章的重点。