23  物流行业数据分析

23.1 引言物流数据分析的维度

物流行业关键指标:

  • 时效性: 按时交货率
  • 质量: 产品合格率、返修率
  • 区域: 不同区域表现
  • 货品: 不同货品特点

23.2 本章学习目标

本章以一份物流行业订单数据为例,走完”清洗、转换、多维分析”的完整链路。通过本章学习,你将掌握:

  1. 业务数据的清洗要点:指定编码读取、去重、删缺失值、删无关列、重置索引
  2. map 加自定义函数统一金额单位(“万元”/“元”混合→元)
  3. groupby + size + unstack 构造”维度×交货状况”交叉频数表,并在其上计算比率指标
  4. 按月份、销售区域、货品及交叉维度层层下钻的分析路径
  5. 把按时交货率、拒货率、合格率、返修率等指标转译为经营判断

先修内容:第 章节 13 章(分组聚合与重塑)与第 章节 16 章(缺失值的识别与处理);字符串清洗的相关做法可回看第 章节 17 章。

23.3 数据清洗

任务要求:读取平台内置的 data_wuliu.csv(gbk编码),去重、删除缺失值、删除”订单行”列并重置索引;定义 data_deal 函数把”万元”/“元”混合的销售金额统一为元,删除金额为0的记录;将销售、交货时间转为datetime并提取月份;随后完成三组研究——(1)分别按月份、销售区域、货品及货品×区域交叉统计按时交货率;(2)按月份、区域、货品统计销量并绘制月度销售折线图,寻找尚有潜力的销售区域;(3)按货品×区域统计拒货率、合格率、返修率,识别存在质量问题的货品。

列表 23.1: 平台原始代码
# 注:data_wuliu.csv数据文件本地没有,但平台已经内置
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import os  # 导入操作系统接口模块
import pandas as pd  # 导入Pandas数据分析库
import numpy as np  # 导入NumPy数值计算库
import matplotlib.pyplot as plt  # 导入Matplotlib绘图库
plt.rcParams['font.sans-serif'] = 'SimHei' ## 设置中文显示

## 数据清洗部分
data = pd.read_csv('data_wuliu.csv',encoding='gbk')

#删除重复记录
data.drop_duplicates(keep='first',inplace=True) #keep='first'默认值,保留第一次出现的重复记录
#删除缺失值
data.dropna(axis=0,how='any',inplace=True) 
#删除订单行
data.drop(columns=['订单行'],inplace=True,axis=1) 
print(data.info())  # 输出数据框基本信息

#更新索引(drop=True:把原来的索引index列删除,重置index)
data.reset_index(drop=True,inplace=True)

def data_deal(number):  # 定义函数data_deal
    if number.find('万元')!= -1:#找到带有万元的,取出数字,去掉逗号,转成float,*10000
        number_new = float(number[:number.find('万元')].replace(',',''))*10000  # 替换数据中的指定值
        pass  # 占位符,暂不执行任何操作
    else: #找到带有元的,删除元,删除逗号,转成float
        number_new = float(number.replace('元','').replace(',',''))  # 替换数据中的指定值
        pass  # 占位符,暂不执行任何操作
    return number_new  # 返回计算结果
data['销售金额'] = data['销售金额'].map(data_deal)  # 对数据进行映射转换
print(data.describe())  # 输出描述性统计信息

#销售金额为0的情况,删除
data = data[data['销售金额']!=0].copy()
data['销售时间'] = pd.to_datetime(data['销售时间'])  # 转换为日期时间格式
data['交货时间'] = pd.to_datetime(data['交货时间'])  # 转换为日期时间格式
data['销售月份'] = data['销售时间'].dt.month  # 提取日期时间属性
data['交货月份'] = data['交货时间'].dt.month  # 提取日期时间属性

#1.配送是否存在问题研究
#月份维度
data['货品交货状况'] = data['货品交货状况'].str.strip() 
data1 = data.groupby(['销售月份','货品交货状况']).size().unstack() #unstack()把行索引变成列索引
data1['按时交货率'] = data1['按时交货']/(data1['按时交货']+data1['晚交货'])  # 提取按时交货列作为data1['按时交货率']变量
print("月份交货状况:\n",data1)  # 输出月份交货状况:\n

data1 = data.groupby(['销售区域','货品交货状况']).size().unstack()  # 按指定列分组聚合
data1['按时交货率'] = data1['按时交货']/(data1['按时交货']+data1['晚交货'])  # 提取按时交货列作为data1['按时交货率']变量
data1 = data1.sort_values(data1.columns[-1],ascending=False)  # 按指定列排序
print("销售区域交货状况:\n",data1)  # 输出销售区域交货状况:\n
#泰国地区的按时交货率最高,西北地区的按时交货率最低,而且非常明显,可以看出,西北地区的配送服务存在问题,需要重点关注

#货品维度
data1 = data.groupby(['货品','货品交货状况']).size().unstack()
data1['按时交货率'] = data1['按时交货']/(data1['按时交货']+data1['晚交货'])  # 提取按时交货列作为data1['按时交货率']变量
data1 = data1.sort_values(data1.columns[-1],ascending=False)  # 按指定列排序
print("不同货品交货状况:\n",data1)  # 输出不同货品交货状况:\n
#可以看出货品4的按时交货率最低

