工业工程

工业工程

首页
专业认知
课程体系
方法与工具
软件技能
就业发展
考研深造
院校学科
行业洞察
文章
关于
登录 →
工业工程

工业工程

首页 专业认知 课程体系 方法与工具 软件技能 就业发展 考研深造 院校学科 行业洞察 文章 关于
登录
  1. 首页
  2. 软件技能
  3. Python 与 VBA:工业工程自动化的两条路径怎么选

Python 与 VBA:工业工程自动化的两条路径怎么选

0
  • 软件技能
  • 发布于 2026-01-14
  • 4 次阅读
伴读书童
伴读书童
目录
当前文章没有目录

Python 与 VBA:工业工程自动化的两条路径怎么选

引言:一个 200 小时的问题

先看一个 IE 的日常:

每周一早上,需要做上周的生产周报。流程是:

  1. 从 MES 导出 5 个 Excel 文件(产量、停机、质量、人员、物料)
  2. 逐个打开,筛选上周日期,删除无关列
  3. 复制粘贴到一个汇总表
  4. 做透视表,算各产线的产量、良率、OEE
  5. 按格式排版,加图表
  6. 生成 PDF,发给 12 个人

耗时:3~4 小时。每周如此。

一年 50 周,就是 150~200 小时,相当于 20~25 个工作日。换句话说,这个 IE 每年有一个多月的时间,在做纯粹的复制粘贴。

而这还只是一份报表。加上日报、月报、临时取数、数据核对……IE 花在"搬运数据"上的时间,往往超过花在"分析数据"上的时间。

这类工作的共同特征是:规则固定、重复执行、耗时但不产生判断。这正是自动化的最佳目标。

本文要解决的就是:用 VBA 还是 Python?各自能做什么?怎么开始?


一、选型:VBA 还是 Python

1.1 一句话结论

选 VBA 选 Python
场景 操作 Excel 本身(格式、单元格、图表、窗体) 处理数据(清洗、计算、分析、跨系统)
用户 只会 Excel 的业务人员 愿意学编程、要做复杂处理
部署 零部署(在 Excel 里就能跑) 需安装环境(但也可以打包)
学习曲线 低(会 Excel 就能入门) 中(需要理解编程概念)
生态 弱(只有 Office) 极强(数据分析、机器学习、Web、数据库…)

1.2 详细对比

维度 VBA Python
出身 1993 年随 Excel 5.0 发布,微软官方 Office 自动化语言 1991 年发布,通用编程语言
运行环境 内置于 Office,无需安装 需安装 Python 解释器 + 库
处理 Excel 原生、最强大(能操作 Excel 的一切:格式、图表、透视表、条件格式、窗体) 通过库(openpyxl 读写、pandas 处理),格式控制弱于 VBA
数据处理能力 弱(数组、集合,没有数据框) 极强(pandas 是数据分析的事实标准)
跨系统能力 弱(能连数据库但麻烦,能调用 API 但麻烦) 强(数据库、Web API、爬虫、文件系统、邮件全都有成熟库)
统计与建模 几乎要自己写 丰富(scipy、statsmodels、sklearn)
代码量 相对冗长 简洁
调试 VBE 编辑器(老旧但能用) 现代 IDE(VS Code、PyCharm)+ Jupyter
可维护性 差(代码嵌在 Excel 文件里,版本管理困难) 好(纯文本 .py 文件,可 git 管理)
运行速度 慢(单元格逐格操作极慢,需用数组优化) 快(pandas 向量化,C 实现)
安全性 宏病毒风险,企业常禁用宏 相对安全(但也可执行任意代码)
趋势 微软不再重点发展(但仍长期支持) 持续火热,生态爆炸

1.3 决策树

你的任务是什么?
│
├─ 操作 Excel 本身(改格式、生成图表、做透视表、做交互窗体)
│   └─ 你在意格式的精确控制吗?
│       ├─ 是 → VBA(或 Python + VBA 混合)
│       └─ 否 → Python(openpyxl)
│
├─ 处理数据(清洗、合并、计算、分析)
│   └─ 数据量多大?
│       ├─ < 10 万行,且是一次性 → Excel 手工/公式也许够
│       ├─ 任意量,需要重复执行 → Python(pandas)
│       └─ 在 Excel 内完成,且要给用户一个"按钮" → VBA
│
├─ 跨系统(连数据库、调 API、读写文件、发邮件)
│   └─ Python
│
├─ 统计分析(回归、检验、时间序列、机器学习)
│   └─ Python
│
└─ 给不懂技术的同事用
    ├─ 他只会 Excel → 做成带按钮的 Excel 文件(VBA)或独立的 exe
    └─ 能用浏览器 → 做成 Web 应用(Python + Streamlit/Gradio)

1.4 最实用的答案:都要,但侧重不同

现实中最高效的工作方式是混合:

Python 负责"重活":
  - 从数据库取数
  - 清洗、合并、计算(pandas)
  - 生成分析结果

Excel/VBA 负责"交付":
  - 接收 Python 输出的数据
  - 用 Excel 的格式、透视表、图表呈现
  - 给用户熟悉的界面和交互

典型架构:

[数据库/ERP/MES] 
      ↓ Python(定时运行)
[中间数据 CSV/Excel]
      ↓ Excel(Power Query 连接)
[报表/看板]

或者更彻底的自动化:Python 直接生成格式化的 Excel 报表(用 openpyxl 或 xlwings),甚至直接生成 PDF 并邮件发送。


二、VBA 核心能力

2.1 入门:宏录制

宏录制是 VBA 最好的老师。

操作:开发工具 → 录制宏 → 手动做一遍操作 → 停止录制 → Alt+F11 查看生成的代码。

示例:录制"给 A1:D10 加边框、设表头加粗"的操作,会得到类似代码:

Sub Macro1()
    Range("A1:D10").Select
    Selection.Borders.LineStyle = xlContinuous
    Range("A1:D1").Select
    Selection.Font.Bold = True
End Sub

录制代码的问题:大量 .Select 和 .Selection,冗长且低效。学会后可以手动优化:

' 优化后(去掉 Select)
Sub FormatTable()
    With Range("A1:D10")
        .Borders.LineStyle = xlContinuous
    End With
    Range("A1:D1").Font.Bold = True
End Sub

