跳到主要内容

示范:时间倒退的处理

这一页用 demo_s2_dirty.csv 示范一个问题从发现、分类、修复到验证的全过程。选这个例子,是因为它说明了处理顺序为什么重要。

发现​

S1 的规则 time_backwards 在文件第 5400 行报警:时间戳比上一行早。那里的原始数据是这样(只列几列):

TimeT1T2Sensors_LeftSensors_RightTank_Level
08:08:59.700233310011
08:08:59.800234110011
08:08:59.90023910211
08:08:59.2002393811
08:08:59.3002393811
08:08:59.40023253611

时间从 08:08:59.900 跳回 08:08:59.200,倒退了 0.8 秒。

分类​

看其他列:Sensors_Left 和 Sensors_Right 的值按动作顺序正常推进,T1、T2、Tank_Level 没有异常。机器在继续工作,只是时间戳往回跳了。

一台机器不可能回到过去,而它的信号又是连续的,所以这是数据层的问题:记录时间的时钟被调整过。

先修时钟,还是先去重?​

这份文件还有重复行。去重有两种做法,在这份文件上结果不同:

去重方法删掉的行
整行完全相同才算重复177
时间戳相同就算重复185

多出来的 8 行,正是时钟倒退之后、时间戳与前面重复的那 8 行。它们的时间戳和前面的行一样,内容却不同,是真实的数据。按时间戳去重会把它们当作重复删掉。

所以顺序是:先删整行完全相同的重复行,再修时钟。

修复​

把跳变点之后的所有时间戳加上 0.8 秒,让它们接上前一行。

clean_log.py(节选)
def fix_clock_jump(df, step_s):
"""时间倒退时,把跳变点之后整段加上偏移量,使其接上前一行。"""
dt = df["t"].diff().dt.total_seconds()
jumps = df.index[dt < 0]
for i in jumps:
offset = pd.Timedelta(milliseconds=round(1000 * (step_s - dt[i])))
df.loc[i:, "t"] += offset
log_step("fix_clock_jump", rule="time_backwards", rows=f"{i}-{len(df) - 1}",
detail=f"offset {offset.total_seconds():+.1f} s")
return df

注意 log_step:清洗日志由修复函数自己写出,修复和记录是同一件事。

验证​

修复后重跑全部规则:

检查修复后
时间倒退0
重复的时间戳0
时间缺口166 处,共缺 171 行
行数17829

时间缺口还在,这是对的:缺的行补不回来,只能标出来。原始文件 18006 行,去掉重复、加上缺口,正好对应 30 分钟应有的 18000 行。

再看清洗改变了哪些数字:

数字清洗前清洗后
分拣的球数(由管内计数累加)613613
满管停机次数9090
停机总时长,按时间戳计360.2 s360.2 s
停机总时长,按行数 × 0.1 s 计359.6 s356.8 s
气压均值(另一处修复:08:15:00 起单位改变)32996001

两点值得注意:

  • 球数和停机次数没有变。管内计数是累计值,丢一行、重一行都不影响它。能用计数的地方,就别用"数行数"。
  • 按行数算的时长,清洗前后不同;按时间戳算的时长则基本不变。时长应该由时间戳计算。

轮到你​

这份文件里还有另外几类数据问题,以及几段缺料停机。用示范文件找出它们,判断每一个属于哪一层,修复数据层的问题,并写出完整的清洗日志。