#货品和销售区域结合
data1 = data.groupby(['货品','销售区域','货品交货状况']).size().unstack()
data1['按时交货率'] = data1['按时交货']/(data1['按时交货']+data1['晚交货'])  # 提取按时交货列作为data1['按时交货率']变量
data1 = data1.sort_values(data1.columns[-1],ascending=False)  # 按指定列排序
print("货品和销售区域结合的交货状况:\n",data1)  # 输出货品和销售区域结合的交货状况:\n

#2.是否存在尚有潜力的销售区域问题研究
#月份维度
data_month = data.groupby(['销售月份','货品'])['数量'].sum().unstack()
print("月份维度货品的销售情况:\n",data_month)  # 输出月份维度货品的销售情况:\n

# 绘制各个月份的销售情况
data_month.plot(kind='line',figsize=(12,6))
plt.title('各个月份的销售情况')  # 设置图表标题
plt.savefig("1.png")  # 保存图形至文件
#货品2在10月和12月销量猛增,有可能是公司加大了营销力度,也有可能是开发了新的市场

#销售区域维度
data_region = data.groupby(['销售区域','货品'])['数量'].sum().unstack()
print("销售区域货品的销售情况:\n",data_region)  # 输出销售区域货品的销售情况:\n
#从销售区域看,每种货品销售区域为1-3个,货品1有三个销售区域,货品2有两个销售区域,货品3、4、5、6只有一个销售区域,货品2在华东地区还有较大市场空间,适合加大投入,同时货品2在西北配送时效长,用户拒收率高,从成本角度考虑,应该减少投入。

#月份和区域结合
data_month_region = data.groupby(['销售月份','销售区域','货品'])['数量'].sum().unstack()
data_month_region['货品2']  # 查看货品2在各月份和区域的销售数量

#货品2没有开发新的市场,而是在华东地区加大了营销力度 可以在7,8,9月份加大华东地区的营销力度

#3.商品是否存在质量问题研究
data['货品用户反馈'] = data['货品用户反馈'].str.strip()
data3 = data.groupby(['货品','销售区域'])['货品用户反馈'].value_counts().unstack()  # 按指定列分组聚合
data3['拒货率'] = data3['拒货']/data3.sum(axis=1)  # 计算总和
data3['合格率'] = data3['质量合格']/data3.sum(axis=1)  # 计算总和
data3['返修率'] = data3['返修']/data3.sum(axis=1)  # 计算总和
# 按合格率、返修率、拒货率降序排列以识别质量问题货品
data3.sort_values(['合格率','返修率','拒货率'],ascending=False)
#货品3 6 5 的合格率较高,返修率较低,货品1 2 4 的合格率较低,返修率较高,货品1、2、4质量存在问题,建议扩大抽检范围,增大质检力度 货品2在马来西亚的拒货率最高,按时交货率最低,猜测马来西亚对时效性要求较高,需要提高时效性。货品2在马来西亚的销售量较低,可以考虑减少投入。

预期输出:数据文件平台内置,本地无数据,具体数值以平台运行结果为准。判读要点:

  1. 文本输出:data.info()data.describe() 摘要;四张交货状况表(按月份、按销售区域、按货品、按货品×区域)以及月度、区域维度的货品销量表。判读要点:比较”按时交货率”列的高低排序,记住每张表的分母口径是”按时交货+晚交货”的订单数。
  2. 图形输出(1.png):各月各货品销量的折线图。判读要点:哪条货品曲线在哪些月份明显抬升、各曲线的相对位置反映的畅销与长尾格局。
  3. 平台原始代码的块内注释保留了作者的判读线索(如西北地区按时交货率最低、货品2在10月和12月销量猛增),可作读图参照,最终以平台运行的实际表格为准。

下方提供一个本地演练版:由于 data_wuliu.csv 仅平台内置,本地以一份 12 行的同结构内联合成迷你表代替(列名与平台数据一致,取值为演示流程而设的模拟值,其中两行”货品交货状况”故意带前后空格,用于演示 str.strip() 的必要性),并在其上复现平台任务中”按销售区域统计按时交货率”的同型输出:

列表 23.2: 本地演练版(合成数据)
# 本地演练说明:平台内置的data_wuliu.csv本地没有,以下用同结构的内联合成迷你数据代替
import pandas as pd  # 导入Pandas数据分析库
data_mini = pd.DataFrame({  # 构造12行迷你表,列结构同平台数据
    '销售时间': ['2023-01-05', '2023-01-18', '2023-02-02', '2023-02-14', '2023-03-03', '2023-03-15',
                 '2023-04-08', '2023-04-19', '2023-05-06', '2023-05-21', '2023-06-09', '2023-06-20'],  # 销售发生日期
    '交货时间': ['2023-01-09', '2023-01-30', '2023-02-06', '2023-02-16', '2023-03-05', '2023-04-02',
                 '2023-04-11', '2023-04-22', '2023-05-09', '2023-05-24', '2023-06-13', '2023-07-08'],  # 实际交货日期
    '销售区域': ['华东', '西北', '华东', '马来西亚', '西北', '华东', '西北', '马来西亚', '华东', '西北', '华东', '马来西亚'],  # 销售区域
    '货品': ['货品1', '货品2', '货品1', '货品2', '货品4', '货品2', '货品4', '货品2', '货品1', '货品4', '货品2', '货品2'],  # 货品
    '数量': [10, 6, 8, 3, 5, 12, 4, 2, 9, 5, 15, 3],  # 销售数量
    '销售金额': ['1.2万元', '8,500元', '9,600元', '2万元', '7,200元', '1.5万元',
                 '6,800元', '1.8万元', '8,800元', '7,500元', '1.6万元', '2.2万元'],  # 万元/元混合单位,同平台数据形态
    '货品交货状况': ['按时交货', '晚交货', '按时交货', '晚交货', ' 按时交货', '按时交货',
                    '晚交货', '按时交货', '晚交货', '晚交货 ', '按时交货', '按时交货'],  # 两行故意带空格,演示strip的必要性
    '货品用户反馈': ['质量合格', '返修', '质量合格', '拒货', '质量合格', '质量合格',
                    '返修', '拒货', '质量合格', '质量合格', '质量合格', '质量合格']  # 用户反馈状态
})
data_mini['货品交货状况'] = data_mini['货品交货状况'].str.strip()  # 与平台任务一致,先去掉状态字段的前后空格
rate_region = data_mini.groupby(['销售区域', '货品交货状况']).size().unstack()  # 区域×交货状况交叉频数表
rate_region['按时交货率'] = rate_region['按时交货'] / (rate_region['按时交货'] + rate_region['晚交货'])  # 分母口径与平台任务一致
rate_region = rate_region.sort_values('按时交货率', ascending=False)  # 按按时交货率降序排序
print('销售区域交货状况:')
print(rate_region)

