Open Source, Open Future!
  menu
118 文章
ღゝ◡╹)ノ❤️

第9篇:92.8%的包裹消失了,一次数据异常追踪

上一篇:第8篇:快递行业的数据驱动决策概述

一、写在前面

上篇分析了快递包裹数据的基本规律。然后我就在想:这些规律能不能用来预测?

今天打算用LaDe真实数据,跑ARIMA、Prophet、LSTM三种方法,看看准确率能到多少。

但数据一上手,就发现不对劲...

二、数据质量检查

2.1 第一个异常:包裹量骤降

按照标准流程,先加载5城市的数据,做个基础统计。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# 设置中文显示
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

cities = {
    'delivery_sh.csv': '上海',
    'delivery_hz.csv': '杭州',
    'delivery_cq.csv': '重庆',
    'delivery_yt.csv': '烟台',
    'delivery_jl.csv': '吉林'
}

all_data = []
for filename, city in cities.items():
    df = pd.read_csv(f'data/raw/delivery/{filename}')
    df['city'] = city
    all_data.append(df)
    print(f"✓ 已加载 {city} 数据: {len(df):,} 条记录")

df_all = pd.concat(all_data, ignore_index=True)

# 转换时间字段
df_all['ds'] = df_all['ds'].astype(str).str.zfill(4)
df_all['ds'] = pd.to_datetime('2022' + df_all['ds'], format='%Y%m%d')

输出:

✓ 已加载 上海 数据: 1,483,864 条记录
✓ 已加载 杭州 数据: 1,861,600 条记录
✓ 已加载 重庆 数据: 931,351 条记录
✓ 已加载 烟台 数据: 206,431 条记录
✓ 已加载 吉林 数据: 31,415 条记录

数据加载没问题。接下来看看每天的包裹量:

# 合并后的总体情况
daily_data = df_all.groupby('ds').size().reset_index(name='orders')
daily_data = daily_data.set_index('ds').sort_index()

print("【5城市合并数据】")
print(f"数据范围: {daily_data.index.min()} 到 {daily_data.index.max()}")
print(f"总天数: {len(daily_data)}")
print(f"日均包裹: {daily_data['orders'].mean():.0f}")
print(f"最大值: {daily_data['orders'].max():,}")
print(f"最小值: {daily_data['orders'].min():,}")

# 看看包裹量最少的几天
print("\n包裹量最少的10天:")
print(daily_data.nsmallest(10, 'orders'))

输出:

5城市合并数据概览:
  数据范围: 2022-05-01 到 2022-10-31
  总天数: 184
  日均包裹: 24536
  最大值: 49,408  (2022-10-26)
  最小值: 10,296  (2022-05-03)

包裹量最少的10天:
  2022-05-03: 10,296单
  2022-05-04: 10,880单
  2022-05-08: 11,079单
  2022-05-09: 11,350单
  2022-05-12: 11,991单
  2022-05-10: 12,043单
  2022-05-14: 12,049单
  2022-05-22: 12,067单
  2022-05-11: 12,185单
  2022-05-23: 12,208单

包裹量最少的10天全是5月!而且最低只有10,296单,不到日均值的一半。

是数据采集问题?还是业务真的暴跌了?

2.2 可视化确认

画张图看看趋势:

plt.figure(figsize=(14, 6))
plt.plot(daily_data.index, daily_data['orders'], linewidth=1.5)
plt.axhline(y=daily_data['orders'].mean(), color='green', linestyle='--',
            label=f'均值 {daily_data["orders"].mean():.0f}单', alpha=0.6)
plt.axvspan(pd.Timestamp('2022-05-01'), pd.Timestamp('2022-05-31'),
            alpha=0.15, color='red', label='5月异常')
plt.title('5城市合并日包裹量(2022年5月1日-10月31日)', fontsize=14)
plt.ylabel('包裹量')
plt.xlabel('日期')
plt.legend()
plt.grid(alpha=0.3)
plt.tight_layout()
plt.savefig('daily_volume_anomaly.png', dpi=150)
图片

图上看得更清楚:5月是个大坑,一直到6月才爬上来。

这到底发生了什么?

三、跨城市对比

如果是数据采集故障,应该所有城市都异常。如果只有个别城市异常,那就是业务问题。

3.1 逐城市分析

先看看每个城市的情况:

for filename, city in cities.items():
    df_city = df_all[df_all['city'] == city]
    daily_city = df_city.groupby('ds').size().reset_index(name='orders')
    daily_city = daily_city.set_index('ds').sort_index()

    print(f"\n【{city}】")
    print(f"  数据范围: {daily_city.index.min()} 到 {daily_city.index.max()}")
    print(f"  总天数: {len(daily_city)}")
    print(f"  日均包裹: {daily_city['orders'].mean():.0f}")
    print(f"  最大值: {daily_city['orders'].max():,}")
    print(f"  最小值: {daily_city['orders'].min():,}")

    # 显示包裹量最少的3天
    print(f"  包裹量最少的3天:")
    for date, row in daily_city.nsmallest(3, 'orders').iterrows():
        pct = row['orders'] / daily_city['orders'].mean() * 100
        print(f"    {date.strftime('%Y-%m-%d')}: {row['orders']:,}单 (日均的{pct:.1f}%)")