2.2 核心对象模型

Excel VBA 的对象层次:

Application(Excel 应用)
  └── Workbook(工作簿)
        └── Worksheet(工作表)
              └── Range(单元格区域)
                    ├── Cells(row, col)
                    ├── Value(值)
                    ├── Formula(公式)
                    ├── Interior.Color(背景色)
                    └── Font(字体)

最常用的是 Range 对象:

' 引用单元格的几种方式
Range("A1")              ' A1 单元格
Range("A1:B10")          ' 区域
Cells(1, 1)              ' 第1行第1列 = A1
Cells(1, "A")            ' 同上
Range("A1", "B10")       ' 起止区域
[A1]                     ' 简写

' 常用操作
Range("A1").Value = 100              ' 写值
x = Range("A1").Value                ' 读值
Range("A1").Formula = "=SUM(B1:B10)" ' 写公式
Range("A1:D1").Font.Bold = True      ' 加粗
Range("A1:D1").Interior.Color = RGB(200, 200, 200)  ' 背景色
Range("A:A").ColumnWidth = 12        ' 列宽
Range("A1").NumberFormat = "0.00%"   ' 数字格式
Range("A1").ClearContents            ' 清除内容
Range("A1").EntireRow.Delete         ' 删除整行

' 找到最后一行(极其常用)
lastRow = Cells(Rows.Count, 1).End(xlUp).Row
lastCol = Cells(1, Columns.Count).End(xlToLeft).Column

Cells(Rows.Count, 1).End(xlUp).Row 是 VBA 中最常用的技巧之一,用于动态获取数据区域的最后一行。相当于在 A 列最后一个单元格按 Ctrl+↑。

2.3 变量、循环与判断

Sub Demo()
    ' 变量声明
    Dim ws As Worksheet
    Dim lastRow As Long, i As Long
    Dim total As Double
    Dim name As String
    
    Set ws = ThisWorkbook.Sheets("数据表")   ' 对象赋值要用 Set
    
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
    
    ' For 循环
    total = 0
    For i = 2 To lastRow          ' 从第2行到最后一行(跳过表头)
        total = total + ws.Cells(i, 3).Value
    Next i
    
    ' 判断
    For i = 2 To lastRow
        If ws.Cells(i, 3).Value > 1000 Then
            ws.Cells(i, 4).Value = "高"
        ElseIf ws.Cells(i, 3).Value > 500 Then
            ws.Cells(i, 4).Value = "中"
        Else
            ws.Cells(i, 4).Value = "低"
        End If
    Next i
    
    ' For Each 循环
    Dim cell As Range
    For Each cell In ws.Range("A2:A100")
        If IsEmpty(cell) Then cell.Interior.Color = vbYellow
    Next cell
    
    ' Do While
    i = 2
    Do While ws.Cells(i, 1).Value <> ""
        i = i + 1
    Loop
End Sub

2.4 性能:数组是关键

VBA 逐格操作单元格极慢。处理 1 万行数据,逐格读写可能需要几分钟;用数组则只要零点几秒。

' 慢:逐格读写
For i = 1 To 10000
    ws.Cells(i, 2).Value = ws.Cells(i, 1).Value * 2
Next i

' 快:一次性读入数组,处理,一次性写回
Dim arr As Variant
arr = ws.Range("A1:A10000").Value    ' 一次性读入(二维数组)

Dim i As Long
For i = 1 To UBound(arr, 1)
    arr(i, 1) = arr(i, 1) * 2
Next i

ws.Range("B1:B10000").Value = arr    ' 一次性写回

性能提速的四个技巧:

Sub FastMacro()
    Application.ScreenUpdating = False   ' 关闭屏幕刷新(最重要)
    Application.Calculation = xlCalculationManual  ' 手动计算
    Application.EnableEvents = False     ' 关闭事件触发
    Application.DisplayAlerts = False    ' 关闭警告提示
    
    ' ... 你的代码 ...
    
    Application.ScreenUpdating = True
    Application.Calculation = xlCalculationAutomatic
    Application.EnableEvents = True
    Application.DisplayAlerts = True
End Sub

注意:如果代码中可能出错,要用错误处理确保这些设置被恢复,否则 Excel 会处于"卡死"状态。

Sub SafeMacro()
    On Error GoTo CleanUp
    Application.ScreenUpdating = False
    
    ' ... 代码 ...
    
CleanUp:
    Application.ScreenUpdating = True
    If Err.Number <> 0 Then MsgBox "出错: " & Err.Description
End Sub

2.5 事件(自动化触发)

工作表和工作簿事件可以自动响应操作:

' 放在工作表代码页(如 Sheet1)
Private Sub Worksheet_Change(ByVal Target As Range)
    ' 当 A 列变化时,自动记录修改时间到 B 列
    If Not Intersect(Target, Range("A:A")) Is Nothing Then
        Application.EnableEvents = False
        Cells(Target.Row, 2).Value = Now
        Application.EnableEvents = True
    End If
End Sub

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    ' 选中单元格时执行
End Sub

' 放在 ThisWorkbook 代码页
Private Sub Workbook_Open()
    ' 打开文件时执行(如自动刷新数据)
    MsgBox "欢迎使用生产报表模板"
End Sub

Private Sub Workbook_BeforeClose(Cancel As Boolean)
    ' 关闭前执行(如自动保存备份)
End Sub

注意:在事件处理过程中修改单元格会再次触发事件,造成无限循环。必须用 Application.EnableEvents = False 保护(如上例)。

2.6 用户窗体(UserForm)

给非技术用户做界面的方式:

  • 在 VBE 中 插入 → 用户窗体
  • 拖放控件(文本框、按钮、下拉框、复选框)
  • 写事件代码
' 按钮点击事件
Private Sub cmdRun_Click()
    Dim startDate As Date
    startDate = CDate(txtStartDate.Value)
    
    If Not IsDate(txtStartDate.Value) Then
        MsgBox "请输入有效日期", vbExclamation
        Exit Sub
    End If
    
    Call GenerateReport(startDate)
    MsgBox "报表生成完成", vbInformation
    Unload Me
End Sub

更简单的方式:不用 UserForm,直接在 Excel 里用单元格做"输入区",加一个形状(Shape)作为按钮,右键"指定宏"。这样用户更容易理解。

2.7 常用代码片段