预期输出(本机实际运行结果):

销售区域交货状况:
货品交货状况  按时交货  晚交货     按时交货率
销售区域                       
华东         4    1  0.800000
马来西亚       2    1  0.666667
西北         1    3  0.250000

判读要点与平台任务同型:迷你表中西北地区按时交货率最低(0.25)、华东最高(0.80)。若先不做 str.strip(),带空格的” 按时交货”会被 unstack() 单列成一列,交叉表中将出现多余的空值列——这正是平台任务先清洗再分组的用意。

23.4 数据转换

列表 23.3: 销售金额数据转换
# 注:本块是平台任务的分步讲解版(重述销售金额转换部分)。若已在平台运行过完整任务,请勿紧接着重复运行本块(销售金额列已被转为 float,再执行字符串清洗会报 AttributeError);建议在平台新会话中单独按块顺序运行讲解块

# =============================================================================
# 题目:销售金额数据的标准化转换
# =============================================================================
# 本代码处理销售金额列,将不同单位(万元、元)统一转换为元。数据转换
# 确保所有数值具有相同的量纲,便于后续分析和比较。同时还处理日期列,
# 提取时间特征(月份)用于时间序列分析。

# ==================== 定义数据转换函数 ====================
def data_deal(number):
    """
    处理销售金额,统一为元

    参数:
        number: 销售金额字符串,可能包含'万元'或'元'单位

    返回:
        float: 转换后的金额(单位:元)
    """
    # 检查字符串中是否包含'万元'
    if number.find('万元') != -1:
        # 如果包含'万元',提取数字部分并乘以10000转换为元
        number_new = float(number[:number.find('万元')].replace(',', '')) * 10000
        # number[:number.find('万元')]提取'万元'之前的数字部分
        # .replace(',', '')删除千位分隔符(如"12,345"变为"12345")
        # float()将字符串转换为浮点数
        # * 10000将万元转换为元
    else:
        # 如果不包含'万元',假设单位为元,直接转换
        number_new = float(number.replace('元', '').replace(',', ''))
        # .replace('元', '')删除'元'字符
        # .replace(',', '')删除千位分隔符
        # float()转换为浮点数
    return number_new  # 返回转换后的数值

# ==================== 应用转换函数 ====================
# 使用map函数将转换函数应用到销售金额列的每个元素
data['销售金额'] = data['销售金额'].map(data_deal)
# map()方法对Series的每个元素应用指定函数
# data_deal函数处理每个销售金额值
# 转换后,所有销售金额都是数值类型,单位为元

# ==================== 删除异常值 ====================
# 删除销售金额为0的记录(可能是退单或数据错误)
data = data[data['销售金额'] != 0].copy()
# data['销售金额'] != 0创建布尔Series,筛选金额不为0的行
# .copy()创建新副本,避免SettingWithCopyWarning警告
# 删除异常值可以提高数据质量,避免影响分析结果

# ==================== 日期转换 ====================
# 将销售时间列转换为datetime类型
data['销售时间'] = pd.to_datetime(data['销售时间'])
# pd.to_datetime将字符串转换为datetime对象
# 转换后可以使用.dt访问器提取时间特征(年、月、日、星期等)

# 将交货时间列转换为datetime类型
data['交货时间'] = pd.to_datetime(data['交货时间'])
# 同样转换为datetime类型,便于计算时间差

# ==================== 提取时间特征 ====================
# 从销售时间中提取月份(1-12)
data['销售月份'] = data['销售时间'].dt.month
# .dt.month返回月份的数值表示
# 用于分析销售的季节性规律

# 从交货时间中提取月份
data['交货月份'] = data['交货时间'].dt.month
# 同样提取交货月份,用于分析交货的月度分布

# ==================== 预览转换结果 ====================
# 打印转换后的数据前几行
print("数据转换完成:")
print(data[['销售金额', '销售时间', '交货时间']].head())
# head()默认显示前5行
# 选择这三列进行预览,确认转换成功

