Python 使用pandas处理Excel —— 快递订单处理 数据匹配 邮费计算

news2024/11/17 15:51:20

问题背景

有表A,其数据如下
在这里插入图片描述
关键信息是邮寄地址单号

表B:
在这里插入图片描述
关键信息是运单号重量
我们需要做的是,对于表A中的每一条数据,根据其单号,在表B中查找到对应的重量。
在表A中新增一列重量,将刚才查到的数据填在该列。
更近一步地,会再提供一张价格表:
在这里插入图片描述

我们需要根据表A的邮寄地址和刚得到的重量计算该订单的运费。
同样在表A中新增一列运费,将计算得到的运费填写在该列。

准备工作

建立一个文件夹,在该文件夹下再建立三个文件夹,分别是origin、query和result,里面分别放表A(可以放多个表)、表B(也可以放多个表),result放的是最终的结果。

其它细节

1、可以发现有些单号为空的行被折叠了,为了保持原样,所以我们会添加一列collapse,如果订单号为空,就设置collapse为1,否则为空。之后再根据collapse这列折叠单号为空的行,后面会介绍。
2、会存在一些在表B中找不到重量信息的订单号,这些订单将被输出在命令行窗口。
3、也可以处理有多个sheet的表。

代码

import os
import re
import pandas as pd
import cpca
import math

# 将所有待处理的文件都保存在这个路径下
ROOT_DIR = '/Users/XXX/Desktop/OrderProcessing/'
# 所有结果将保存在这个路径下
SAVE_DIR = '/Users/XXX/Desktop/OrderProcessing/result/'

# 参照此格式,三个数字分别表示0.5kg,首重,续重。
# 注意省份名称一定要规范。不过不要求Excel表格中的邮寄地址必须要规范。
COST_TABLE_ORIGIN = {'江苏省': [1, 3, 1],
                     '浙江省': [1, 3, 1],
                     '上海市': [1, 3, 1],
                     '安徽省': [1, 3, 1],
                     '舟山市': [1, 3, 1]
                     }


def calc_cost(province, city, weight, cost_table):
    """
    根据地区和重量计算运费
    :param province: 省份
    :param city: 城市
    :param weight: 重量
    :param cost_table: 价格表
    :return: 价格
    """
    costs = None
    additional = 0
    if str(province) in "北京市" or str(province) in "上海市":
        additional = 1
    for p, cost in cost_table.items():
        if str(city) in p:
            costs = cost
    if costs is None:
        for p, cost in cost_table.items():
            if str(province) in p:
                costs = cost
    if costs is None:
        print("    计算费用时发生错误,可能是价格表中没有对应的地区")
        return None
    if weight <= 0.5:
        return costs[0] + additional
    elif weight <= 1:
        return costs[1] + additional
    else:
        return costs[1] + math.ceil(weight - 1) * costs[2] + additional


def query_weight_by_order(file_name, order, order_str='运单号', weight_str='重量'):
    """
    根据订单号查询重量
    :param file_name: 去哪个文件里查找
    :param order: 订单号
    :param order_str: 订单的列名
    :param weight_str: 重量的列名
    :return: 该订单的重量
    """
    df = pd.read_excel(io=file_name)
    num_rows = len(df.index.values)
    weight = None
    for row in range(num_rows):
        if str(df.iloc[row][order_str]) == order:
            weight = df.iloc[row][weight_str]
            break
    return weight