输出:

【上海】
  数据范围: 2022-05-01 到 2022-10-31
  总天数: 184
  日均包裹: 8,064
  最大值: 17,381
  最小值: 438
  包裹量最少的3天:
    2022-05-13: 438单 (日均的5.4%)
    2022-05-01: 466单 (日均的5.8%)
    2022-05-15: 466单 (日均的5.8%)

【杭州】
  数据范围: 2022-05-01 到 2022-10-31
  总天数: 184
  日均包裹: 10,117
  最大值: 17,800
  最小值: 5,953
  包裹量最少的3天:
    2022-05-03: 5,953单 (日均的58.8%)
    2022-05-09: 6,114单 (日均的60.4%)
    2022-06-15: 6,210单 (日均的61.4%)

【重庆】
  数据范围: 2022-05-01 到 2022-10-31
  总天数: 184
  日均包裹: 5,062
  最大值: 14,172
  最小值: 2,281
  包裹量最少的3天:
    2022-06-15: 2,281单 (日均的45.1%)
    2022-08-29: 2,661单 (日均的52.6%)
    2022-08-28: 2,675单 (日均的52.8%)

【烟台】
  数据范围: 2022-05-01 到 2022-10-31
  总天数: 184
  日均包裹: 1,122
  最大值: 2,252
  最小值: 603
  包裹量最少的3天:
    2022-05-24: 603单 (日均的53.7%)
    2022-05-23: 683单 (日均的60.9%)
    2022-05-14: 725单 (日均的64.6%)

【吉林】
  数据范围: 2022-05-12 到 2022-10-28
  总天数: 163
  日均包裹: 193
  最大值: 434
  最小值: 1
  包裹量最少的3天:
    2022-10-20: 1单 (日均的0.5%)
    2022-10-26: 1单 (日均的0.5%)
    2022-10-28: 1单 (日均的0.5%)
图片

第一个线索:上海5月异常低

上海最低438单(5月13日),是日均8,064单的5.4%!而且包裹量最少的3天全在5月。

第二个线索:吉林10月突变

吉林10月20日之后降到1单,从日均193单骤降到0.5%。

但其他城市呢?杭州、重庆、烟台的最低值都不在5月或10月。

3.2 热力图分析

画个热力图,更直观地看跨城市的异常模式:

# 构建跨城市热力图数据
city_daily = df_all.groupby(['ds', 'city']).size().reset_index(name='orders')
pivot_data = city_daily.pivot(index='ds', columns='city', values='orders')

# 计算每个城市相对于其均值的比率
pivot_ratio = pivot_data / pivot_data.mean()

# 找出异常日期:任一城市低于其均值的30%
anomaly_threshold = 0.3
anomaly_days = pivot_ratio[pivot_ratio.min(axis=1) < anomaly_threshold].index

print(f"发现 {len(anomaly_days)} 个异常日期")
print("(判断标准:任一城市的包裹量 < 该城市平均值的30%)")

# 画热力图
plt.figure(figsize=(20, 6))
sns.heatmap(pivot_ratio.T, cmap='RdYlGn', center=1.0,
            vmin=0, vmax=2, cbar_kws={'label': '相对均值比率'},
            linewidths=0.5, annot=False)
plt.title('各城市包裹量热力图(相对于均值,2022-05至10月)', fontsize=14)
plt.xlabel('日期')
plt.ylabel('城市')
plt.tight_layout()
plt.savefig('cross_city_anomaly_heatmap.png', dpi=150, bbox_inches='tight')

输出:

发现 55 个异常日期
(判断标准:任一城市的包裹量 < 该城市平均值的30%)

热力图非常直观!

看到两个明显的异常模式:

  1. 5月上海一片深红(包裹量极低)
  2. 10月中下旬吉林深红,但其他城市都是深绿(高位)

四、推理真相

现在证据很明显:

4.1 异常模式1:上海5月

  • 上海5月深度异常(最低日均5.4%)
  • 同期其他城市相对正常(杭州58.8%,烟台53.7%)
  • 6月上海快速恢复

4.2 异常模式2:吉林10月

  • 吉林10月中下旬骤降(降到0.5%)
  • 同期其他4城市正常,甚至更高

这不是数据故障,是真实的业务中断。

4.3 查证新闻

真相1:上海4-5月疫情封控

2022年4-5月,上海因疫情封控,物流基本停摆。

数据验证:

sh_data = city_daily['上海']
sh_may = sh_data.loc['2022-05-01':'2022-05-31']
sh_normal = sh_data.loc['2022-06-15':'2022-09-30']

print(f"上海5月日均: {sh_may['orders'].mean():.0f}单")
print(f"上海正常期日均: {sh_normal['orders'].mean():.0f}单")
print(f"5月相对正常期: {sh_may['orders'].mean()/sh_normal['orders'].mean()*100:.1f}%")

输出:

上海5月日均: 675单
上海正常期日均: 9,367单
5月相对正常期: 7.2%