# ==================== 输出解读 ====================
# 数据转换使数据更适合分析:
# 1. 单位统一:所有金额都转换为元,便于比较和计算
#    例如: "5万元" → 50000, "12345元" → 12345
# 2. 日期转换:字符串 → datetime对象,可以:
#    - 计算时间差(交货周期 = 交货时间 - 销售时间)
#    - 提取时间特征(年、月、日、星期)
#    - 按时间排序和分组
# 3. 删除异常值:金额为0可能是数据错误或退单,删除可以提高分析准确性
# 4. 提取特征:销售月份和交货月份可用于:
#    - 分析季节性规律(如11月、12月销售是否增长)
#    - 对比不同月份的按时交货率
#    - 识别业务高峰期

23.5 配送问题分析

列表 23.4: 按时交货率的多维度分析
# 注:该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)

# =============================================================================
# 题目:按时交货率的多维度对比分析
# =============================================================================
# 本代码从月份和销售区域两个维度分析按时交货率,识别配送服务的薄弱环节。
# 按时交货率是物流服务的核心KPI,直接影响客户满意度。通过多维度分析,
# 可以发现系统性问题(如特定区域或时段的服务质量下降)。

# ==================== 清洗交货状态数据 ====================
# 去除货品交货状况列的前后空格
data['货品交货状况'] = data['货品交货状况'].str.strip()
# .str.strip()删除字符串开头和结尾的空白字符
# 数据录入时可能包含多余的空格,如" 按时交货 " → "按时交货"
# 清洗后可以正确分组统计

# ==================== 月份维度分析 ====================
# 按销售月份和交货状况分组,统计订单数量
data1 = data.groupby(['销售月份', '货品交货状况']).size().unstack()
# groupby按多列分组(销售月份、交货状况)
# .size()计算每个组合的数量
# .unstack()将分组索引转换为列,使数据更易读
# 结果格式:行=月份,列=交货状况('按时交货','晚交货'),值=订单数

# 计算每个月的按时交货率
data1['按时交货率'] = data1['按时交货'] / (data1['按时交货'] + data1['晚交货'])
# 按时交货率 = 按时交货订单数 / 总订单数
# 结果是0-1之间的小数,如0.95表示95%按时交货

# 打印月份维度的交货状况
print("月份交货状况:")
print(data1)  # 显示每个月的订单数和按时交货率

# ==================== 销售区域维度分析 ====================
# 按销售区域和交货状况分组,统计订单数量
data1 = data.groupby(['销售区域', '货品交货状况']).size().unstack()
# 同样的groupby操作,但分组变量改为销售区域
# 这样可以对比不同地区的服务质量

# 计算每个销售区域的按时交货率
data1['按时交货率'] = data1['按时交货'] / (data1['按时交货'] + data1['晚交货'])
# 公式相同,但现在是按区域计算

# 按按时交货率降序排序
data1 = data1.sort_values(data1.columns[-1], ascending=False)
# data1.columns[-1]是最后一列('按时交货率')
# ascending=False表示降序(按时交货率高的排在前面)
# 排序后可以快速识别表现最好和最差的区域

# 打印销售区域维度的交货状况
print("\n销售区域交货状况:")
print(data1)  # 显示每个区域的订单数和按时交货率

# ==================== 输出解读 ====================
# 多维度分析可以发现不同层面的业务问题:
#
# 1. 月份维度:
#    - 识别特定月份的服务质量下降(如双11、春节期间可能晚交货率上升)
#    - 分析季节性规律,提前准备资源
#    - 例如:12月晚交货率可能高于其他月份,因为年尾订单量大
#
# 2. 区域维度:
#    - 识别服务薄弱区域(如西北地区按时交货率最低)
#    - 优秀区域可以作为标杆,学习最佳实践
#    - 例如:华东地区按时交货率98%,西北地区仅85%,需要改进西北物流
#
# 3. 改进建议:
#    - 对按时交货率低的区域增加配送资源
#    - 在高峰期(如12月)提前做好运力储备
#    - 分析晚交货订单的共同特征,找出根本原因
#    - 建立预警机制,按时交货率低于阈值时自动报警

23.6 销售潜力分析

列表 23.5
# 注:该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)

# =============================================================================
# 题目:货品销售的月度趋势分析
# =============================================================================
# 本代码分析不同货品在各月的销售数量,通过折线图可视化销售趋势,
# 识别热销产品和销售高峰期。销售潜力分析有助于优化库存管理和营销策略。

# ==================== 月份维度货品销售统计 ====================
# 按销售月份和货品分组,统计销售数量
data_month = data.groupby(['销售月份', '货品'])['数量'].sum().unstack()
# groupby按月份和货品两个维度分组
# ['数量']选择要聚合的列(销售数量)
# .sum()计算每组销售数量的总和
# .unstack()将货品从索引转为列,使数据更适合可视化

# 打印月份维度的货品销售情况
print("月份维度货品销售情况:")
print(data_month)  # 显示每个月各货品的销售数量

# ==================== 可视化销售趋势 ====================
# 绘制折线图,展示各货品销售数量随时间的变化
data_month.plot(kind='line', figsize=(12, 6))
# kind='line'指定绘制折线图(也可以简写为data_month.plot())
# figsize=(12, 6)设置图表尺寸为12x6英寸
# 每个货品一条线,不同颜色区分

# 设置图表标题
plt.title('各个月份的销售情况')  # 标题说明图表内容

# 设置x轴标签
plt.xlabel('月份')  # x轴表示月份(1-12)

# 设置y轴标签
plt.ylabel('销售数量')  # y轴表示销售数量

