在pandas中高效对AI Builder的输出进行后处理,使用前向填充并合并拆分后的行
我正在处理来自Microsoft AI Builder Document Processing的 JSON输出。将JSON展平为pandas DataFrame之后,需要在导出之前对提取的行进行归一化处理。
提取结果通常比较准确,但存在若干可预见的模式,需要进行后处理。
场景1 — 头字段向前填充
列 Ticket、BOL 和 Date 仅在每个逻辑分组的第一行被赋值。同一分组中的后续行这些字段都为null。
这些值需要在每个分组内向前填充,直到出现新的非空值 Ticket。
场景2 — 移位的单字段行(累计净数量)
在某些情况下,OCR只将 Additive Net Qty 的值移到单独的一行。这一行除了 Additive Net Qty 外,其他字段均为null。
该值必须通过填充缺失的 Additive Net Qty,合并到前一条有效行中;随后删除这条独立的行。
场景3 — 跨多列拆分的行
在某些情况下,一个记录会分成两行相邻的行。第一行仅包含 Ticket、BOL 和 Date,而 Product、Net Qty 和 Additive Net Qty 缺失。下一行包含缺失的产品级字段,但头字段为null。
这两行应合并为一个完整的记录,并删除冗余行。
MRE:
import pandas as pd
import numpy as np
df = pd.DataFrame(
[
# Normal row
["1000", "AAA", "4/1/26", "Regular", 8500, 15],
# Scenario 1 - Ticket/BOL/Date missing
["1001", "ABC", "4/1/26", "Gasoline", 8000, 30],
[np.nan, np.nan, np.nan, "Ethanol", 500, 12],
[np.nan, np.nan, np.nan, "Dye", 20, 1],
# Normal row
["1002", "DEF", "4/2/26", "Diesel", 7500, 40],
# Scenario 2 - Additive Net Qty shifted to next row
["1003", "GHI", "4/3/26", "Premium", 9000, np.nan],
[np.nan, np.nan, np.nan, np.nan, np.nan, 25],
# Normal row
["1004", "JKL", "4/4/26", "Jet Fuel", 6000, 18],
# Scenario 3 - Product/Net Qty/Additive Qty shifted to next row
["1005", "MNO", "4/5/26", np.nan, np.nan, np.nan],
[np.nan, np.nan, np.nan, "Diesel", 7800, 35],
],
columns=[
"Ticket",
"BOL",
"Date",
"Product",
"Net Qty",
"Additive Net Qty",
],
)
# Expected Output
Ticket BOL Date Product Net Qty Additive Net Qty
0 1000 AAA 4/1/26 Regular 8500.0 15.0
1 1001 ABC 4/1/26 Gasoline 8000.0 30.0
2 1001 ABC 4/1/26 Ethanol 500.0 12.0
3 1001 ABC 4/1/26 Dye 20.0 1.0
4 1002 DEF 4/2/26 Diesel 7500.0 40.0
5 1003 GHI 4/3/26 Premium 9000.0 25.0
6 1004 JKL 4/4/26 Jet Fuel 6000.0 18.0
7 1005 MNO 4/5/26 Diesel 7800.0 35.0
解决方案
可以通过向量掩码、.shift()、.ffill() 和 .dropna() 的组合来解决上述三种场景。
import numpy as np
import pandas as pd
# 1. Identify standalone "Additive Net Qty" rows (Scenario 2)
# Check for rows where all columns except 'Additive Net Qty' are null
is_shifted_additive = df[
["Ticket", "BOL", "Date", "Product", "Net Qty"]
].isna().all(axis=1) & df["Additive Net Qty"].notna()
# 2. Pull the shifted Additive Net Qty value back up to the previous row
df["Additive Net Qty"] = df["Additive Net Qty"].fillna(
df["Additive Net Qty"].shift(-1)
)
# 3. Drop the now-redundant standalone Scenario 2 rows
df = df[~is_shifted_additive]
# 4. Forward fill the header columns (Scenario 1 & Scenario 3)
df[["Ticket", "BOL", "Date"]] = df[["Ticket", "BOL", "Date"]].ffill()
# 5. Drop rows missing 'Product' to eliminate empty split headers (Scenario 3)
df_clean = df.dropna(subset=["Product"]).reset_index(drop=True)
print(df_clean)
最终输出
Ticket BOL Date Product Net Qty Additive Net Qty
0 1000 AAA 4/1/26 Regular 8500.0 15.0
1 1001 ABC 4/1/26 Gasoline 8000.0 30.0
2 1001 ABC 4/1/26 Ethanol 500.0 12.0
3 1001 ABC 4/1/26 Dye 20.0 1.0
4 1002 DEF 4/2/26 Diesel 7500.0 40.0
5 1003 GHI 4/3/26 Premium 9000.0 25.0
6 1004 JKL 4/4/26 Jet Fuel 6000.0 18.0
7 1005 MNO 4/5/26 Diesel 7800.0 35.0
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。