上海5月包裹量暴跌92.8%!

这完美解释了为什么:

  • 5月包裹量骤降
  • 只有上海深红
  • 6月解封后快速恢复

真相2:吉林10月疫情管控

2022年10月中旬,吉林市再次出现疫情。

验证一下其他城市10月的情况:

oct_late = pivot_ratio.loc['2022-10-15':'2022-10-31']
print("10月中下旬各城市对比(相对各自均值):")
for city in ['上海', '杭州', '重庆', '烟台', '吉林']:
    val = oct_late[city].mean()
    print(f"  {city}: {val*100:.1f}%")

输出:

10月中下旬各城市对比(相对各自均值):
  上海: 153.7% (反而更高!)
  杭州: 133.4% (反而更高!)
  重庆: 223.6% (暴涨!)
  烟台: 112.8% (正常)
  吉林: 7.6% (崩了)

真相大白:疫情导致两次明显的业务中断。

五、除了疫情,还有什么规律?

虽然有疫情干扰,但数据里应该还有其他规律。我选取6-9月相对正常的数据进行分析。

5.1 规律1:618促销冲击

6月中旬出现巨大尖峰。标记618前后对比:

jun_data = daily_data.loc['2022-06-01':'2022-06-30']
jun_normal = jun_data[~jun_data.index.isin(pd.date_range('2022-06-15', '2022-06-20'))]
jun_618 = jun_data.loc['2022-06-15':'2022-06-20']

print("618促销分析:")
print(f"  6月正常日均: {jun_normal['orders'].mean():.0f}单")
print(f"  618期间日均: {jun_618['orders'].mean():.0f}单")
print(f"  增幅: {(jun_618['orders'].mean()/jun_normal['orders'].mean()-1)*100:.1f}%")
print(f"  618峰值: {jun_618['orders'].max():,}单")

输出:

618促销分析:
  6月正常日均: 20,564单
  618期间日均: 28,794单
  增幅: 40.0%
  618峰值: 35,522单

618期间包裹量暴涨40%!

5.2 规律2:整体上升趋势

业务在增长:

x = np.arange(len(normal_period))
y = normal_period['orders'].values
coeffs = np.polyfit(x, y, 1)
daily_growth = coeffs[0]

print("整体增长趋势(6-9月):")
print(f"  每天增长: {daily_growth:.1f}单")
print(f"  月增长: {daily_growth*30:.0f}单")
print(f"  月增长率: {daily_growth*30/normal_period['orders'].mean()*100:.1f}%")

输出:

整体增长趋势(6-9月):
  每天增长: 68.9单
  月增长: 2,066单
  月增长率: 8.4%

业务月增长8.4%,市场在扩张。

六、还要不要做预测?

现在我有了完整的线索:

数据特征:

  • 有促销影响(618暴涨40%)
  • 有增长趋势(月增8.4%)
  • 有疫情黑天鹅(无法预测)
  • 只有半年数据(缺年周期)

做预测的挑战:

  1. 疫情完全随机,不可预测
  2. 疫情期间的数据不能用来预测正常期
  3. 半年数据缺少完整年度周期
  4. 上海5月数据异常,会严重影响模型训练

我的决定:

不做预测了。原因很简单:

  1. 数据质量决定预测上限:5月数据异常占比太大(1/6的数据),无论用什么模型都会被污染
  2. 黑天鹅无法预测:疫情这种系统性风险,历史数据学不到
  3. 半年数据太短:缺少完整年度周期,无法捕捉季节性规律

但深度分析已经给了我们宝贵的洞察。

七、深度分析才是宝藏

虽然不做预测,但通过数据分析,获得了宝贵的业务洞察。

7.1 洞察1:疫情对快递业的非对称影响

  • 上海5月:大城市封控,冲击巨大(跌92.8%)
  • 吉林10月:小城市局部封控(跌93.3%)
  • 杭州5月:虽受影响但较小(仍有58.8%)

启示

  • 大城市网络更脆弱
  • 但恢复能力也更强(上海6月下旬已恢复63%)
  • 风险分散很重要

7.2 洞察2:促销日影响巨大

618期间增长40%,双11可能更高。

启示

  • 大促前2周开始备人力
  • 不能只看历史日均
  • 需要促销日历

7.3 洞察3:业务在增长

月增长8.4%,市场在扩张。

启示

  • 人力需求持续增加
  • 网点布局要提前规划
  • 设备投资回报期缩短

八、总结

这次"预测实战"最大的收获不是模型准确率,而是:

8.1 数据侦探的思维方式

  • 从异常出发 → 提出假设 → 交叉验证 → 揭示真相
  • 不迷信数据,要质疑数据
  • 结合外部信息(新闻、政策)

8.2 真实数据的挑战

  • 疫情这种黑天鹅无法预测
  • 半年数据有局限性
  • 但不妨碍做分析、找规律

8.3 分析比预测更重要

  • 预测只是工具,业务洞察才是目标
  • 宁可不预测,也不能误导决策
  • 诚实对待数据局限,比虚假的高准确率更有价值

下一篇:第10篇:配送路线优化VRP实战