1

我想根据它们的时间信息计算项目(0,1,2,3..)之间的相似性。时间信息可以是时间瞬间(开始日期)、时间间隔(开始日期、结束日期)或空值(NaT);请参阅下面的数据框(df_for)示例。

在此处输入图像描述

  1. {instant,instant} :如果等于 sim = 1,否则 sim =0
  2. {instant,null} 或反之,sim =0
  3. {instant, interval}:如果区间内的瞬间,sim =1 或者如果一个区间包含一个瞬间,sim = 1
  4. {interval,interval} :如果区间重叠,sim = 两个区间的交集/两个区间的并集
  5. {interval,interval} :如果一个区间包含另一个,则 sim = 1

以下 python 代码从数据帧中获取时间信息并执行上述条件 (1-5)。代码很冗长,我想知道是否有一种聪明的方式/lib来使用python计算时间段和时间瞬间之间的相似性。

m, k = df_for.shape
sim = np.zeros((m, m))
data = df_for.values
for i in range(m):
    for j in range(m):
        if i != j:
            st1 = data[i][0]
            ed1 = data[i][1]
            st2 = data[j][0]
            ed2 = data[j][1]
            #both items are null values
            if pd.isnull(st1) and pd.isnull(ed1) and pd.isnull(st2) and pd.isnull(ed2):
                sim[i][j] = 0.
            # {instant, instant} => equal, not equal
            if pd.notnull(st1) and pd.isnull(ed1) and pd.notnull(st2) and pd.isnull(ed2):
                if st1 == st2:
                    sim[i][j] = 1.
                else:
                    sim[i][j] = 0.
            # {instant, null} => sim is 0
            if pd.notnull(st1) and pd.isnull(ed1) and pd.isnull(st2) and pd.isnull(ed2):
                sim[i][j] = 0.
            # {instant, interval} => meets, during
            if pd.notnull(st1) and pd.isnull(ed1) and pd.notnull(st2) and pd.notnull(ed2):
                    if(st2 <= st1 <= ed2):
                        sim[i][j] = 1. #a time is between two other times
                    else:
                        sim[i][j] = 0.
            # {interval, instant} => meets, contains
            if pd.notnull(st1) and pd.notnull(ed1) and pd.notnull(st2) and pd.isnull(ed2):
                    if(st1 <= st2 <= ed1):
                        sim[i][j] = 1. #a time is between two other times
                    else:
                        sim[i][j] = 0.
            # {interval, interval} => equal, overlaps, not overlaps
            if pd.notnull(st1) and pd.notnull(ed1) and pd.notnull(st2) and pd.notnull(ed2): 
                if (st1 <= st2 <= ed1) or (st2 <= st1 <= ed2):
                    intersect = min(ed1,ed2)- max(st1,st2) # earliestend-lateststart
                    union = max(st1,st2,ed1,ed2) - min(ed1,ed2,st1,st2)
                    overlaps = intersect/union
                    #print(intersect/np.timedelta64(1, 'D'),union/np.timedelta64(1, 'D'))
                    if (st1 > st2 and ed1 < ed2) or (st1 < st2 and ed1 > ed2): # contains, during
                        overlaps = 1.0
                    sim[i][j]=overlaps  
                else:
                    sim[i][j] = 0.  
        else:
            sim[i][j] = 1.
4

2 回答 2

2