def add_weight(read_file_name, write_file_name, sheet_name=None, collapse_flag=True):
    """
    添加重量信息
    :param read_file_name: 读取文件
    :param write_file_name:  写入文件
    :param sheet_name: 工作表名称
    :param collapse_flag: 是否隐藏指定行,比如某项值为空,则隐藏该行
    :return:
    """

    if sheet_name is None:
        df = pd.read_excel(io=read_file_name)
        writer = pd.ExcelWriter(write_file_name)
    else:
        df = pd.read_excel(io=read_file_name, sheet_name=sheet_name)
        # 这样写好像有点笨
        if os.path.exists(write_file_name):
            writer = pd.ExcelWriter(write_file_name, mode='a')
        else:
            writer = pd.ExcelWriter(write_file_name, mode='w')

    num_rows = len(df.index.values)

    if '单号' not in df.columns.values:
        print("    没有单号这一列,请确保单号那列的列名为'单号'")
        writer.close()
        return

    for row in range(num_rows):
        order = str(df.loc[row, '单号'])
        '''
        像order这一列,如果全是正常的单号,读进来会是浮点数,比如78649717XXX259.0
        如果有几行是"停发",读进来的就都是不带小数点的了,比如78XXX17332259
        空值就是显示nan
        '''
        if order == "nan" or order == "停发":  # pd.isnull(order)
            if order == "nan" and collapse_flag:  # 若订单号为空,则标记隐藏该行
                df.loc[row, 'collapse'] = 1
            continue

        # 到这里的,就是带小数点的订单号,或者正常的不带小数点的订单号
        if order[-2] == '.':  # 去除小数点
            order = order[:-2]
            # df.loc[row, '单号'] = order

        # 有可能写了多个订单号,比如786497173XXX9;78649719X80XX0;786497X799ZXX4
        # 这种情况下,就把多个订单的重量进行累加
        orders = re.split(',|;|\n| |,|;', order)
        weight = 0
        for o in orders:
            if len(o) <= 0:
                continue
            w = None
            '''
            这里就是根据订单的不同查询不同的表
            比如Y开头的,查哪个表;数字开头的,查哪个表
            此处需要自定义
            '''
            if o[0] == 'Y':
                # 根据订单号查询重量
                w = query_weight_by_order(ROOT_DIR + "query/A.xlsx", o, order_str='运单号码', weight_str='计费重量(kg)')
            elif '0' <= o[0] <= '9':
                w = query_weight_by_order(ROOT_DIR + "query/B.xlsx", o)
            if w is not None and (isinstance(w, float) or isinstance(w, int)):
                weight += w
            else:
                print("    没有找到该订单的重量数据:" + o)
        if weight > 0:
            df.loc[row, '重量'] = weight
            # 格式化地址信息
            address = cpca.transform([df.loc[row, '邮寄地址']])
            # 计算运费
            cost = calc_cost(address.loc[0, '省'], address.loc[0, '市'], weight, COST_TABLE_ORIGIN)
            if cost is None:
                print("    发生错误的订单号为:", order)
                continue
            else:
                df.loc[row, '运费'] = cost
    if sheet_name is None:
        df.to_excel(writer, index=False)
    else:
        df.to_excel(writer, index=False, sheet_name=sheet_name)
    writer.close()


"""
TODO:
1、修改ROOT_DIR和SAVE_DIR
2、将所有待处理的xlsx文件保存在ROOT_DIR/origin路径下,查询表保存在ROOT_DIR/query路径下
2、修改查询订单重量的代码,只需要简单地填写文件名,关键的列名等
3、修改价格表,并在调用calc_cost方法的地方指定价格表
"""
if __name__ == '__main__':
    if not os.path.exists(ROOT_DIR):
        print(ROOT_DIR + "不存在")
        exit()

    if not os.path.exists(SAVE_DIR):
        print("创建目录:" + SAVE_DIR)
        os.mkdir(SAVE_DIR)
    else:
        ans = input("是否删除%s下的所有文件?(Y/N):" % SAVE_DIR)
        if ans == "Y":
            # 删除该目录下的所有文件
            for filename in os.listdir(SAVE_DIR):
                os.remove(SAVE_DIR+filename)
            print("已删除SAVE_DIR下的所有文件")

    print("开始处理")

    for filename in os.listdir(ROOT_DIR+"origin/"):
        if filename[0] == '.' or filename[-4:] != "xlsx":  # 去除隐藏文件和非xlsx文件
            continue
        print("正在处理:" + filename)
        xlsx = pd.ExcelFile(ROOT_DIR + "origin/" + filename)
        sheet_names = xlsx.sheet_names
        xlsx.close()  # 不知道是不是需要

        for sheet_name in sheet_names:
            print("  正在处理:", sheet_name)
            add_weight(ROOT_DIR + "origin/" + filename, SAVE_DIR + filename, sheet_name)

    print("处理完毕")

处理结果

在这里插入图片描述
然后我们需要根据collapse列来折叠单号为空的行。
这个我还不知道怎么通过pandas实现,现在就只能先通过Excel自带的功能处理。
比如Mac版的WPS是这么处理的
1、选中collapse列
在这里插入图片描述
2、按command+G。按下图设置
在这里插入图片描述

3、点击定位
在这里插入图片描述

可以发现collapse为1的行被选中了
4、点击command+9。单号为空的行就被折叠了
在这里插入图片描述
5、然后再删除collapse这列就行了
最终结果:
在这里插入图片描述

命令行窗口输出

是否删除/Users/XXX/Desktop/OrderProcessing/result/下的所有文件?(Y/N):Y
已删除SAVE_DIR下的所有文件
开始处理
正在处理:table1.xlsx
  正在处理: Sheet1
  正在处理: Sheet2
