简介:这份PDF资料面向生产制造领域的设备管理、工艺工程与精益改善人员,系统讲解设备稼动率(OEE)的计算逻辑与落地方法,帮助读者掌握从可用率、性能率到质量率的完整测算思路。资源共1个PDF文件,压缩包约56KB,内容紧凑,适合作为现场培训或自学速查手册。资料围绕OEE=可用率×性能率×质量率展开,逐一给出三项子指标的计算公式,并结合实际产线案例演示:以实际生产时间400分钟、理论速度25件/分钟等数据,算出可用率0.86、性能率0.8、质量率0.98,最终OEE为96.87%,让抽象公式变得可验证。同时引入TPM总体生产维护理念,说明设备维护保养对稼动率的影响,并给出OEE计算的三步流程与结果分析建议。目前已有347人学习,适合需要快速建立设备效率评估框架、优化产线效能的从业者参考。
1. 设备稼动率如何计算:从车间日报到 OEE 的真实距离
很多工厂的日报表上,设备稼动率一栏常年写着 92%、95%,但老板到现场一看,机器停着,人也不在。问题不在统计员偷懒,而在于「稼动率」这三个字被不同岗位理解成了不同的东西。有人拿开机时长除以日历时长,有人拿实际产出除以理论产能,还有人把计划停机也算进分子里。设备稼动率如何计算,本质上不是一道算术题,而是一次对「什么算停机、什么算有效时间」的定义谈判。这篇笔记面向的是需要自己动手搭统计口径的设备工程师、生产主管和刚接手 OEE 报表的工艺人员。我会把稼动率的定义边界、数据采集方式、计算公式和常见翻车点拆开讲,让你能照着在自己的产线上跑通一套可复现的计算逻辑,而不是继续抄一份来路不明的 Excel 模板。
2. 稼动率、可动率、OEE:先把三个口径掰开
2.1 稼动率到底在算哪段时间
稼动率最容易被混淆的地方,是「时间」这个分母怎么取。常见做法有三种:日历时间、负荷时间、计划运行时间。日历时间是 24 小时乘以天数,适合连续生产流程行业;负荷时间是日历时间减去计划停机,比如交接班、计划保养、无排产时段;计划运行时间则再减去非计划停机。你选哪个分母,算出来的数字能差出 20 个百分点。
我一般建议先明确一个基准:稼动率 = 实际运行时间 / 计划运行时间 × 100%。这里的计划运行时间等于负荷时间减去计划内停机。计划内停机包括:班前会、计划保养、换型调机、无订单待机。非计划停机包括:故障、缺料、缺人、质量异常停线。这个口径的好处是,它把「管理可控」和「管理不可控」分开了,后续做改善时能直接定位到责任部门。
如果你所在工厂的日报只写「开机 8 小时」,那这个 8 小时里可能包含了换型 40 分钟、等料 30 分钟、故障 20 分钟。直接拿 8 除以 8 得到 100%,没有任何意义。所以第一步不是套公式,而是把停机原因分类表先定下来,让班组长在交接班时能勾选。
2.2 可动率与稼动率的区别在哪
可动率衡量的是设备「想开就能开」的能力,分母通常是负荷时间,分子是设备可运行时间,不扣除计划停机。它反映的是设备维护水平。稼动率衡量的是设备「实际开了多久」,扣除计划停机后看实际运行占比,反映的是生产组织水平。两者混用会导致一个后果:设备明明没故障,但因为没订单没开,可动率很高,稼动率很低,报表上却写成同一个数。
一个简单的判断方法:如果停机是因为「没排产」,那影响的是稼动率,不影响可动率;如果停机是因为「坏了」,那两者都受影响。做设备管理的看可动率,做生产计划的看稼动率,做整体效率的看 OEE。
2.3 OEE 与稼动率的乘法关系
OEE = 时间稼动率 × 性能稼动率 × 良品率。这里的「时间稼动率」就是前面说的稼动率。性能稼动率 = 实际产出 / 理论产出,反映的是设备速度有没有跑满。良品率 = 良品数 / 总产出数。
很多工厂只算时间稼动率,然后把它当成 OEE 汇报,这是典型的偷换概念。一台设备可能 95% 的时间在运行,但速度只有额定值的 60%,良品率 90%,实际 OEE 只有 51%。所以当你看到「稼动率 95%」时,先问一句:这是时间稼动率还是 OEE?如果是时间稼动率,那另外两个乘数去哪了?
提示:在日报表上建议把三个乘数分列,不要只写一个合计数。合计数好看,但找不到改善点。
3. 用 Python 从设备日志算出真实稼动率
3.1 数据准备:需要哪几列字段
假设你从 PLC 或 MES 导出了一份设备状态日志,常见字段包括:设备编号、状态开始时间、状态结束时间、状态代码。状态代码一般有:RUN(运行)、IDLE(待机)、FAULT(故障)、SETUP(换型)、MAINT(保养)、OFF(关机)。
你需要先把状态代码映射到「计划停机」「非计划停机」「运行」三类。这个映射表不要写死在代码里,单独放一个字典,方便后续调整。
# 状态分类映射:根据工厂实际口径调整 STATUS_MAP = { "RUN": "run", # 运行 "IDLE": "unplanned", # 待机,通常算非计划停机 "FAULT": "unplanned", # 故障 "SETUP": "planned", # 换型,计划内 "MAINT": "planned", # 保养,计划内 "OFF": "planned", # 关机,计划内 }逻辑说明:这个映射决定了最终数字的走向。比如「IDLE」到底算计划还是非计划,不同工厂有不同定义。如果待机是因为计划排产不足,应该算计划停机;如果是因为上工序断料,算非计划。我一般建议先按「原因是否可提前预知」来分,能提前排进计划的算 planned,突发算 unplanned。
参数说明:STATUS_MAP 的 key 必须和日志里的状态代码完全一致,大小写敏感。如果日志里是中文「运行」,就改成中文 key。
3.2 按时间段聚合计算稼动率
有了分类之后,核心逻辑是:把每条状态记录的时间跨度算出来,按分类累加,最后用 run 时间除以(run + unplanned)时间。注意,planned 时间不进分母,因为它已经被排除在计划运行时间之外。
import pandas as pd def calc_utilization(log_df, status_map): # 确保时间列是 datetime 类型 log_df["start"] = pd.to_datetime(log_df["start"]) log_df["end"] = pd.to_datetime(log_df["end"]) # 计算每条记录的持续秒数 log_df["duration"] = (log_df["end"] - log_df["start"]).dt.total_seconds() # 映射分类 log_df["category"] = log_df["status"].map(status_map) # 按分类汇总时长 summary = log_df.groupby("category")["duration"].sum() run_sec = summary.get("run", 0) unplanned_sec = summary.get("unplanned", 0) planned_sec = summary.get("planned", 0) # 计划运行时间 = 运行 + 非计划停机 planned_run_sec = run_sec + unplanned_sec if planned_run_sec == 0: return 0.0, summary utilization = run_sec / planned_run_sec * 100 return round(utilization, 2), summary逻辑说明:分母用 run + unplanned,而不是 run + unplanned + planned,是因为 planned 时间已经被视为「不安排生产」的时间,不应该拉低稼动率。如果你所在工厂把换型也算作损失,那就把 SETUP 从 planned 挪到 unplanned,数字会立刻下降,但更真实。
参数说明:log_df 至少要有 start、end、status 三列。duration 单位是秒,如果日志时间跨度大,注意时区问题,建议统一转成 UTC 再计算。
3.3 按班次和设备维度拆分
单台设备一天一个总数不够用,你需要按班次看,才能发现「白班高、夜班低」这种玄学现象。做法是在日志里增加班次字段,或者用开始时间判断落在哪个班次区间。
def assign_shift(ts): hour = ts.hour if 8 <= hour < 20: return "day" else: return "night" log_df["shift"] = log_df["start"].apply(assign_shift) # 按设备和班次分组计算 results = [] for (eq, shift), group in log_df.groupby(["equipment_id", "shift"]): util, _ = calc_utilization(group, STATUS_MAP) results.append({"equipment_id": eq, "shift": shift, "utilization": util}) result_df = pd.DataFrame(results)逻辑说明:按班次拆分后,你会发现同一台设备白班稼动率 88%、夜班 72%,差距往往来自夜班缺人、缺料或故障响应慢。这个信息比一个日平均数有用得多。
参数说明:班次边界按工厂实际调整,有的工厂三班倒,需要改成三段。equipment_id 是设备唯一编号,不要用设备名称,名称可能重复。
4. 稼动率统计的避坑与排查清单
4.1 现象:稼动率超过 100%
原因:分母取错了。常见情况是把计划运行时间算成了「实际运行时间减去停机时间」,或者把重叠的状态记录重复累加。还有一种可能是日志里同一时间段有两条记录,一条 RUN 一条 FAULT,时间重叠导致 run 时间被重复计算。
解决:先检查日志有没有时间重叠。用排序后比较上一条的 end 和下一条的 start,如果 start 小于上一条 end,说明有重叠。处理方式是取并集或者按优先级保留一条。另外确认分母公式,计划运行时间必须大于等于运行时间,不可能出现分子大于分母。
4.2 现象:换型时间被算进稼动率分子
原因:状态映射把 SETUP 归到了 run 类。换型时设备可能仍在运转,但不出合格品,如果日志只记录「运行/停止」,换型会被误判为运行。
解决:在 PLC 或 MES 里增加换型状态信号,或者在映射表里把 SETUP 单独归类。如果无法增加信号,至少要在日报里手工扣除换型时间。我见过最离谱的案例是,一台压铸机换模 2 小时,日志显示 RUN,稼动率算出来 96%,实际有效产出时间只有 70%。
4.3 现象:短停机被忽略,稼动率虚高
原因:日志采集频率太低,比如 5 分钟采一次,30 秒的卡料停机根本记不上。或者班组长嫌麻烦,只记录 10 分钟以上的停机。
解决:把采集频率降到 10 秒级,至少对关键设备。如果硬件不支持,用电流互感器加边缘网关做辅助判断:电流低于阈值持续 30 秒即视为停机。短停机累积起来很可怕,一台设备一天 20 次 1 分钟停机,就是 20 分钟,占一个班的 4%。
4.4 现象:不同车间稼动率无法横向对比
原因:各车间对「计划停机」的定义不同。A 车间把保养算计划,B 车间把保养算非计划,两个数字放一起没有可比性。
解决:由工艺或 IE 部门出一份全厂统一的停机分类标准,明确每一类停机的归属。标准发布后,在 MES 里做成下拉选项,不允许手工输入。对比之前先对齐口径,否则就是自欺欺人。
4.5 现象:稼动率改善后 OEE 没变化
原因:只提升了时间稼动率,性能稼动率或良品率拖了后腿。比如设备运行时间长了,但速度没提上来,或者运行中产出的废品多了。
解决:把三个乘数一起看。如果时间稼动率从 80% 提到 90%,但性能稼动率从 85% 降到 75%,OEE 可能原地踏步。改善动作要针对瓶颈乘数,而不是只盯着开机时长。
注意:稼动率是一个管理指标,不是财务指标。不要用它直接算成本,除非你同时确认了性能稼动率和良品率。
5. 从日报到实时看板:稼动率计算的进阶用法
5.1 用滑动窗口做实时稼动率
日报的滞后性太强,等看到数字时停机已经发生了。进阶做法是用滑动窗口算最近 2 小时的稼动率,每 5 分钟刷新一次。实现方式是在边缘网关或服务器上维护一个时间窗口,只保留窗口内的状态记录,重新计算。
from collections import deque from datetime import datetime, timedelta class SlidingUtilization: def __init__(self, window_minutes=120): self.window = timedelta(minutes=window_minutes) self.records = deque() def add_record(self, start, end, category): self.records.append((start, end, category)) self._evict(datetime.now()) def _evict(self, now): cutoff = now - self.window while self.records and self.records[0][1] < cutoff: self.records.popleft() def current(self): run = unplanned = 0 for start, end, cat in self.records: dur = (end - start).total_seconds() if cat == "run": run += dur elif cat == "unplanned": unplanned += dur total = run + unplanned return round(run / total * 100, 2) if total else 0.0逻辑说明:滑动窗口的好处是能反映「当下」的设备状态,而不是昨天的事。窗口长度一般取 1 到 4 小时,太短会抖动,太长反应慢。evict 方法负责清理过期记录,避免内存无限增长。
参数说明:window_minutes 根据设备节拍调整,高速冲压可以取 60 分钟,注塑机可以取 240 分钟。add_record 的 category 必须和前面 STATUS_MAP 的输出一致。
5.2 用稼动率数据反推瓶颈工序
单台设备的稼动率只能看自己,把整条线的设备稼动率按工序排开,就能找到瓶颈。做法是拉取同一时间段内各设备的稼动率,画成柱状图,最低的那台通常就是瓶颈。但要注意,瓶颈不一定是稼动率最低的,还要看它的下游是否在等它。
我一般会做一个简单的矩阵:横轴是设备,纵轴是稼动率,再叠加一个「下游待机时长」字段。如果某台设备稼动率 70%,同时下游待机 30%,那它就是瓶颈。如果它稼动率 70%,但下游稼动率 95%,说明它不是瓶颈,只是排产不满。
5.3 稼动率报表的验证方法
算出来的数字对不对,不能只看公式。我习惯用三种方式交叉验证:第一,拿一个班次的原始日志手工算一遍,和脚本结果对比;第二,和班组长确认当天有没有明显异常,比如「昨天下午停了 2 小时」;第三,看稼动率曲线和设备电流曲线是否吻合,电流持续为零的时间段应该对应停机。
如果三种方式都对不上,优先怀疑日志时间戳的时区或格式问题。我踩过一次坑:PLC 导出的时间没有时区,脚本按本地时间解析,结果夜班数据整体偏移了 8 小时,稼动率算出来完全对不上。后来统一在导出时加 UTC 标记才解决。
5.4 一个具体技巧:把停机原因做成帕累托图
稼动率告诉你「停了多少」,但不告诉你「为什么停」。进阶用法是把非计划停机按原因分类,做帕累托图。前 3 个原因通常占 70% 以上,集中解决这几个,稼动率提升最快。
# 假设 log_df 里有 reason 列记录停机原因 unplanned_df = log_df[log_df["category"] == "unplanned"] reason_summary = unplanned_df.groupby("reason")["duration"].sum().sort_values(ascending=False) reason_summary["cum_pct"] = reason_summary["duration"].cumsum() / reason_summary["duration"].sum() * 100 print(reason_summary.head(10))逻辑说明:按停机时长降序排列,累计百分比到 80% 左右的原因就是重点改善对象。不要试图一次解决所有原因,先打前三个。
参数说明:reason 列需要班组长在交接班时填写,建议在 MES 里做成必填项,否则数据质量无法保证。如果实在填不准,至少区分「设备故障」「缺料」「缺人」「质量异常」四大类。
我做了这么多年设备统计,最大的教训是:稼动率不是算出来的,是管出来的。公式再漂亮,如果停机原因没人填、状态信号没人校准,算出来的数字只是安慰剂。先花两周把数据采集和分类标准跑顺,再谈改善。希望帮到你。
本文还有配套的精品资源,点击获取