我可以看到几种简化代码的方法。

  1. 首先,我建议将比较代码移到一个单独的函数中,该函数以st1ed1st2ed2作为参数。(作为旁注,为什么st(开始时间)但是ed(结束日期)?一致的名称可能会很好。)您可以return将结果传递给调用代码,这将负责对结果数组进行分配.

  2. 说到那个调用代码......它不需要为每一对时间范围调用比较函数。结果应该始终是对称的(例如compare(data[i][0], data[i][1], data[j][0], data[j][1])将返回与 相同的东西compare(data[j][0], data[j][1], data[i][0], data[i][1]))。因此,只需调用其中一个,并将结果分配给sim[i][j]sim[j][i]

  3. 而不是很多if some_test_expression: return 1; else: return 0块,只返回比较的结果:return some_test_expression. Python 的bool类型是 的子类int,因此当您将它们分配到 numpy 数组中时,它们应该自动转换为数字(float如果您希望函数始终返回相同的类型,也可以显式转换为)。

  4. 处理顶部为空的所有情况。它们是一个简单的例子,如果你先处理它们(然后返回,所以函数的其余部分不会运行),你以后不需要检查很多东西是否为 null。您不需要分别处理 null/null、null/instant 和 null/interval,如果任一起始值为 null,则只需返回零:if isnull(st1) or isnull(st2): return 0.

  5. 即时/间隔比较是对称的,因此只需以一种方式编写逻辑并翻转参数,如果它们最初的顺序错误:if isnull(ed2): st1, ed1, st2, ed2 = st2, ed2, st1, ed1

  6. 我看不出有太多可以通过您在瞬间和间隔或两个间隔之间进行比较的具体实现来改进(除了上面已经提到的一般情况)。但是,我想说的是,您的重叠区间公式在相似性上会有很大的不连续性,因为小区间慢慢地与较大的区间重叠更多,然后完全包含。例如,在一小时间隔前一毫秒开始的一秒间隔将导致0.000277( 0.999 / (3600 + 0.001)) 两者之间的相似值,但与较大间隔同时开始的间隔将具有相似性1.0(一个很大的区别)。一个更连续变化的相似性度量来自于将重叠量除以较小间隔的大小(而不是联合的大小)。对于一个完全包含另一个间隔的间隔,此公式不需要特殊情况,因为默认计算将给出1.0已经的相似性(重叠将是较小间隔的大小,因此您将其除以自身)。

所以,把所有这些放在一起,这就是我重写的方式:

def compare_intervals(st1, et1, st2, et2): # note I've renamed the ed's to et
    # all nulls
    if pd.isnull(st1) or pd.isnull(st2):
        return 0.

    # {instant, instant} => equal, not equal
    if pd.isnull(et1) and pd.isnull(et2):
        return float(st1 == st2)

    # {instant, interval} => flip params (to be handled in next block)
    if pd.isnull(et1):
        st1, et1, st2, et2 = st2, et2, st1, et1

    # {interval, instant} => meets, contains
    if pd.isnull(et2):
        return float(st1 <= st2 <= et1)

    # {interval, interval} => equal, overlaps, not overlaps
    if (st1 <= st2 <= et1) or (st2 <= st1 <= et2):
        intersect = min(et1, et2) - max(st1, st2)
        min_interval = min(et1 - st1, et2 - st2) # using minimum instead of union
        return intersect / min_interval

    return 0. # intervals didn't overlap

m, k = df_for.shape
sim = np.zeros((m, m))
data = df_for.values
for i in range(m):
    for j in range(i, m): # we only iterate on j values >= i
        if i == j:
            sim[i,j] = 1.
        else:
            sim[i,j] = sim[j,i] = compare_intervals(data[i][0], data[i][1],
                                                    data[j][0], data[j][1])

一些我无法放入代码中的注释的注释:

  1. 在函数的最后一步不需要测试间隔值compare_interval,因为所有其他情况(涉及瞬间和空值)都已处理。

  2. 我已将sim数组的分配更改为使用元组索引,因为这比多维 numpy 数组的嵌套索引更自然(如果使用嵌套索引,切片不起作用)。您可能可以对data查找执行相同的操作,但我没有更改它们,因为我对 pandas 的了解几乎不如我对 numpy 的了解。

  3. 说到我缺乏熊猫知识,可能有一种方法可以将比较函数直接应用于数据框与其自身的“连接”。我不知道该怎么做。如果它存在,它可能会比我在调用代码中基本保持不变的嵌套循环更快(即使它没有优化对称情况)。

于 2017-01-09T06:15:41.683 回答
0

不想在这里给出完整的答案。我会创建一个类来接收开始和结束时间,然后对构造函数执行所有 null 而非 null 检查。稍后在该课程中,我将重载比较运算符。像==的eq和 != 的ne在重载上,您可以尝试返回整数值而不是 True 和 False。有了所有这些我会重写可能像 Instant( data[i][0], data[i][1] ) == Instant( data[j][0], data[j][1])

并从那里得到你需要的东西

于 2017-01-09T05:23:41.433 回答