正在处理:A.xlsx
  正在处理: AA
    没有找到该订单的重量数据:中通:786XXXX23
    没有找到该订单的重量数据:786X5780XX37
    没有找到该订单的重量数据:合在一起打包
    没有找到该订单的重量数据:786493XX3783158
  正在处理: AB
    计算费用时发生错误,可能是价格表中没有对应的地区
    发生错误的订单号为: 78649XXX184656
    计算费用时发生错误,可能是价格表中没有对应的地区
    发生错误的订单号为: 786497XXX08769
    没有找到该订单的重量数据:786X979XX8226
    没有找到该订单的重量数据:5箱
    没有找到该订单的重量数据:直发
  正在处理: AC
  正在处理: AD
    没有找到该订单的重量数据:YT699X121XX068
    没有找到该订单的重量数据:YT6993X9X987155
    没有找到该订单的重量数据:786499616XXX08
    没有找到该订单的重量数据:YT6XXX875919847
    没有找到该订单的重量数据:786497XXX57489
  正在处理: AE
    没有单号这一列,请确保单号那列的列名为'单号'
处理完毕

Process finished with exit code 0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/353691.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

防水防汗耳机什么品牌好?四款防水效果不错的蓝牙耳机推荐

近年来蓝牙耳机中可谓是火爆全网&#xff0c;非常受到大家追捧&#xff0c;当然&#xff0c;也随着蓝牙耳机的增长&#xff0c;很多人不知道蓝牙耳机该如何选择&#xff0c;其实蓝牙耳机不止要音质表现好&#xff0c;佩戴体验好&#xff0c;还有着防水性能不能差&#xff0c;不…

window字符集与利用向导创建mfc

1.字节对应英语1个字符对应1个字节 多字节中文1个字符对应多个字节 宽字节 Unicode utf-8 3个 GBK2个2.多字节转换 为宽字节TEXT是由自适应编码的转换 TCHER 自适应编码的转换 _T是由自适应编码转换&#xff0c;L("")多字节转宽字节3.统计字符串长度统计字符串长度 c…

Ambari2.7.5集群搭建详细流程

0 说明 本文基于本地虚拟机从零开始搭建ambari集群 1 前置条件 1.1 本地虚拟机环境 节点角色ambari-1ambari-server ambari-agentambari-2ambari-agentambari-3ambari-agent 1.2 安装包 1.3 修改主机名并配置hosts文件 hostnamectl set-hostname ambari-1 hostnamectl se…

2022 年度回顾|The Sandbox 开放元宇宙的发展历程

2023 年又会为大家带来什么呢&#xff1f; 2022 年是很值得庆祝的一年。回顾这一年&#xff0c;The Sandbox 开放游戏元宇宙达成诸多里程碑。我们努力让各方面都更接近我们的愿景&#xff1a;一个开放的、去中心化的元宇宙&#xff0c;通过真正的数位所有权赋予创作者权力。社区…

mysql 通过客户端执行now()函数,时差为8小时

1.场景演示 假设当前北京时间是&#xff1a;2023-02-17 19:31:37。明显执行出来的结果和实际时间晚8小时。 所用Mysql版本为&#xff1a; 解决方式&#xff1a; 需要在my.conf文件中的[mysqld]下添加 default-time-zoneAsia/Shanghai 由于这个mysql8.0是通过 docker 安装的&…

【Python合集】我见过最有趣好玩强大的代码都在这里,涨见识啦~建议收藏起来慢慢学。(墙裂推荐)

前言 大家好&#xff0c;我是栗子同学啦~ 所有文章完整的素材源码都在&#x1f447;&#x1f447; 粉丝白嫖源码福利&#xff0c;请移步至CSDN社区或文末公众hao即可免费。 Python 凭借语法的易学性&#xff0c;代码的简洁性以及类库的丰富性&#xff0c;赢得了众多开发者的喜爱…

Linux延时队列工作原理与实现

当进程要获取某些资源&#xff08;例如从网卡读取数据&#xff09;的时候&#xff0c;但资源并没有准备好&#xff08;例如网卡还没接收到数据&#xff09;&#xff0c;这时候内核必须切换到其他进程运行&#xff0c;直到资源准备好再唤醒进程。 waitqueue (等待队列) 就是内核…

【初探人工智能ChatGPT】2、雏形开始长成

【初探人工智能ChatGPT】2、雏形开始长成【初探人工智能ChatGPT】2、雏形开始长成安装Flask封装Web接口雏形设置接收参数功能验证聊天写代码代码补全生成图片写在后面笔者初次接触人工智能领域&#xff0c;文章中错误的地方还望各位大佬指正&#xff01; 【初探人工智能ChatGPT…

NIFI大数据进阶_内嵌ZK模式集群1_搭建过程说明---大数据之Nifi工作笔记0015