# 添加网格线,便于读取数值
plt.grid(True, alpha=0.3)  # alpha=0.3设置网格线透明度

# 添加图例
plt.legend(title='货品')  # 图例标题为"货品"

# 自动调整布局并显示图表
plt.tight_layout()  # 防止标签重叠
plt.show()  # 显示完整的折线图

# ==================== 输出解读 ====================
# 销售趋势分析可以揭示多种商业洞察:
#
# 1. 季节性规律:
#    - 某些货品在特定月份销量激增(如空调在夏季销量高)
#    - 识别规律后可以提前备货,避免缺货
#
# 2. 货品表现对比:
#    - 货品2和货品3是畅销品,销售曲线持续高于其他货品
#    - 货品4销量较低,可能需要促销或淘汰
#    - 货品1在10月和12月销量猛增,可能有促销活动
#
# 3. 业务决策支持:
#    - 库存管理:根据预测备货,减少库存积压
#    - 营销策略:在淡季推出促销,刺激销售
#    - 产品策略:淘汰滞销品,增加畅销品库存
#    - 人员安排:在高峰期增加临时工
#
# 4. 可视化优势:
#    - 折线图直观展示趋势和变化
#    - 多条线对比容易发现规律
#    - 异常点(如突然的峰值)一目了然

23.7 质量问题分析

列表 23.6: 产品质量问题分析
# 注:该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)

# =============================================================================
# 题目:基于用户反馈的产品质量分析
# =============================================================================
# 本代码分析货品的质量问题,包括拒货率、合格率、返修率等指标。质量
# 分析是质量管理的重要环节,可以识别问题产品和问题区域,指导改进措施。

# ==================== 清洗用户反馈数据 ====================
# 去除货品用户反馈列的前后空格
data['货品用户反馈'] = data['货品用户反馈'].str.strip()
# .str.strip()删除字符串开头和结尾的空白字符
# 用户反馈可能有"质量合格"、"拒货"、"返修"等值
# 清洗后可以正确分类统计

# ==================== 按货品和区域统计反馈 ====================
# 按货品和销售区域分组,统计各种反馈的数量
data3 = data.groupby(['货品', '销售区域'])['货品用户反馈'].value_counts().unstack()
# groupby按货品和销售区域两个维度分组
# ['货品用户反馈']是要统计的列
# .value_counts()计算每种反馈的次数
# .unstack()将反馈类型从索引转为列

# ==================== 计算各类质量指标 ====================
# 计算拒货率(拒货数量 / 总反馈数量)
data3['拒货率'] = data3['拒货'] / data3.sum(axis=1)
# data3.sum(axis=1)计算每行的总和(即该货品在该区域的总反馈数)
# 拒货率 = 拒货数 / 总数
# 拒货通常指客户拒绝收货,可能因货损、错发等原因

# 计算合格率(质量合格数量 / 总反馈数量)
data3['合格率'] = data3['质量合格'] / data3.sum(axis=1)
# 合格率 = 质合格数 / 总数
# 质量合格表示产品符合标准,客户满意

# 计算返修率(返修数量 / 总反馈数量)
data3['返修率'] = data3['返修'] / data3.sum(axis=1)
# 返修率 = 返修数 / 总数
# 返修表示产品有问题,需要维修或更换

# ==================== 排序 ====================
# 按合格率、返修率、拒货率的优先级排序
data3 = data3.sort_values(['合格率', '返修率', '拒货率'], ascending=False)
# 按多列排序,优先级从左到右
# ascending=False表示降序(合格率高的排在前面)
# 排序后可以快速识别质量最好和最差的产品-区域组合

# ==================== 输出质量分析结果 ====================
# 打印货品质量分析表
print("货品质量分析:")
print(data3)  # 显示每个货品在每个区域的质量指标

# ==================== 输出解读 ====================
# 质量分析揭示了产品和服务的多个方面:
#
# 1. 质量指标解读:
#    - 合格率:越高越好,表示产品符合客户预期
#    - 返修率:越低越好,高返修率说明产品可靠性差
#    - 拒货率:越低越好,高拒货率说明配送或产品有问题
#
# 2. 问题识别:
#    - 货品1、2、4的合格率较低,需要改进产品质量
#    - 货品2在马来西亚拒货率高,可能是国际物流问题
#    - 某些区域返修率高,可能需要检查当地配送条件
#
# 3. 改进措施:
#    - 产品:提高质量控制,加强出厂检验
#    - 包装:改进包装,减少运输损坏
#    - 配送:优化物流流程,提升服务质量
#    - 售后:建立快速响应机制,及时处理问题
#
# 4. 管理决策:
#    - 暂停质量问题产品的发货
#    - 对问题区域进行专项整改
#    - 建立质量追溯体系,找出问题根源
#    - 定期监控质量指标,持续改进

23.8 分析结论

配送问题:

  • 西北地区按时交货率最低,需重点关注
  • 货品4按时交货率最低

销售潜力:

  • 货品2在华东地区有增长空间
  • 10月和12月销量猛增,可能因促销活动

质量问题:

  • 货品1、2、4合格率较低
  • 货品2在马来西亚拒货率高

23.9 本章小结

