挖掘思路:时令蔬菜有哪些?针对国内农产品批发价的时间序列分析
时令挖掘
借鉴上述链接的挖掘思路,练习matplotlib库
摘要¶
又到了吃西瓜的季节。之所以在夏季吃西瓜,不仅仅因为其可以解暑,而且在夏天是西瓜的大量上市时期,价格还会比较便宜。在我们的食谱中,还有许多像西瓜这样的具有明显季节性的食材。这次的项目将通过分析农产品批发价的时间序列数据,发现具有强季节性的蔬菜,为在合适的季节吃合适的蔬菜提供参考。同时也对过去几年农产品的价格变化趋势进行了解。其中的主要发现包括:
价格具有强季节性的农产品主要是蔬菜和水果。例如菠菜、韭菜在春季,西瓜、冬瓜在夏季,巨峰葡萄在秋季,莲藕在冬季的价格会较低。
在过去两年里,约52%的农产品有明显的价格变化趋势。其中大部分为价格上升趋势,以水产品为代表。而猪肉和生姜是少有的有着价格下降趋势的产品。
不过,价格变化不仅受到农产品供给变化的影响,还会受到市场需求、运输存储、制度政策等变化的影响。因此如果要确认是否真的是时令蔬菜,还应结合农产品的实际培育周期来进行校验。
数据说明
数据来自中国农业农村部在过去两年(2020年6月到2022年5月)从全国批发市场采集的逐月农产品批发价格数据。数据中涉及到的农产品品种包含5种畜禽产品、5种水果、7种水产品以及28种蔬菜。
数据预处理
improt pandas as pd
# 画图
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.ticker as ticker
from pylab import mpl
# 数据one_hot 处理
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
mpl.rcParams['font.sans-serif'] = ['Source Han Serif SC'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
# 文件来自于挖掘思路的数据文件
df = pd.read_csv("wholesale_price_of_agricultural_products.csv")
#df.head(1)
#year_month product price category
#0 202012 芹菜 3.76 蔬菜
# 日期转换
df['ds'] = pd.to_datetime(df['year_month'])
# 月份
df['mon'] = df['ds'].dt.month
# 年
df['year'] = df['ds'].dt.year
# 月份进行one_hot编码
enc = OneHotEncoder(sparse=False)
result = enc.fit_transform(df[['mon']])
pd_mon = pd.DataFrame(result)
df_all = pd.concat([df, pd_mon], axis=1)
#df_all.head(1)
#year_month product price category ds mon year mon1 mon2 mon3 mon4 mon5 mon6 mon7 mon8 mon9 mon10 mon11 mon12
#202012 芹菜 3.76 蔬菜 2020-12-01 12 2020 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0
各类农产品批发价的历史变化
col = ['category', 'product']
df_group = df_all.groupby(col)
import numpy as np
import matplotlib.pyplot as plt
# 画出产品历史价格变化图
for k, v in df_group:
v = v.sort_values(by=['year', 'mon']).reset_index(drop=True)
ax = plt.figure(figsize=(20, 10)).add_subplot(111)
plt.xticks(rotation='90')
plt.xlabel("Mon")
plt.ylabel("Price")
plt.margins(0, 0)
title = k[0] + "_" + k[1]
# 图标题
plt.title(title, fontdict={'fontsize':20})
# 横坐标 - 纵坐标
x = [str(v.iloc[i]['year']) + "_" + str(v.iloc[i]['mon']) for i in range(len(v))]
y = v['price']
# 绘制折线图
ax.plot(x, y,'bo-',color = 'b', label='m_price')
# 使用axvspan 填充背景
# 红-春 1,2,3月
# 蓝-夏 4,5,6月
# 黄-秋 7,8,9月
# 灰-冬 10,11,12月
plt.axvspan(0,1, facecolor='dodgerblue', alpha=0.5)
for _ in range(2):
i = _ * 12
plt.axvspan(i,i+3, facecolor='r', alpha=0.5)
plt.axvspan(i+3,i+6, facecolor='dodgerblue', alpha=0.5)
plt.axvspan(i+6,i+9, facecolor='y', alpha=0.5)
plt.axvspan(i+9,i+12, facecolor='c', alpha=0.5)
# 设置网格线
ax.xaxis.grid(linestyle = '-', linewidth=2)
plt.show()
效果如下
水产 – 活鲤鱼
- 从水产品的价格历史变化可以看出:水产品的价格整体呈小幅上升的趋势。鲫鱼、鲤鱼等淡水鱼在2021年的春季出现明显的价格上涨,并在当年夏秋季逐渐回落。
当时有一份2021年5月份关于淡水鱼价格上升的报道,其中提到了一些可能的原因:- 以往淡水鱼价格较低,导致淡水鱼养殖面积减少
- 春节期间的供应减少了当前的供给
- 气温较高时活鱼在运输中的损耗大
- 夏季为淡水鱼的需求旺季
水果 – 西瓜
- 水果有着更加明显的季节性
农产品批发价的历史波动幅度
#添加画布 -- 画布属性
ax = plt.figure(figsize=(6, 4), facecolor='c',edgecolor='black', dpi=120).add_subplot(111, facecolor='aquamarine', alpha=0.1)
# plt.figure(figsize=(20,10),dpi=120)
plt.xlabel("历史价格波动幅度")
plt.ylabel("历史价格平均值")
ax.xaxis.grid(linestyle = '--', linewidth=2, alpha=0.3)
ax.yaxis.grid(linestyle = '--', linewidth=2, alpha=0.3)
# plt.margins(0, 0)
for cate1_k, cate1_v in group:
cate1_v = cate1_v.reset_index(drop=True)
price_mean = cate1_v['price'].mean()
price_std = cate1_v['price'].std()/cate1_v['price'].mean()
# 散点图
ax.scatter(price_std, price_mean, s=50)
# 散点标签
ax.text(price_std, price_mean, cate1_k[1], fontsize=10)
# plt.grid()
#设计标题
plt.title(cate1_k[0] + "的历史平均价格和价格波动幅度的对比")
# plt.ylim(0,200)
#显示图例和图表
#markerscale 参数调整图例大小
# plt.legend(markerscale=0.5)
plt.show()
- 上面对比了各个农产品的历史平均价格和历史价格的波动幅度:
- 波动幅度较大的农产品以蔬菜为主。且在蔬菜里面,瓜果类的价格变动会更高,根茎类/菇类的蔬菜价格波动较小,叶菜类的价格波动居中。这可能和不同蔬菜的培育和存储特点有关。
- 猪肉的位置非常显眼,其在平均价格较高的情况下仍然有较大的价格波动幅度。其价格变化也确实在过去几年引起了人们的普遍关注
- 西瓜和巨峰葡萄是水果品类中价格波动最大的两个产品。这可能体现了其较强的季节性。
- 价格较高的牛肉、羊肉等的价格波动幅度较小。
注:
历史价格波动幅度 = 历史价格的变异系数 = 历史价格的标准差 历史价格的平均值 \frac{历史价格的标准差}{历史价格的平均值} 历史价格的平均值历史价格的标准差
使用多元回归计算农产品价格的趋势和季节性
如何计算季节性
下面使用多元回归的方法来分析价格的季节性。以西瓜为例,其步骤为:
- 选取西瓜的历史批发价数据。本数据集中一共有24个月的数据。
- 将每个月的价格视为回归的目标变量。
- 将24个月按照顺序赋予从1到24的序号,作为趋势的特征变量。
- 对月份进行One-Hot编码,得到从1月到12月的12个特征变量。每条西瓜的价格记录都会在其中某个月的特征变量中标注为1,其他月份则为0。
- 基于上述特征变量,针对目标变量价格构建多元回归模型
- 以回归模型针对趋势特征变量的系数代表价格变化趋势,以各个月份特征变量的系数代表该月对价格的影响,即季节性影响。
回归分析
import statsmodels.api as sm
# 使用anss 存储模型模拟结果,月份的系数(ceof),以及对应的p值
anss = []
# 月份转成one_hot模式输入,进行回归模拟
m_col = ['mon1', 'mon2', 'mon3', 'mon4', 'mon5', 'mon6', 'mon7', 'mon8', 'mon9', 'mon10', 'mon11', 'mon12']
for k, v in df_group:
ans = list(k)
v = v.sort_values(by=['year_month', 'mon']).reset_index(drop=True)
ans.append(v['price'].mean())
ans.append(v['price'].std()/v['price'].mean())
x = v[m_col]
y = v['price']
model = sm.OLS(y,x)
results = model.fit()
results_summary = results.summary()
res_df = pd.DataFrame(results_summary.tables[1])
# 存储模型结果
m_std = []
m_p_value = []
for i in range(2,14):
m_p_value.append(float(res_df[4][i].format(0)))
m_std.append(float(res_df[1][i].format(0)))
ans.append(m_std)
ans.append(m_p_value)
sea_m = []
# 统计月份的p值小于0.5的
cnt = 0
for i,p in enumerate(m_p_value):
if p < 0.05:
cnt += 1
sea_m.append(i+1)
ans.append(cnt)
ans.append(sea_m)
anss.append(ans)
#print(results.summary())
下面是以西瓜为例的拟合结果,可以看到各个特征变量的参数及其P值的结果:
不同农产品的季节性程度对比
#添加画布
ax = plt.figure(figsize=(20, 10), facecolor='aquamarine',edgecolor='black', dpi=120).add_subplot(111, facecolor='c', alpha=0.1)
# plt.figure(figsize=(20,10),dpi=120)
plt.ylabel("季节对价格的实际影响程度")
plt.xlabel("季节性在统计意义上的季节显著性")
# plt.xlim(0, 1)
# plt.ylim(0,1.5)
ax.xaxis.grid(linestyle = '-', linewidth=2, alpha=0.3, color='white')
ax.yaxis.grid(linestyle = '-', linewidth=2, alpha=0.3, color='white')
# plt.margins(0, 0)
#代码思路:
x_min = 1
x_max = 0
y_min = 1
y_max = 0
for i in range(len(anss)):
# if anss[i][-2] == 0:
# continue
# if res[i][-3] == "体温计" or res[i][-3] == "消毒酒精":
# continue
# plt.scatter(X[y==i,0],X[y==i,1],c=colors[i],s=z[y==i]*20,label = labels[i])
x_min = min(x_min, anss[i][-2]/12)
x_max = max(x_max, anss[i][-2]/12)
y_min = min(y_min, np.std(anss[i][-4])/anss[i][-6])
y_max = max(y_max, np.std(anss[i][-4])/anss[i][-6])
plt.scatter(anss[i][-2]/12, np.std(anss[i][-4])/anss[i][2])
plt.text(anss[i][-2]/12, np.std(anss[i][-4])/anss[i][2], anss[i][1], fontsize=8)
# 象限标记
plt.text(0.4, 0.3, "①", fontsize=20, color='red')
plt.text(0.1, 0.3, "②", fontsize=20, color='red')
plt.text(0.1, 0.05, "④", fontsize=20, color='red')
plt.text(0.4, 0.05, "③", fontsize=20, color='red')
# 分割线
plt.vlines(0.2, 0, 0.5, colors='red', linestyles='--')
plt.hlines(0.1, 0, 1, colors='red', linestyles='--')
# plt.hlines(0.8, 0, 1, colors='red', linestyles='--')
#设计标题
plt.title("不同农产品的季节性程度对比")
#显示图例和图表
#markerscale 参数调整图例大小
plt.legend(markerscale=0.5)
plt.show()
上图的横坐标是农产品的季节性在统计意义上的显著性程度,纵坐标是季节性对农产品价格的实际影响程度。二者的数值越高,则代表农产品的季节性越强烈。基于这两个数值,分别以0.2和0.1作为阈值,可以将农产品划分为四组:
- 第1组是我们最感兴趣的,其既有统计意义上显著性,在实际意义上也大幅影响价格波动。这一组以蔬菜和水果为主。
- 唯一的例外是猪肉。虽然猪肉有一定的季节性,但在过去几年,由于其降价趋势的影响,其季节性体现得不明显。
- 第4组的农产品虽然有统计意义上显著的季节性,但实际对价格影响较小,在实际生活中往往可以忽略不记。
ps:
季节性在统计意义上的季节显著性 = 通过显著性检验的月份 12 季节性在统计意义上的季节显著性 = \frac{通过显著性检验的月份}{12} 季节性在统计意义上的季节显著性=12通过显著性检验的月份
季节性在统计意义上的季节显著性 = 季节性波动的标准差 价格的平均值 季节性在统计意义上的季节显著性 = \frac{季节性波动的标准差}{价格的平均值} 季节性在统计意义上的季节显著性=价格的平均值季节性波动的标准差
热力图
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
# sphinx_gallery_thumbnail_number = 2
# 筛选前15个产品
vegetables = [h[1] for h in anss[:15]]
# farmers =[] = ["Farmer Joe", "Upland Bros.", "Smith Gardening",
# "Agrifun", "Organiculture", "BioGoods Ltd.", "Cornylee Corp."]
farmers = [str(i) + "月" for i in range(1, 13)]
# 回归模型对应月份的系数
tmp_harvest = [h[4] for h in anss[:15]]
# 画布
fig, ax = plt.subplots(figsize=(20,10))
im = ax.imshow(tmp_harvest, cmap="RdYlBu_r")
# 设置热力图的边框
cbar = ax.figure.colorbar(im, ax=ax)
cbar.ax.set_ylabel("harvest legend", rotation=-90, va="bottom")
# 设置x,y轴刻度
ax.set_xticks(np.arange(len(farmers)))
ax.set_yticks(np.arange(len(vegetables)))
# 将任何其他类型的值作为标签.赋值给之前已经设置过的set_xtick,set_yticks.
ax.set_xticklabels(farmers)
ax.set_yticklabels(vegetables)
# x轴的坐标旋转45度
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",
rotation_mode="anchor")
# 设置网格图
ax.grid(which="minor", color="w", linestyle='-', linewidth=3)
ax.tick_params(which="minor", bottom=False, left=False)
# 设置热力图的值
for i in range(len(vegetables)):
for j in range(len(farmers)):
text = ax.text(j, i, round(tmp_harvest[i][j], 3),
ha="center", va="center", color="black")
ax.set_title("农产品季节性价格变化热力图")
fig.tight_layout()
plt.show()
季节性价格热力图中,正数代表价格在当季的提升趋势,负数代表价格的下降趋势。