集群可以使用nifi内嵌的zookeeper来搭建集群,也可以使用外部的自己装的zookeeper来 搭建集群. 因为nifi是依赖zookeeper集群来进行工作的,为了避免运维人员还需要额外的去搭建,维护一个 zookeeper集群,所以nifi,就内嵌了一个zookeeper集群. 可以看到有两个属性需要配置,第一…

OpenGL - 如何理解 VAO 与 VBO 之间的关系

系列文章目录 LearnOpenGL 笔记 - 入门 01 OpenGLLearnOpenGL 笔记 - 入门 02 创建窗口LearnOpenGL 笔记 - 入门 03 你好&#xff0c;窗口LearnOpenGL 笔记 - 入门 04 你好&#xff0c;三角形 文章目录系列文章目录1. 前言2. 渲染管线的入口 - 顶点着色器2.1 顶点着色器处理过…

关于IIC通讯协议的有关问题

问题&#xff1a;如何正确使用IIC这么优秀的通讯协议呢&#xff1f;解决:第一步&#xff1a;知道起始信号和终止信号当SCL为1的时候&#xff0c;SDA从1变成0&#xff0c;这个就是起始信号&#xff0c;说明可以开始传输&#xff1b;当SCL为1的时候&#xff0c;SDA从0变成1&#…

Spire.Office for Java 8.2.0 强大之喜Xaspose

Spire.Office for Java是由E-iceblue开发的一款Java系列的Office文档操作类库&#xff0c;用于操作MS Word、PDF、MS Power Point 以及条形码&#xff0c;可实现文档创建、编辑、转换、打印等功能&#xff0c;是E-iceblue 所有 Java组件&#xff0c;包括Spire.Doc for Java、Sp…

Java爬虫入门——HttpClient,JSoup

一&#xff0c;网络爬虫介绍爬虫也叫网络机器人&#xff0c;可以代替人工&#xff0c;自动的在网络上采集和处理信息。爬虫包括数据采集&#xff0c;分析&#xff0c;存储三部爬虫引入依赖<!--引入httpClient依赖--><dependency><groupId>org.apache.httpcom…

Netty服务端请求接受过程源码剖析

目标 服务器启动后&#xff0c;客户端进行连接&#xff0c;服务器端此时要接受客户端请求&#xff0c;并且返回给客户端想要的请求&#xff0c;下面我们的目标就是分析Netty 服务器端启动后是怎么接受到客户端请求的。我们的代码依然与上一篇中用同一个demo&#xff0c; 用io.…

Tektronix TAP3500/泰克TAP3500有源探头

产品概览 泰克 TAP3500 有源探头&#xff0c;2 .5 GHz 泰克 TAP3500 单端有源 FET 探头是一种多功能且易于使用的探头&#xff0c;可提供数字系统设计所需的高速电气和机械性能。泰克 TAP3500 探头专为使用和连接到 TekVPI™ 探头接口而设计。 泰克 TAP3500 有源探头的特性和规…

带你认识一下什么是函数式接口Comparator

函数式接口Comparator 1、函数式接口是什么&#xff1f; 所谓的函数式接口&#xff0c;实际上就是接口里面只能有一个抽象方法的接口。Comparator接口就是一个典型的函数式接口&#xff0c;它只有一个抽象方法compare。 有人会说equales方法也没有方法体&#xff0c;也是抽象…

江苏专转本考试倒计时,该如何自救?

专转本考试倒计时&#xff0c;该如何自救&#xff1f;第一点&#xff1a;回归考纲教材&#xff0c;刷题。 最后一段时间&#xff0c;一定要回归考纲及教材&#xff01;要把知识点看细&#xff0c;看明白。 另外大家也可以根据历年考试题对知识点进行系统地复习和梳理&#xff0…

【java】Spring Boot --深入SpringBoot注解原理及使用

步骤一 首先&#xff0c;先看SpringBoot的主配置类&#xff1a; SpringBootApplication public class StartEurekaApplication {public static void main(String[] args){SpringApplication.run(StartEurekaApplication.class, args);} }步骤二 点进SpringBootApplication来…

线程间通信的常用方式

线程间通信的常用方式 1.简介 线程通信简单来说就是实现线程的交替工作&#xff0c;传递信息。例如在一个方法中我有两个线程A和B在运行&#xff0c;我希望线程A先向一个集合里面循环新增数据&#xff0c;当增加到第五次的时候&#xff0c;线程B才开始执行其他的操作。 线程间…

博客系统测试用例

博客系统测试用例目录博客系统测试用例博客系统删除功能测试用例 (判定表)提交BUG 1测试用例 1测试用例 2提交BUG提交BUG 2博客系统测试用例 博客系统删除功能测试用例 (判定表) # 首先确定输入条件与输出条件 输入条件: 博客作者, 非博客作者, 点击删除博客输出条件: 删除成…