本章新增的技能要点:

  1. 先清洗后分析:gbk编码读取、drop_duplicatesdropna、删除无关列、reset_index(drop=True) 五步是业务表进入分析前的标准准备。
  2. 分类字段先去空格再分组:str.strip() 防止”按时交货”与” 按时交货”被当成两类,这是交叉计数的隐形陷阱。
  3. 单位统一是高频坑:对”5万元”/“12,345元”这类混合单位列,map + 自定义函数 + 字符串切片/替换是通用解法。
  4. 交叉计数的标准句式:groupby([维度, 状态]).size().unstack() 得到”维度×状态”频数表,在其上做除法即得比率指标。
  5. 下钻式分析路径:先总体、再单维度、后交叉维度,避免被总量聚合掩盖局部问题。

易错点:

  • 不指定 encoding='gbk' 直接读 data_wuliu.csv 会报 UnicodeDecodeError:中文业务表先确认编码再读取
  • 分组前不做 str.strip():” 按时交货”与”按时交货”被当成两类,unstack() 后多出带空值的列,比率随之失真
  • 对”8,500元”直接 float() 会抛 ValueError:须先去单位、replace(',','') 去千位分隔符,含”万元”的还要乘10000
  • 按时交货率的分母误用全部订单行数:平台口径是”按时交货+晚交货”两列之和;某组只出现一种状态时另一列为 NaN,除法得 NaN,不能当0处理
  • 拒货率、合格率、返修率的分母是 data3.sum(axis=1) 全部反馈数,与按时交货率”两种状态之和”的口径不同,两套分母混用会得出互相矛盾的结论
  • 平台任务跑完后紧接着重跑数据转换讲解块会报 AttributeError:销售金额已转为 float,不能再做字符串清洗

23.10 动手与思考