' ① 遍历文件夹里的所有 Excel 文件并合并
Sub MergeFiles()
    Dim fso As Object, folder As Object, file As Object
    Dim wb As Workbook, ws As Worksheet
    Dim targetRow As Long
    
    Set fso = CreateObject("Scripting.FileSystemObject")
    Set folder = fso.GetFolder("D:\数据\")
    
    targetRow = 2
    For Each file In folder.Files
        If LCase(fso.GetExtensionName(file.Name)) = "xlsx" Then
            Set wb = Workbooks.Open(file.Path)
            Set ws = wb.Sheets(1)
            
            ' 复制数据
            ws.Range("A2:D100").Copy
            ThisWorkbook.Sheets("汇总").Cells(targetRow, 1).PasteSpecial xlPasteValues
            targetRow = targetRow + 99
            
            wb.Close False
        End If
    Next file
End Sub

' ② 拆分工作表为多个文件(按某列的值)
Sub SplitByColumn()
    Dim ws As Worksheet, dict As Object
    Dim lastRow As Long, i As Long, key As String
    
    Set ws = ThisWorkbook.Sheets("数据")
    Set dict = CreateObject("Scripting.Dictionary")
    
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
    
    ' 按 A 列分组
    For i = 2 To lastRow
        key = ws.Cells(i, 1).Value
        If Not dict.Exists(key) Then dict.Add key, New Collection
        dict(key).Add i
    Next i
    
    ' 为每个 key 创建新表
    Dim k As Variant, newWs As Worksheet, r As Variant, outRow As Long
    For Each k In dict.Keys
        Set newWs = ThisWorkbook.Sheets.Add
        newWs.Name = Left(CStr(k), 30)
        ws.Rows(1).Copy newWs.Rows(1)   ' 复制表头
        outRow = 2
        For Each r In dict(k)
            ws.Rows(r).Copy newWs.Rows(outRow)
            outRow = outRow + 1
        Next r
    Next k
End Sub

' ③ 自动生成图表
Sub MakeChart()
    Dim cht As Chart
    Set cht = ThisWorkbook.Charts.Add
    With cht
        .SetSourceData Source:=Range("数据!A1:B20")
        .ChartType = xlLine
        .HasTitle = True
        .ChartTitle.Text = "产量趋势"
        .Axes(xlCategory).HasTitle = True
        .Axes(xlCategory).AxisTitle.Text = "日期"
        .Axes(xlValue).HasTitle = True
        .Axes(xlValue).AxisTitle.Text = "产量"
    End With
End Sub

' ④ 导出为 PDF
Sub ExportPDF()
    ThisWorkbook.Sheets("报表").ExportAsFixedFormat _
        Type:=xlTypePDF, _
        Filename:="D:\输出\生产周报_" & Format(Date, "yyyymmdd") & ".pdf", _
        Quality:=xlQualityStandard
End Sub

' ⑤ 发送邮件(Outlook)
Sub SendMail()
    Dim olApp As Object, olMail As Object
    Set olApp = CreateObject("Outlook.Application")
    Set olMail = olApp.CreateItem(0)
    
    With olMail
        .To = "manager@company.com"
        .CC = "team@company.com"
        .Subject = "生产周报 - " & Format(Date, "yyyy-mm-dd")
        .Body = "请查收附件。"
        .Attachments.Add "D:\输出\生产周报.pdf"
        .Send    ' 或 .Display 先预览
    End With
End Sub

2.8 VBA 的局限与未来

微软对 VBA 的态度:仍然支持,但不再发展新功能。宏安全策略越来越严(默认禁用宏,打开带宏文件需要手动"启用内容")。

实务影响:

  • 企业环境常禁用宏 → 你的工具发给同事打不开/不能用
  • 文件要存为 .xlsm 格式(不是 .xlsx)
  • 代码嵌在文件里,版本管理困难

建议:

  • 个人效率工具 → VBA 完全够用,继续用
  • 要分发给别人的工具 → 考虑 Python + 打包成 exe,或考虑 Office 脚本(Office Scripts,新一代自动化,基于 TypeScript,可在 Excel 网页版运行)
  • 长期、重要的工具 → Python

三、Python 工业工程数据处理栈

3.1 环境搭建

推荐方式:Anaconda 或 Miniconda

  • Miniconda(推荐):精简版,只含 Python 和 conda,需要什么装什么
  • Anaconda:完整版,预装 200+ 科学计算包,体积大约 3GB

安装后创建环境:

conda create -n ie python=3.11
conda activate ie
conda install pandas openpyxl numpy matplotlib
# 或
pip install pandas openpyxl numpy matplotlib

开发工具:

工具 适合
VS Code 通用,轻量,推荐
PyCharm 专业 Python IDE
Jupyter Notebook / JupyterLab 数据分析探索,强烈推荐(可交互、可边写边看结果)
Spyder 类似 MATLAB 的科学计算 IDE(Anaconda 自带)

给 IE 的建议:用 Jupyter Notebook 入门。它的"分单元格执行 + 即时看结果"特性,非常适合数据探索——你读入一批数据,一步步处理,每步都能立刻看到中间结果。这比写完整脚本再运行要高效得多。

3.2 核心库

库 用途 IE 场景
pandas 数据框(DataFrame)处理 核心中的核心,一切数据处理
numpy 数值计算、数组 矩阵运算、随机数、仿真
openpyxl 读写 .xlsx(保留格式) 生成格式化 Excel 报表
xlrd / xlwings 读 Excel / 控制 Excel 应用 xlwings 可在 Python 里操控 Excel
matplotlib 绘图 基础图表
seaborn 统计图表 更好看的统计图
plotly 交互式图表 可交互的看板
scipy 科学计算、统计检验 t 检验、分布拟合、优化
statsmodels 统计建模 回归、时间序列
scikit-learn 机器学习 预测、分类、聚类
simpy 离散事件仿真 轻量仿真
pyodbc / sqlalchemy 数据库连接 从 MES/ERP 取数
requests HTTP 请求 调用 API、爬虫
schedule / APScheduler 定时任务 自动跑报表
smtplib / yagmail 发邮件 自动发送报表

3.3 pandas 核心操作

DataFrame 是 pandas 的核心数据结构——可以理解为"Excel 的一张表,但有行索引和列名,且能高效处理百万行"。

import pandas as pd

# 读取
df = pd.read_excel("生产数据.xlsx", sheet_name="报工")
df = pd.read_csv("数据.csv", encoding="utf-8-sig")   # 中文 CSV 注意编码
df = pd.read_sql("SELECT * FROM mes_report", conn)

# 查看
df.head()          # 前 5 行
df.tail(10)        # 后 10 行
df.info()          # 列类型、非空数
df.describe()      # 数值列的统计摘要
df.shape           # (行数, 列数)
df.columns         # 列名
df.dtypes          # 数据类型

# 选择
df["产量"]                      # 一列(Series)
df[["产量", "良品数"]]           # 多列
df.loc[0:10, ["产量", "日期"]]   # 按标签选择
df.iloc[0:10, 0:3]              # 按位置选择

# 筛选
df[df["产量"] > 1000]
df[(df["产线"] == "A线") & (df["产量"] > 1000)]
df[df["产品"].isin(["A001", "A002"])]
df[df["备注"].isna()]                    # 空值
df[~df["备注"].isna()]                   # 非空
df[df["工单号"].str.contains("WO2026")]  # 包含

# 新增/修改列
df["良率"] = df["良品数"] / (df["良品数"] + df["不良数"])
df["日期"] = pd.to_datetime(df["日期"])
df["月份"] = df["日期"].dt.month
df["是否延期"] = df["实际完工"] > df["计划完工"]

# 分组聚合
df.groupby("产线")["产量"].sum()
df.groupby(["产线", "月份"])["产量"].agg(["sum", "mean", "count"])
df.groupby("产线").agg({
    "产量": "sum",
    "不良数": "sum",
    "工时": "mean"
})

# 透视表
pd.pivot_table(df, values="产量", index="产线", columns="月份", aggfunc="sum")

# 排序
df.sort_values("产量", ascending=False)
df.sort_values(["产线", "日期"])

# 合并
pd.merge(df1, df2, on="工单号", how="left")    # 类似 SQL JOIN
pd.concat([df1, df2, df3])                      # 纵向拼接

# 处理缺失
df.dropna()                          # 删除含空值的行
df.dropna(subset=["产量"])            # 只删除"产量"为空的行
df.fillna(0)                         # 填充为 0
df["产量"] = df["产量"].fillna(df["产量"].mean())   # 均值填充

# 去重
df.drop_duplicates()
df.drop_duplicates(subset=["工单号"], keep="last")

# 类型转换
df["产量"] = df["产量"].astype(int)
df["日期"] = pd.to_datetime(df["日期"], errors="coerce")  # 转换失败的变 NaT

# 重命名列
df.rename(columns={"旧名": "新名"})

# 应用函数
df["等级"] = df["产量"].apply(lambda x: "高" if x > 1000 else "低")
df["良率"] = df.apply(lambda row: row["良品"] / row["总数"], axis=1)

# 写出
df.to_excel("输出.xlsx", index=False)
df.to_csv("输出.csv", index=False, encoding="utf-8-sig")

pandas 与 SQL 的对应关系(帮助理解):

SQL pandas
SELECT col FROM t df["col"]
WHERE 条件 df[条件]
GROUP BY + SUM df.groupby("k")["v"].sum()
JOIN pd.merge()
ORDER BY df.sort_values()
DISTINCT df.drop_duplicates()
CASE WHEN np.where() / .apply()
窗口函数 .groupby().transform() / .rolling()

3.4 openpyxl:生成格式化 Excel

pandas 的 to_excel 只能输出裸数据。要控制格式(颜色、边框、公式、图表、多 sheet),用 openpyxl。

from openpyxl import Workbook, load_workbook
from openpyxl.styles import Font, PatternFill, Border, Side, Alignment
from openpyxl.utils.dataframe import dataframe_to_rows
from openpyxl.chart import LineChart, BarChart, Reference

# 新建工作簿
wb = Workbook()
ws = wb.active
ws.title = "周报"

# 写数据(从 DataFrame)
df = pd.read_excel("源数据.xlsx")
for r in dataframe_to_rows(df, index=False, header=True):
    ws.append(r)

# 样式
header_font = Font(bold=True, color="FFFFFF", size=11)
header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
thin_border = Border(
    left=Side(style="thin"), right=Side(style="thin"),
    top=Side(style="thin"), bottom=Side(style="thin")
)

# 应用表头样式
for cell in ws[1]:
    cell.font = header_font
    cell.fill = header_fill
    cell.alignment = Alignment(horizontal="center", vertical="center")

# 应用边框
for row in ws.iter_rows(min_row=1, max_row=ws.max_row, max_col=ws.max_column):
    for cell in row:
        cell.border = thin_border

# 列宽
ws.column_dimensions["A"].width = 15
for col in ws.columns:
    max_len = max(len(str(c.value or "")) for c in col)
    ws.column_dimensions[col[0].column_letter].width = min(max_len + 2, 30)

# 数字格式
for cell in ws["D"][1:]:
    cell.number_format = "0.00%"

# 条件格式:低于目标的标红
from openpyxl.formatting.rule import CellIsRule
ws.conditional_formatting.add(
    "D2:D100",
    CellIsRule(operator="lessThan", formula=["0.95"],
               fill=PatternFill(start_color="FFC7CE"))
)

# 冻结首行
ws.freeze_panes = "A2"

# 图表
chart = LineChart()
chart.title = "产量趋势"
chart.y_axis.title = "产量"
chart.x_axis.title = "日期"
data = Reference(ws, min_col=2, min_row=1, max_row=ws.max_row)
cats = Reference(ws, min_col=1, min_row=2, max_row=ws.max_row)
chart.add_data(data, titles_from_data=True)
chart.set_categories(cats)
ws.add_chart(chart, "H2")

# 保存
wb.save("生产周报.xlsx")

关键技巧:

  • 先做好一个 Excel 模板(含格式、图表、公式),Python 只往里填数据 → 比代码生成格式简单得多
  • 用 openpyxl 的 load_workbook 打开模板,写入数据,save 成新文件

3.5 数据清洗七步

真实数据永远是脏的。标准清洗流程:

import pandas as pd
import numpy as np

# ① 读入并初查
df = pd.read_excel("原始数据.xlsx")
print(df.shape, df.columns.tolist())
print(df.isna().sum())          # 各列缺失数
print(df.duplicated().sum())    # 重复行数

# ② 清理列名(去空格、统一命名)
df.columns = df.columns.str.strip()
df = df.rename(columns={"产 量": "产量", "日期时间": "日期"})

# ③ 删除无关列
df = df[["工单号", "产线", "产品", "日期", "产量", "不良数", "工时"]]

# ④ 类型转换
df["日期"] = pd.to_datetime(df["日期"], errors="coerce")
df["产量"] = pd.to_numeric(df["产量"], errors="coerce")
df["产线"] = df["产线"].astype(str).str.strip().str.upper()

# ⑤ 处理缺失与异常
df = df.dropna(subset=["产量", "日期"])     # 关键字段缺失就删
df = df[df["产量"] > 0]                     # 删除非正值
df = df[df["产量"] < df["产量"].quantile(0.999)]  # 删除极端值(或用业务规则)

# ⑥ 去重与标准化
df = df.drop_duplicates(subset=["工单号", "日期"], keep="last")
df["产线"] = df["产线"].replace({"A": "A线", "A线 ": "A线"})  # 统一取值

# ⑦ 派生字段与校验
df["良率"] = df["产量"] / (df["产量"] + df["不良数"])
assert df["良率"].between(0, 1).all(), "良率超出 [0,1] 范围!"
print(f"清洗完成:{len(df)} 行")

第 7 步的 assert 校验很重要。数据清洗一定会出问题,与其让错误流到报表里,不如在处理时就报错。


四、五个可复制的自动化案例

案例 1:多文件合并(日报汇总)

场景:每天从 MES 导出一个 Excel,每月要合并成月报。

import pandas as pd
import glob
import os

# 找到所有文件
files = glob.glob(r"D:\MES导出\*.xlsx")
print(f"找到 {len(files)} 个文件")

# 逐个读取并合并
dfs = []
for f in files:
    df = pd.read_excel(f, sheet_name="报工")
    df["来源文件"] = os.path.basename(f)   # 记录来源,便于追溯
    dfs.append(df)
    print(f"  读取 {os.path.basename(f)}: {len(df)} 行")

combined = pd.concat(dfs, ignore_index=True)
print(f"合并后共 {len(combined)} 行")

# 去重(如果文件之间有重叠)
combined = combined.drop_duplicates(subset=["工单号", "报工时间"])

# 输出
combined.to_excel(r"D:\输出\月度报工汇总.xlsx", index=False)

变体:如果文件名含日期,可以从文件名提取日期作为一列。

案例 2:BOM 比对(新旧版本差异)

场景:工程变更(ECN)后,需要知道新旧 BOM 的差异。

import pandas as pd

# 读入新旧 BOM
old = pd.read_excel("BOM_旧.xlsx")
new = pd.read_excel("BOM_新.xlsx")

# 统一关键字段
for df in (old, new):
    df["物料编码"] = df["物料编码"].astype(str).str.strip()

# 外连接,标记来源
merged = old.merge(new, on="物料编码", how="outer",
                   suffixes=("_旧", "_新"), indicator=True)

# 分类差异
新增 = merged[merged["_merge"] == "right_only"]
删除 = merged[merged["_merge"] == "left_only"]
共有 = merged[merged["_merge"] == "both"].copy()

# 数量变化
共有["数量变化"] = 共有["用量_新"] - 共有["用量_旧"]
数量变化 = 共有[共有["数量变化"] != 0]

# 输出报告
with pd.ExcelWriter("BOM_差异报告.xlsx") as writer:
    新增.to_excel(writer, sheet_name="新增物料", index=False)
    删除.to_excel(writer, sheet_name="删除物料", index=False)
    数量变化.to_excel(writer, sheet_name="用量变化", index=False)

print(f"新增 {len(新增)} 项,删除 {len(删除)} 项,用量变化 {len(数量变化)} 项")

这个脚本的价值:手工比对两个几百行的 BOM 需要 1~2 小时且容易漏;脚本 10 秒完成且零遗漏。

案例 3:工时数据清洗(剔除异常)

场景:从系统导出的工时数据里有大量异常(负值、超大值、测试记录),需要清洗后才能做标准工时分析。

import pandas as pd
import numpy as np

df = pd.read_excel("工时原始数据.xlsx")

# 记录处理过程
log = {"原始": len(df)}

# 1. 删除测试记录
df = df[~df["工单号"].astype(str).str.contains("TEST|测试|试用", na=False)]
log["删除测试"] = len(df)

# 2. 删除关键字段为空
df = df.dropna(subset=["工时秒", "操作人"])
log["删除空值"] = len(df)

# 3. 删除非正值
df = df[df["工时秒"] > 0]
log["删除非正"] = len(df)

# 4. 用 IQR 方法剔除离群值(按产品-工序分组)
def remove_outliers(group):
    q1 = group["工时秒"].quantile(0.25)
    q3 = group["工时秒"].quantile(0.75)
    iqr = q3 - q1
    lower = q1 - 1.5 * iqr
    upper = q3 + 1.5 * iqr
    return group[(group["工时秒"] >= lower) & (group["工时秒"] <= upper)]

df = df.groupby(["产品编码", "工序"], group_keys=False).apply(remove_outliers)
log["剔除离群"] = len(df)

# 5. 剔除样本量不足的组(<10 条)
counts = df.groupby(["产品编码", "工序"])["工时秒"].transform("count")
df = df[counts >= 10]
log["样本不足"] = len(df)

# 6. 计算标准工时(用中位数更稳健)
std_time = df.groupby(["产品编码", "工序"])["工时秒"].agg([
    ("样本数", "count"),
    ("均值", "mean"),
    ("中位数", "median"),
    ("标准差", "std"),
    ("变异系数", lambda x: x.std() / x.mean())
]).round(2)

# 建议标准工时 = 中位数 + 宽放
std_time["建议标准工时秒"] = (std_time["中位数"] * 1.15).round(1)   # 15% 宽放

# 输出
with pd.ExcelWriter("标准工时分析.xlsx") as writer:
    df.to_excel(writer, sheet_name="清洗后数据", index=False)
    std_time.to_excel(writer, sheet_name="标准工时")
    pd.Series(log).to_excel(writer, sheet_name="清洗日志", header=["剩余行数"])

print(log)

要点:

  • 分组剔除离群值,而不是全局剔除(不同产品的工时量级不同)
  • 用中位数而非均值作为标准工时的基础(更稳健,不受极端值影响)
  • 变异系数 CV 大的组要重点检查(说明操作不稳定或数据有问题)
  • 保留清洗日志,让结果可追溯

案例 4:批量文件处理(重命名/格式转换)

场景:几百个图纸/工艺文件需要按规则重命名,或批量转 PDF。

import os
import re
from pathlib import Path

src_dir = Path(r"D:\工艺文件")
dst_dir = Path(r"D:\工艺文件_已整理")
dst_dir.mkdir(exist_ok=True)

# 读入映射表(旧名 -> 新名)
mapping = pd.read_excel("重命名映射表.xlsx")
mapping_dict = dict(zip(mapping["原文件名"], mapping["新文件名"]))

renamed, skipped = 0, 0
for file in src_dir.glob("*.pdf"):
    stem = file.stem   # 不含扩展名的文件名
    
    # 从文件名提取编码(如 "DWG-A001-Rev2" 中的 A001)
    match = re.search(r"([A-Z]{2}\d{4})", stem)
    
    if stem in mapping_dict:
        new_name = mapping_dict[stem] + file.suffix
    elif match:
        code = match.group(1)
        new_name = f"{code}_工艺文件{file.suffix}"
    else:
        print(f"  跳过(无法识别): {file.name}")
        skipped += 1
        continue
    
    # 处理重名
    target = dst_dir / new_name
    if target.exists():
        target = dst_dir / f"{Path(new_name).stem}_副本{file.suffix}"
    
    file.rename(target)
    renamed += 1

print(f"重命名 {renamed} 个,跳过 {skipped} 个")

安全提醒:批量文件操作前,先备份,先在副本上测试,先打印出"将要做什么"让用户确认,再实际执行。

案例 5:自动生成并发送报表

import pandas as pd
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from datetime import datetime, timedelta
import schedule
import time

def generate_report():
    """生成上周的生产周报"""
    end = datetime.now()
    start = end - timedelta(days=7)
    
    # 取数
    df = pd.read_sql(
        f"SELECT * FROM v_production_daily WHERE stat_date >= '{start:%Y-%m-%d}'",
        conn
    )
    
    # 计算指标
    summary = df.groupby("产线").agg({
        "产量": "sum",
        "不良数": "sum",
        "运行时间": "sum",
        "停机时间": "sum"
    })
    summary["良率"] = summary["产量"] / (summary["产量"] + summary["不良数"])
    summary["OEE"] = ...  # 按公式计算
    
    # 生成 Excel(用模板)
    from openpyxl import load_workbook
    wb = load_workbook("周报模板.xlsx")
    ws = wb["数据"]
    for r in dataframe_to_rows(summary.reset_index(), index=False, header=False):
        ws.append(r)
    
    output = f"生产周报_{end:%Y%m%d}.xlsx"
    wb.save(output)
    
    return output, summary

def send_report(attachment, summary):
    """发送邮件"""
    msg = MIMEMultipart()
    msg["From"] = "ie@company.com"
    msg["To"] = "manager@company.com; team@company.com"
    msg["Subject"] = f"生产周报 - {datetime.now():%Y-%m-%d}"
    
    # 正文(把关键指标写进正文,不用打开附件就知道概况)
    body = f"""
    <html><body>
    <h3>上周生产概况</h3>
    <table border="1" cellpadding="5">
    <tr><th>产线</th><th>产量</th><th>良率</th><th>OEE</th></tr>
    {"".join(f"<tr><td>{idx}</td><td>{row['产量']:.0f}</td>"
             f"<td>{row['良率']:.2%}</td><td>{row['OEE']:.1%}</td></tr>"
             for idx, row in summary.iterrows())}
    </table>
    <p>详见附件。</p>
    </body></html>
    """
    msg.attach(MIMEText(body, "html", "utf-8"))
    
    # 附件
    with open(attachment, "rb") as f:
        part = MIMEApplication(f.read())
        part.add_header("Content-Disposition", "attachment", 
                       filename=attachment)
        msg.attach(part)
    
    # 发送
    with smtplib.SMTP("smtp.company.com", 25) as server:
        server.login("ie@company.com", "password")
        server.send_message(msg)
    print(f"已发送: {attachment}")

def job():
    print(f"[{datetime.now()}] 开始生成周报...")
    try:
        attachment, summary = generate_report()
        send_report(attachment, summary)
        print("完成")
    except Exception as e:
        print(f"出错: {e}")
        # 出错时也要发通知
        # send_error_notification(str(e))

# 定时任务:每周一早上 8:00
schedule.every().monday.at("08:00").do(job)

# 或者直接用操作系统的定时任务(更可靠)
# Windows: 任务计划程序
# Linux: cron

print("定时任务已启动,等待触发...")
while True:
    schedule.run_pending()
    time.sleep(60)

实务建议:定时任务的可靠性,用操作系统级的调度(Windows 任务计划程序 / Linux cron)比 Python 的 schedule 库更稳(Python 进程挂了就没了)。Python 脚本只负责"跑一次",调度交给系统。


五、VBA 与 Python 的协作

5.1 三种协作方式

方式 做法 适用
Python 生成 → Excel 消费 Python 输出 CSV/Excel,Excel 用 Power Query 连接 最简单,推荐
VBA 调用 Python 用 Shell 命令调用 Python 脚本 用户点 Excel 按钮触发 Python
xlwings Python 直接控制 Excel 应用 需要双向交互

5.2 推荐架构:Python 处理 + Excel 呈现

[数据源]
   ↓ Python 脚本(定时运行)
[中间数据.csv / 数据.xlsx]  ← 放在共享盘
   ↓ Excel 用 Power Query 连接(自动刷新)
[报表/看板.xlsx]  ← 用户在 Excel 里看、切片、透视

优点:

  • Python 负责重活(取数、清洗、计算)
  • Excel 负责呈现(用户熟悉、格式灵活、可交互)
  • 用户不需要装 Python,只需要刷新 Excel
  • 逻辑与呈现分离,易于维护

5.3 VBA 调用 Python 示例

Sub RunPython()
    Dim pythonPath As String, scriptPath As String
    Dim shellCmd As String
    
    pythonPath = "C:\Users\xxx\miniconda3\envs\ie\python.exe"
    scriptPath = "D:\scripts\generate_report.py"
    
    shellCmd = """" & pythonPath & """ """ & scriptPath & """"
    
    ' 同步执行,等待完成
    Dim wsh As Object
    Set wsh = CreateObject("WScript.Shell")
    Dim exitCode As Integer
    exitCode = wsh.Run(shellCmd, 1, True)   ' 1=显示窗口, True=等待
    
    If exitCode = 0 Then
        ' 刷新 Excel 数据连接
        ThisWorkbook.RefreshAll
        MsgBox "报表已更新", vbInformation
    Else
        MsgBox "Python 脚本执行失败,错误码: " & exitCode, vbCritical
    End If
End Sub

5.4 xlwings:Python 控制 Excel

import xlwings as xw

# 连接已打开的 Excel,或打开新实例
wb = xw.Book("报表.xlsx")
# 或 app = xw.App(visible=True); wb = app.books.open(...)

# 读写单元格
sheet = wb.sheets["数据"]
sheet.range("A1").value = "产量"
sheet.range("A2").value = [[1, 2], [3, 4]]   # 写二维数组

# 读入 DataFrame
df = sheet.range("A1").expand().options(pd.DataFrame).value

# 写 DataFrame
sheet.range("A1").value = df

# 调用 Excel 公式
sheet.range("D2").formula = "=B2/C2"

# 运行 VBA 宏
macro = wb.macro("GenerateChart")
macro()

# 保存
wb.save()

xlwings 的价值:Python 的数据处理能力 + Excel 的完整对象模型。适合"数据处理复杂、格式要求也高"的场景。


六、学习路径

6.1 VBA 学习路径(约 20 小时)

阶段 内容 用时
1 宏录制 + 修改录制代码 3h
2 变量、循环、判断、Range 对象 6h
3 数组优化、事件、错误处理 4h
4 文件系统、字典、Outlook 集成 4h
5 实战:做一个自己的自动化工具 3h+

最好的学习方法:从自己每周重复做的工作开始。打开宏录制,做一遍,看代码,然后改。

6.2 Python 学习路径(约 60 小时)

阶段 内容 用时
基础语法 变量、类型、列表、字典、循环、函数、文件读写 15h
pandas DataFrame 操作、分组、合并、清洗 20h
Excel 交互 openpyxl、xlwings 8h
可视化 matplotlib、seaborn 5h
实战项目 自动化报表、数据分析 12h+

给 IE 的建议:不要从"Python 入门教程"开始(那些教你写猜数字游戏、学生管理系统的内容对 IE 没用)。直接从 pandas 开始,用你自己的数据练。

推荐学习资源:

  • 《利用 Python 进行数据分析》(Python for Data Analysis,Wes McKinney) — pandas 作者写的,最权威
  • pandas 官方文档的 "10 Minutes to pandas" — 一小时快速上手
  • Kaggle Learn — 免费的交互式课程
  • B 站/知乎的中文教程 — 注意甄别质量

6.3 从"会写"到"能用"

写好脚本只是开始,要让它真正产生价值,还需要:

环节 做法
稳定化 加错误处理、日志记录、异常通知
参数化 日期、路径、阈值做成配置,不要硬编码
文档化 写清楚"这个脚本做什么、怎么运行、依赖什么"
可交接 别人能跑起来(requirements.txt、说明文档)
自动化 用任务计划程序定时跑
验证 输出结果的合理性检查(断言、对账)

一个"能用的"脚本和"玩具脚本"的区别,全在这些工程细节上。


七、九个新手常见的坑

# 坑 后果 解法
1 路径写死(C:\Users\张三\...) 换台电脑就跑不了 用相对路径或配置文件;用 Path(__file__).parent
2 中文编码问题 CSV 乱码、读取报错 CSV 用 encoding="utf-8-sig";文件名避免中文
3 Excel 文件被占用 无法写入 关闭文件;或用 with 确保释放
4 不备份就批量修改 数据丢失 先备份,先在副本上测试
5 逐格操作单元格(VBA) 极慢 用数组一次性读写
6 不处理缺失值就计算 结果全为 NaN 先 dropna 或 fillna
7 浮点比较用 == 判断出错 用 abs(a-b) < 1e-6
8 脚本报错没人知道 自动化悄悄失败 加 try-except + 错误通知(邮件/日志)
9 硬编码日期(WHERE date = '2026-08-01') 每次要改代码 用 datetime.now() - timedelta(days=7) 动态计算

第 4 条最严重。批量处理文件的脚本,如果逻辑写错并且没有备份,可能一次性毁掉几百个文件。永远先在小样本上测试,永远保留原始副本。


八、给 IE 的务实建议

8.1 先算 ROI,再动手

自动化一个任务前,先算:

年度耗时 = 单次耗时 × 年频次
开发投入 = 学习 + 编写 + 调试
回收期 = 开发投入 / 年度耗时

经验规则:

  • 年度耗时 > 20 小时 → 值得自动化
  • 任务规则固定、很少变 → 值得自动化
  • 只用一两次 → 不要自动化,手动做更快

不要为了自动化而自动化。 一个耗时 10 分钟的月度任务,花 8 小时写脚本,需要 48 个月才回本——不值得。

8.2 从"最痛的"开始

列出你所有重复性的工作,按"耗时 × 频率"排序,从最痛的开始。通常是:

  1. 周期性报表(日报/周报/月报)
  2. 数据合并与清洗
  3. 格式转换与文件处理
  4. 对账与核对

8.3 复利效应

自动化有一个重要特点:一次投入,长期收益,且可复制。

  • 你写了一个合并报表的脚本,以后每月省 3 小时
  • 你把这个脚本改改给同事用,又省了别人的时间
  • 你积累了处理同类问题的模式,写下一个脚本只要 1/3 的时间

三年下来,一个善于自动化的 IE,比同僚多出几百小时的分析和改善时间。 这就是差距的来源。

8.4 不要走火入魔

最后一点提醒:自动化的目的是把时间还给思考,不是把工作变成写代码。

见过一些工程师,花两周写一个高度定制化的自动化工具,结果这个工具只用了一次(因为业务需求变了)。判断标准:

  • 这个工具会被反复使用吗?
  • 需求会频繁变动吗?
  • 有没有现成工具能用(Excel 公式、Power Query、低代码平台)?

能用 Power Query 解决的,不必写 Python。能用 Excel 公式解决的,不必写 VBA。选择最简单的、能解决问题的工具。


结语:自动化是 IE 的"杠杆"

IE 的核心工作是消除浪费。而"重复搬运数据"本身就是一种巨大的浪费——只不过它浪费的是 IE 自己的时间,所以常常被忽视。

学会自动化,本质上是给自己装了一根杠杆:

  • 同样的时间,能处理 10 倍的数据
  • 同样的分析,能覆盖 10 倍的范围
  • 同样的结论,能快 10 倍得到

更重要的是,自动化改变了你的工作方式。当你不再需要手工搬运数据,你才有时间和精力去问那些真正重要的问题:

  • 这个数字为什么是这个值?
  • 这个趋势意味着什么?
  • 我们该做什么?

工具解决"怎么做",而 IE 的价值在于"做什么"。把前者交给脚本,把后者留给自己。

相关阅读

  • 工业工程必备软件地图:从 Excel 到 FlexSim,每个阶段该学什么:按学习阶段给出 IE 的软件全景图:Excel、统计分析、仿真建模、CAD、企业系统,并给出…
  • 用 Python 做 IE 数据分析:从工时数据到线平衡计算:四个可直接运行的 IE 场景代码:标准工时自动计算、产线平衡分析、离散事件仿真、运筹优化排产…
  • 工业工程师的 Excel 实战手册:从工时分析、过程能力到线平衡的 30 个技法:面向 IE 场景的 Excel 实操手册:连续测时数据差分还原、IQR 与 3σ 异常值剔除…
  • 工业工程数据与指标看板:从指标定义、采集口径到可视化落地的完整手册:从指标定义卡 12 要素讲到看板落地:OEE 三种分母口径对照(负荷 75.45% / 计划…
  • Minitab 工业工程实战指南:从数据到结论的完整链路:工业工程领域出镜率最高的统计软件。本文按拿到数据后的真实使用顺序组织:数据导入清洗、图形化汇…
相关文章
Visio 与流程图实战:价值流图、工序流程图与业务建模

Visio 与流程图实战:价值流图、工序流程图与业务建模

一张画得对的价值流图,能让改善讨论从「我觉得」变成「数据显示」。本文讲透:工具选型(Visio/draw.io/Mermaid 等十款对比)、流程图四层体系(工序流程/业务泳道/价值流/BPMN)、工序分析五种符号与流程程序图、VSM 完整绘制法(现状图七步、数据框填法、时间线与增值比、未来图设计六问、定拍工序)、泳道图画法、Visio 与 draw.io 高效操作技巧、让图能沟通的九原则,以及一套装配线 VSM 落地案例。

Python 与 VBA:工业工程自动化的两条路径怎么选

Python 与 VBA:工业工程自动化的两条路径怎么选

每周花 4 小时做同一份报表,一年就是 200 小时。本文系统对比两条自动化路径:VBA(Excel 原生、零部署、适合操作 Excel 本身)与 Python(生态强大、适合数据处理与跨系统),给出明确选型决策树;讲透 VBA 核心能力(宏录制、Range、数组优化、事件、代码片段)与 Python 数据处理栈(pandas、openpyxl、数据清洗七步),含五个可复制案例(多文件合并、BOM 比对、工时清洗、批量重命名、自动发邮件报表)和九个新手坑。

MES 系统完全指南:从原理、选型到落地实施

MES 系统完全指南:从原理、选型到落地实施

MES 是工厂数字化投入最大、失败率也最高的系统之一。本文讲透:MES 到底解决什么问题(ERP 和 SCADA 为何解决不了)、ISA-95 五层架构与 MES 定位、十一大核心功能模块、与 ERP/QMS/WMS/SCADA 的边界与集成接口、三种数据采集方式的取舍与老设备改造方案、选型评估六维度、实施五阶段与分批上线策略,以及八个常见陷阱——包括为什么「先上 MES 再理流程」必然失败。

Power BI 工业工程看板实战:从数据到管理驾驶舱

Power BI 工业工程看板实战:从数据到管理驾驶舱

每天早会 25 分钟花在对数字上,根源是没有口径统一、自动刷新的数据源。本文讲透 Power BI 完整链路:何时该用何时不该、Power Query 数据清洗十类操作与逆透视、星型模型与表关系设计、DAX 核心(计算列 vs 度量值、筛选上下文、CALCULATE、迭代函数、时间智能)、十五个生产管理度量值代码、图表选择与避坑、网关刷新与行级安全,以及看板设计七原则和一套 OEE 管理驾驶舱落地案例。

SQL 工业工程数据分析实战:从 MES 取数到指标看板

SQL 工业工程数据分析实战:从 MES 取数到指标看板

IE 日常有 60% 的时间花在等数据上。本文从真实取数场景出发,讲透 SQL 核心语法(SELECT/WHERE/GROUP BY/JOIN/子查询/CTE/窗口函数/日期处理)、MES 与 ERP 的典型表结构与 ER 关系、如何快速摸清陌生数据库、十二个高频实战查询模板(OEE、工单周期、瓶颈识别、不良帕累托、停机归因、WIP 追踪、挣值工时、正反向追溯、同比环比、换型矩阵、技能矩阵、呆滞库存),以及六大陷阱(一对多重复计数、NULL 静默丢数、日期边界、时区班次、粒度错误、生产库性能)。

AutoCAD 工厂布局实战:从平面图到可落地的设施规划

AutoCAD 工厂布局实战:从平面图到可落地的设施规划

一张合格的车间布局图要能直接拿去施工、报消防、做产能核算、给仿真建模。本文系统讲解 IE 用 AutoCAD 做设施规划的完整链路:软件选型、国标制图规范、高频命令速查、设备图块库与动态块建设、SLP 五步法、厂区级与车间级布局要点、消防与安全间距、人机工程尺寸应用,以及从 CAD 到仿真的数字化交付。

目录
当前文章没有目录
Copyright © 2002 CUMT All Rights Reserved. Powered by 中国矿业大学工业工程系.
粤ICP备2024349181号-3