数值模糊比较加入 Python/pandas 个数据帧
Numeric Fuzzy Comparison Join in Python/pandas dataframes
我有四个 table 的航班时刻表制作。我实际上没有 PK 列,但我将它放在那里以便我可以更轻松地参考航班。我现在在 pd.DataFrames 中找到了它们。
Table 1. 草案 #1
MarketID Origin Dest FltNum DepCentral ArrCentral PK
1DFWSJC DFW SJC 111 0700 1035 A
2DFWSJC DFW SJC 222 2035 2410 B
Table 2. 草案 #2
MarketID Origin Dest FltNum DepCentral ArrCentral PK
1DFWSJC DFW SJC 111 0700 1030 A
2DFWSJC DFW SJC 222 2040 2410 B
Table 3. 草案 #3
MarketID Origin Dest FltNum DepCentral ArrCentral PK
1DFWSJC DFW SJC 444 0645 1015 A
2DFWSJC DFW SJC 555 1300 1630 X
3DFWSJC DFW SJC 666 2040 2410 B
Table 4. 最终时间表
MarketID Origin Dest FltNum DepCentral ArrCentral PK
1DFWSJC DFW SJC 444 0645 1015 A
2DFWSJC DFW SJC 666 2040 2415 B
我基本上想关注草案 1 中的每个航班,这些航班也在最终时间表中,看看它们在制作中是如何改变的。但我也在尝试跟踪每个 table 对每个航班所做的更改。本质上它是一个内部连接,我有 DepCentral_Draft1、DepCentral_Draft2 等的列。
通过这个我可以看到在草稿 1 和草稿 2 之间,航班 A 的到达时间提前了 5 分钟。在草案 2 和草案 3 之间,航班 A 的出发和到达时间提前了 15 分钟。等等
MarketID 接近,但在草案 #2 和 #3 之间,他们插入了一个航班并重新标记了 MarketID(按起飞时间排序)。他们似乎在草稿#2 和#3 之间以及草稿#3 和最终草稿之间非常随意地增加和减少了航班,所以我不能依赖 MarketID。
FltNum 也很不一致。在 Draft #1 和 #2 之间,它们匹配,但是 #3 和 #4,它们似乎在 Draft #3 之后随机重新标记航班号。
作为一个人,我可以说 Table 3 中的 2DFWSJC 既不是航班 A 也不是航班 B,因为它的 departure/arrival 时间有多么不同。当我加入 MarketID 时,它认为航班 B 正在从 2040 年移至 1300 年(这就是我注意到这些插入的方式)。
我无法连接出发地、目的地和出发或到达时间并进行匹配,因为它们略有偏移,所以它们不是完全匹配。我见过用字符串进行的模糊比较,但我没有见过用数字做的模糊比较。
类似于将航班 B 定义为
Origin = DFW
Dest = SJC
DepCentral = ~2035
ArrCentral = ~2410
我可以在哪里定义 ~ 的接近程度,或者它会自动意识到 2040 比 1300 更接近 2035 并在可能的情况下加入。
这是我编写的原始函数,用于查找原始时间表和新时间表之间的差异。
def schdChanges(base, new):
merged = pd.merge(base, new, on = ['MktSegID'])
diffDf = pd.DataFrame()
diffDf['MktSegID'] = merged['MktSegID']
diffDf['Cdep'] = merged['Cdep_y'] - merged['Cdep_x']
diffDf['Carr'] = merged['Carr_y'] - merged['Carr_x']
diffDf['Block'] = merged['Block_y'] - merged['Block_x']
diffDf['Turn'] = merged['Turn_y'] - merged['Turn_x']
return diffDf
编辑。我所拥有的替代解决方案是确定市场容量(如 DFW-SJC)发生了哪些变化,并只关注这些变化。
DirMkt - 起点 + 终点
Draft1Cap = Draft1.groupby('DirMkt')['MktSegID'].nunique()
FinalCap = Final.groupby( 'DirMkt')['MktSegID'].nunique()
FinalCap.subtract(Draft1Cap, fill_value = 0)[FinalCap.subtract(Draft1Cap, fill_value = 0) != 0]
所以现在我确定了哪些发生了变化。我的旧功能适用于除这些以外的所有功能。
并使用它确定了要从时间表中删除哪些市场,因为它们不存在于草稿和最终
def returnNotMatches(a, b):
return [[x for x in a if x not in b], [x for x in b if x not in a]]
returnNotMatches(set(Draft1['DirMkt'].unique()), set(Final['DirMkt'].unique()))
所以基本上我需要的最低限度是弄清楚,当添加航班时,相对于其市场中的其他航班,它是在哪里添加的?
您有 6 列。让我们根据它们在这种情况下的用处来分解它们。
MarketID
和 FltNum
可以任意更改,所以这对我们没有帮助。
Origin
和 Dest
我假设必须相同且无法更改,所以我们会检查一下
DepCentral
和 ArrCentral
是最重要的,根据问题陈述最多可以更改 50 分钟。
您有一些复杂的业务逻辑,因此可能没有简单的解决方案。所以这就是乐趣所在,编写一些东西来处理该逻辑!
这个程序找到了匹配项,我会把它留给你来获得你想要的输出
import pandas as pd
如果你有日期,或者确定你不会在 23:55 找到需要与 00:04 匹配的东西,那么你可以简化或替换这个逻辑
def time_change(old_time, new_time):
old_hrs, new_hrs = int(old_time[0:2]), int(new_time[0:2])
old_mins, new_mins = int(old_time[2:]), int(new_time[2:])
old_total = 60 * old_hrs + old_mins
new_total = 60 * new_hrs + new_mins
# note, this may make incorrect assumptions since we don't have the day.
# If you have the day in your actual data, there are better ways of comparing the times
return abs((new_total - old_total)) % (24 * 60)
现在,进入正题,检查匹配项。你列出了你正在寻找的东西,所以这只是实现它的一些逻辑。此函数接受任意两行进行比较。
def check_match(old, new):
#["MarketID", "Origin", "Dest", "FltNum", "DepCentral", "ArrCentral"]
if old['Origin'] != new['Origin']:
return False, "", ""
if old['Dest'] != new['Dest']:
return False, "", ""
total_time_change = time_change(old["DepCentral"], new["DepCentral"]) + \
time_change(old["ArrCentral"], new["ArrCentral"])
if abs(total_time_change) <= 50:
return True, total_time_change, "other fields that changed"
else:
return False, "", ""
遍历新旧 table 中的所有行并进行比较。以你说的数据量,翻遍应该没问题。
def compare_tables(old, new_df):
if 'PK' not in old.columns:
# use the current index as the starting PK
old['PK'] = old.index
for _, old_row in old.iterrows():
print("Looking at row:")
print(old_row.T)
pk = old_row['PK']
best_match = None
best_match_time_change = float('inf')
for _, new_row in new_df.iterrows():
is_match, time_change, old_changes = check_match(old_row, new_row)
if is_match and (time_change < best_match_time_change):
best_match_time_change = time_change
best_match = new_row
print("The best match is:")
if best_match is not None:
print(best_match.T, best_match_time_change)
else:
print("no matches found")
print()
print()
遍历所有 table 对:
all_tables = [table_1, table_2, table_3, table_4]
for old, new_df in zip(all_tables, all_tables[1:]):
compare_tables(old, new_df)
我有四个 table 的航班时刻表制作。我实际上没有 PK 列,但我将它放在那里以便我可以更轻松地参考航班。我现在在 pd.DataFrames 中找到了它们。
Table 1. 草案 #1
MarketID Origin Dest FltNum DepCentral ArrCentral PK
1DFWSJC DFW SJC 111 0700 1035 A
2DFWSJC DFW SJC 222 2035 2410 B
Table 2. 草案 #2
MarketID Origin Dest FltNum DepCentral ArrCentral PK
1DFWSJC DFW SJC 111 0700 1030 A
2DFWSJC DFW SJC 222 2040 2410 B
Table 3. 草案 #3
MarketID Origin Dest FltNum DepCentral ArrCentral PK
1DFWSJC DFW SJC 444 0645 1015 A
2DFWSJC DFW SJC 555 1300 1630 X
3DFWSJC DFW SJC 666 2040 2410 B
Table 4. 最终时间表
MarketID Origin Dest FltNum DepCentral ArrCentral PK
1DFWSJC DFW SJC 444 0645 1015 A
2DFWSJC DFW SJC 666 2040 2415 B
我基本上想关注草案 1 中的每个航班,这些航班也在最终时间表中,看看它们在制作中是如何改变的。但我也在尝试跟踪每个 table 对每个航班所做的更改。本质上它是一个内部连接,我有 DepCentral_Draft1、DepCentral_Draft2 等的列。
通过这个我可以看到在草稿 1 和草稿 2 之间,航班 A 的到达时间提前了 5 分钟。在草案 2 和草案 3 之间,航班 A 的出发和到达时间提前了 15 分钟。等等
MarketID 接近,但在草案 #2 和 #3 之间,他们插入了一个航班并重新标记了 MarketID(按起飞时间排序)。他们似乎在草稿#2 和#3 之间以及草稿#3 和最终草稿之间非常随意地增加和减少了航班,所以我不能依赖 MarketID。
FltNum 也很不一致。在 Draft #1 和 #2 之间,它们匹配,但是 #3 和 #4,它们似乎在 Draft #3 之后随机重新标记航班号。
作为一个人,我可以说 Table 3 中的 2DFWSJC 既不是航班 A 也不是航班 B,因为它的 departure/arrival 时间有多么不同。当我加入 MarketID 时,它认为航班 B 正在从 2040 年移至 1300 年(这就是我注意到这些插入的方式)。
我无法连接出发地、目的地和出发或到达时间并进行匹配,因为它们略有偏移,所以它们不是完全匹配。我见过用字符串进行的模糊比较,但我没有见过用数字做的模糊比较。
类似于将航班 B 定义为
Origin = DFW
Dest = SJC
DepCentral = ~2035
ArrCentral = ~2410
我可以在哪里定义 ~ 的接近程度,或者它会自动意识到 2040 比 1300 更接近 2035 并在可能的情况下加入。
这是我编写的原始函数,用于查找原始时间表和新时间表之间的差异。
def schdChanges(base, new):
merged = pd.merge(base, new, on = ['MktSegID'])
diffDf = pd.DataFrame()
diffDf['MktSegID'] = merged['MktSegID']
diffDf['Cdep'] = merged['Cdep_y'] - merged['Cdep_x']
diffDf['Carr'] = merged['Carr_y'] - merged['Carr_x']
diffDf['Block'] = merged['Block_y'] - merged['Block_x']
diffDf['Turn'] = merged['Turn_y'] - merged['Turn_x']
return diffDf
编辑。我所拥有的替代解决方案是确定市场容量(如 DFW-SJC)发生了哪些变化,并只关注这些变化。
DirMkt - 起点 + 终点
Draft1Cap = Draft1.groupby('DirMkt')['MktSegID'].nunique()
FinalCap = Final.groupby( 'DirMkt')['MktSegID'].nunique()
FinalCap.subtract(Draft1Cap, fill_value = 0)[FinalCap.subtract(Draft1Cap, fill_value = 0) != 0]
所以现在我确定了哪些发生了变化。我的旧功能适用于除这些以外的所有功能。
并使用它确定了要从时间表中删除哪些市场,因为它们不存在于草稿和最终
def returnNotMatches(a, b):
return [[x for x in a if x not in b], [x for x in b if x not in a]]
returnNotMatches(set(Draft1['DirMkt'].unique()), set(Final['DirMkt'].unique()))
所以基本上我需要的最低限度是弄清楚,当添加航班时,相对于其市场中的其他航班,它是在哪里添加的?
您有 6 列。让我们根据它们在这种情况下的用处来分解它们。
MarketID
和 FltNum
可以任意更改,所以这对我们没有帮助。
Origin
和 Dest
我假设必须相同且无法更改,所以我们会检查一下
DepCentral
和 ArrCentral
是最重要的,根据问题陈述最多可以更改 50 分钟。
您有一些复杂的业务逻辑,因此可能没有简单的解决方案。所以这就是乐趣所在,编写一些东西来处理该逻辑!
这个程序找到了匹配项,我会把它留给你来获得你想要的输出
import pandas as pd
如果你有日期,或者确定你不会在 23:55 找到需要与 00:04 匹配的东西,那么你可以简化或替换这个逻辑
def time_change(old_time, new_time):
old_hrs, new_hrs = int(old_time[0:2]), int(new_time[0:2])
old_mins, new_mins = int(old_time[2:]), int(new_time[2:])
old_total = 60 * old_hrs + old_mins
new_total = 60 * new_hrs + new_mins
# note, this may make incorrect assumptions since we don't have the day.
# If you have the day in your actual data, there are better ways of comparing the times
return abs((new_total - old_total)) % (24 * 60)
现在,进入正题,检查匹配项。你列出了你正在寻找的东西,所以这只是实现它的一些逻辑。此函数接受任意两行进行比较。
def check_match(old, new):
#["MarketID", "Origin", "Dest", "FltNum", "DepCentral", "ArrCentral"]
if old['Origin'] != new['Origin']:
return False, "", ""
if old['Dest'] != new['Dest']:
return False, "", ""
total_time_change = time_change(old["DepCentral"], new["DepCentral"]) + \
time_change(old["ArrCentral"], new["ArrCentral"])
if abs(total_time_change) <= 50:
return True, total_time_change, "other fields that changed"
else:
return False, "", ""
遍历新旧 table 中的所有行并进行比较。以你说的数据量,翻遍应该没问题。
def compare_tables(old, new_df):
if 'PK' not in old.columns:
# use the current index as the starting PK
old['PK'] = old.index
for _, old_row in old.iterrows():
print("Looking at row:")
print(old_row.T)
pk = old_row['PK']
best_match = None
best_match_time_change = float('inf')
for _, new_row in new_df.iterrows():
is_match, time_change, old_changes = check_match(old_row, new_row)
if is_match and (time_change < best_match_time_change):
best_match_time_change = time_change
best_match = new_row
print("The best match is:")
if best_match is not None:
print(best_match.T, best_match_time_change)
else:
print("no matches found")
print()
print()
遍历所有 table 对:
all_tables = [table_1, table_2, table_3, table_4]
for old, new_df in zip(all_tables, all_tables[1:]):
compare_tables(old, new_df)