以下练习每题附参考答案(默认折叠);概念题与变式题可对照自查,商业判断题不设唯一答案,判断依据与口径比结论更重要。

  1. 概念辨析:“按时交货率”的分母为什么是”按时交货+晚交货”两列之和,而不是全部订单行数?若数据中还存在第三种交货状态(如”丢失”),直接沿用该公式会产生什么偏差?

    参考答案(点开前请先独立完成)

    解题思路:分母口径决定比率的含义。平台代码(列表 23.1)把“按时交货+晚交货”两列之和作分母,度量的是“在交货状况已明确为这两类的订单中,按时完成的比例”。若改用全部订单行数作分母,一旦某个月份只出现一种状态(unstack() 后另一列为缺失值)或将来出现第三种状态,两个分母就会给出不同数值——用行数作分母的隐含假设是“每个订单行都能落入某个已知状态列”,这个假设未必成立。若存在第三种状态“丢失”:沿用 按时/(按时+晚) 会把丢失订单整体排除在分母之外,按时交货率被系统性高估、丢失风险被隐藏;更稳妥的做法是先单独算出“丢失率”这样的第三项指标,再决定按时交货率的分母是否应把丢失计入(按时/全部订单),并把所用口径随结论一起报告。

    回扣本章:对应“本章新增的技能要点”第4条(在交叉频数表上做除法得到比率时,分母怎么取就是口径本身)。

  2. 概念辨析:data.groupby(['销售月份','货品交货状况']).size().unstack()data.groupby('销售月份')['货品交货状况'].value_counts().unstack() 想构造同类交叉表时,两者口径有何区别?

    参考答案(点开前请先独立完成)

    解题思路:两种写法得到的是同一张“月份×交货状况”交叉计数表,数值完全一致,差别在“谁在计数、按什么顺序”。写法一先按两个键分组、再用 size() 数每组的行数,unstack() 把内层的“货品交货状况”从行索引转到列上;写法二先按月份分组、再在每组内对该列做 value_counts() 频数统计(组内按频数降序排列),unstack() 仍会按状态取值对齐到列,最终两张表的数值与行列位置相同(在12行迷你合成表上实测,两表 equals 判定完全一致)。真正要警惕的是丢掉“按月分组”这一层:若只写 data['货品交货状况'].value_counts(),得到的是不分月份的总体频数,它只有一层索引,unstack() 之后只剩一行,排不出“各月×各状态”的结构。另外两种写法有共同前提:都必须先 str.strip() 去掉状态字段前后空格,否则“按时交货”与“ 按时交货”会被当成两个状态,交叉表里多出带空值的列。

    回扣本章:对应“本章新增的技能要点”第2条(分类字段先 str.strip() 再分组)与第4条(groupby([维度, 状态]).size().unstack() 是交叉计数的标准句式)。

  3. 变式任务:在每张交货状况表上增加一列”晚交货率”,并按它升序排序找出问题最集中的维度——需要改动哪几行代码?说明思路即可。

    参考答案(点开前请先独立完成)

    改造思路:在每张交叉频数表上只动两行——新增一列“晚交货率”,分母沿用与“按时交货率”完全相同的口径(按时+晚);再把 sort_values 的排序键由最后一列“按时交货率”改为“晚交货率”、ascending 改为 True,问题最集中的维度就排在表首。月份、销售区域、货品、货品×区域四张表逐一同样处理。以“销售区域”表为例:

    # 变式代码:以“销售区域”表为例增加晚交货率并按其升序排序(月份、货品、货品×区域三张表同改)
    data1 = data.groupby(['销售区域', '货品交货状况']).size().unstack()  # 与平台任务一致的交叉频数表
    data1['按时交货率'] = data1['按时交货'] / (data1['按时交货'] + data1['晚交货'])  # 与平台任务一致的原有列
    data1['晚交货率'] = data1['晚交货'] / (data1['按时交货'] + data1['晚交货'])  # ← 新增行:与按时交货率同一分母
    data1 = data1.sort_values('晚交货率', ascending=True)  # ← 改动行:由按按时交货率降序改为按晚交货率升序
    print('销售区域交货状况(含晚交货率,按其升序):')  # 打印标题同步更新
    print(data1)  # 输出该表

    结构性判读:每张表的行数、前两列计数与平台任务完全一致,只是多了“晚交货率”一列且行序反转——升序排列表首的维度就是晚交货占比最高、配送问题最集中的维度。判读要点:晚交货率与按时交货率互为补数(分母相同时两者之和为1),排序结论方向相反但信息等价,新增列的价值在于让“问题严重程度”直接可读。

    本地演练版(模拟数据):在12行合成迷你表上实跑(计数与正文本地演练版一致):

    # 本地演练版:在合成迷你数据上增加“晚交货率”列并按其升序排序
    import pandas as pd  # 导入Pandas数据分析库
    pairs = pd.DataFrame({  # 12行迷你表:仅保留“维度×交货状况”交叉计数所需的两列
        '销售区域': ['华东'] * 5 + ['马来西亚'] * 3 + ['西北'] * 4,  # 维度列(以销售区域为例)
        '货品交货状况': ['按时交货', '按时交货', '按时交货', '按时交货', '晚交货',
                        '按时交货', '按时交货', '晚交货', '按时交货', '晚交货', '晚交货', '晚交货']  # 状况列
    })
    rate_region = pairs.groupby(['销售区域', '货品交货状况']).size().unstack()  # 与平台任务同型的交叉频数表
    rate_region['按时交货率'] = rate_region['按时交货'] / (rate_region['按时交货'] + rate_region['晚交货'])  # 平台已有口径
    rate_region['晚交货率'] = rate_region['晚交货'] / (rate_region['按时交货'] + rate_region['晚交货'])  # 变式新增列:分母口径不变
    rate_region = rate_region.sort_values('晚交货率', ascending=True)  # 变式改动点:按晚交货率升序,问题最集中的维度排最上
    print('销售区域交货状况(含晚交货率,按其升序):')
    print(rate_region)  # 输出结果

    模拟数据演练结果(本机 peter 环境实际运行结果,仅演示流程与判读方法,真实数值以平台运行结果为准):

    销售区域交货状况(含晚交货率,按其升序):
    货品交货状况  按时交货  晚交货     按时交货率      晚交货率
    销售区域
    华东         4    1  0.800000  0.200000
    马来西亚       2    1  0.666667  0.333333
    西北         1    3  0.250000  0.750000

    演练判读:升序排序后西北排在表首(晚交货率0.75),与平台任务注释“西北地区的按时交货率最低”的判读方向一致,只是把“最差”直接放在了第一行。

    预期输出:数据文件由教学平台内置,本机无法复现,具体数值以平台运行结果为准。

    注意:以上为本题变式的独立代码;列表 23.1 对应平台任务的原始代码块仍须原样输入教学平台,不要用本变式替换。

    回扣本章:对应“本章新增的技能要点”第4条(交叉计数后在同一分母上做除法即得比率指标)。

  4. 变式任务:把”销售潜力”分析的分组维度从”销售月份×货品”改为”交货月份×货品”,用来回答”晚交货集中的月份是否也是销量高峰月”,改造思路是什么?

    参考答案(点开前请先独立完成)

    改造思路:平台代码已在清洗阶段用 data['交货月份'] = data['交货时间'].dt.month 派生了交货月份列,因此只需把“销售潜力”部分 groupby 的第一个分组键由 '销售月份' 换成 '交货月份',其余聚合、绘图行不动;建议同时保留销售月份口径的表并排打印,两表对照才能看出“跨月履约”把销量挪动了多少。

    # 变式代码:销售潜力分析的分组维度由“销售月份×货品”改为“交货月份×货品”
    data_month = data.groupby(['交货月份', '货品'])['数量'].sum().unstack()  # ← 改动点1:分组键由“销售月份”改为“交货月份”
    print('交货月份维度货品的销售情况:')  # ← 表名与标题同步更新
    print(data_month)  # 输出交货月份口径的数量表
    data_month_sale = data.groupby(['销售月份', '货品'])['数量'].sum().unstack()  # ← 新增:保留销售月份口径作对照
    print('销售月份维度货品的销售情况(对照):')  # 对照表标题
    print(data_month_sale)  # 输出销售月份口径的数量表
    data_month.plot(kind='line', figsize=(12, 6))  # 与平台任务一致的折线图,改画交货月份口径
    plt.title('各交货月份的销售情况')  # ← 标题同步更新
    plt.savefig('delivery_month_trend.png')  # 建议另存新文件名,避免覆盖平台任务的1.png

    结构性判读:两张表形状相同(月份×货品),但月末销售、次月交货的订单会在交货月份口径中后移一格,月度总量因此此消彼长。判读要点:逐月对照两表的总量行——差值大的月份说明存在大量跨月履约;再把交货月份口径的数量峰值月与第1题口径下的晚交货集中月对照,若两者重合,说明高峰期的运力紧张正在转化为晚交货,回答了本题的问题。

    本地演练版(模拟数据):在10行“销售与交货跨月”的合成订单上实跑:

    # 本地演练版:对比“销售月份×货品”与“交货月份×货品”两种分组口径
    import pandas as pd  # 导入Pandas数据分析库
    orders = pd.DataFrame({  # 10行迷你表:包含月末销售、次月交货的订单
        '销售时间': ['2023-01-28', '2023-01-30', '2023-02-03', '2023-02-10', '2023-03-25', '2023-03-28', '2023-04-02', '2023-04-15', '2023-05-28', '2023-06-05'],
        '交货时间': ['2023-01-31', '2023-02-04', '2023-02-06', '2023-02-14', '2023-03-29', '2023-04-03', '2023-04-06', '2023-04-19', '2023-06-02', '2023-06-09'],
        '货品': ['货品1', '货品2', '货品1', '货品2', '货品1', '货品2', '货品1', '货品2', '货品1', '货品2'],
        '数量': [10, 6, 8, 12, 5, 15, 9, 7, 20, 4]
    })
    for col in ['销售时间', '交货时间']:  # 两列时间统一转为datetime
        orders[col] = pd.to_datetime(orders[col])  # 类型转换
    orders['销售月份'] = orders['销售时间'].dt.month  # 平台任务已有:按销售时间记月份
    orders['交货月份'] = orders['交货时间'].dt.month  # 变式改动点:分组键改用交货月份
    sales_view = orders.groupby(['销售月份', '货品'])['数量'].sum().unstack()  # 平台原口径:销售月份×货品
    deliver_view = orders.groupby(['交货月份', '货品'])['数量'].sum().unstack()  # 变式口径:交货月份×货品
    print('销售月份口径的数量表:')
    print(sales_view)  # 销售口径明细
    print('交货月份口径的数量表:')
    print(deliver_view)  # 交货口径明细
    print('各月份总量对照(销售口径 vs 交货口径):')
    print(pd.DataFrame({'销售口径': sales_view.sum(axis=1), '交货口径': deliver_view.sum(axis=1)}))  # 逐月总量对照

    模拟数据演练结果(本机 peter 环境实际运行结果,仅演示流程与判读方法,真实数值以平台运行结果为准):

    销售月份口径的数量表:
    货品     货品1   货品2
    销售月份
    1     10.0   6.0
    2      8.0  12.0
    3      5.0  15.0
    4      9.0   7.0
    5     20.0   NaN
    6      NaN   4.0
    交货月份口径的数量表:
    货品     货品1   货品2
    交货月份
    1     10.0   NaN
    2      8.0  18.0
    3      5.0   NaN
    4      9.0  22.0
    6     20.0   4.0
    各月份总量对照(销售口径 vs 交货口径):
       销售口径  交货口径
    1  16.0  10.0
    2  20.0  26.0
    3  20.0   5.0
    4  16.0  31.0
    5  20.0   NaN
    6   4.0  24.0

    演练判读:1月末销售、2月交货的6件订单把2月交货口径总量从20推高到26,5月的一笔大单则整月后移到6月——换成交货月份口径后,“运力压力月”与“销售高峰月”会呈现为不同的月份分布,这正是本题要对照回答的问题。

    预期输出:数据文件由教学平台内置,本机无法复现,具体数值以平台运行结果为准。

    注意:以上为本题变式的独立代码;列表 23.1 对应平台任务的原始代码块仍须原样输入教学平台,不要用本变式替换。

    回扣本章:对应“本章新增的技能要点”第5条(下钻式分析路径:换一个分组维度对照看,避免总量聚合掩盖跨月履约的时点错位)。

  5. 商业判断:若平台结果显示货品2在华东销量增长空间最大、但在西北的按时交货率与拒货率均明显偏差(具体数值以平台运行结果为准),你会建议”全国统一加大货品2投入”还是”分区域差异化投入”?给出数据依据与风险考量。

    参考答案(点开前请先独立完成)

    参考作答框架:本题不设唯一结论,优秀作答应覆盖以下维度。

    • 口径先行:先核对三个数字各自的口径——“增长空间”是按数量还是金额衡量、是观察值还是趋势外推;“按时交货率”的分母是否包含全部交货状态(见第1题);“拒货率”的分母是该区域全部反馈订单。口径不清时,任何“明显偏差”都可能只是计算方式不同。
    • 数据依据:分区域差异化投入的直接依据是服务能力与需求的错位——华东需求增长而服务跟得上,西北需求存在但履约与质量指标偏差;若“全国统一加大投入”,追加的订单会流向履约能力最弱的区域,晚交货与拒货随单量放大,运费与退货处理成本同步上升。
    • 风险考量:差异化投入要求区域运营能力配套(西北需先解决配送时效与质量问题,否则投入越多亏损越大);统一投入的风险是把资源错配到高成本区域,还可能拉低整体按时交货率。反过来,过度收缩西北也可能让竞品占领市场,退出节奏本身也需要数据支撑。
    • 模型边界:以上判断基于历史订单数据,不代表加大投入后供给与运力不变;平台数据的时间范围有限,季节性与促销因素未分离。
    • 还需补充的数据:分区域毛利与履约成本(运费、退货处理、损耗)、晚交货与拒货的归因(承运商、距离、包装)、西北市场容量与竞品份额、现有库存与补货周期。
    • 常见错误作答形态:只按销量增速排优先级而忽略履约成本;把“按时交货率低”直接当成“加大投入就能解决”的信号;不给口径与前提就给出唯一的资源分配数字。

    回扣本章:对应“本章新增的技能要点”第5条(层层下钻的交叉维度分析正是发现“同一货品在不同区域表现相反”的手段)。