数值模糊比较加入 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 列。让我们根据它们在这种情况下的用处来分解它们。

MarketIDFltNum 可以任意更改,所以这对我们没有帮助。 OriginDest 我假设必须相同且无法更改,所以我们会检查一下 DepCentralArrCentral 是最重要的,根据问题陈述最多可以更改 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)