2017-01-04 8 views
1

Я хочу вычислить сходство между элементами (0,1,2,3 ..) на основе их временной информации. Временная информация может быть мгновенной (начальная), временным интервалом (startdate, enddate) или null (NaT); см. пример кадра данных (df_for) ниже.Python - вычислить сходство между элементами на основе временной информации (мгновенный, интервал)

enter image description here

  1. {мгновенная, мгновенные}: если равно сим = 1, в противном случае сим = 0
  2. {мгновенная, нуль} или наоборот, сим = 0
  3. {мгновенная, интервал} : если мгновенно в пределах интервала, sim = 1 или если интервал содержит момент, sim = 1
  4. {интервал, интервал}: если интервалы перекрываются, sim = пересечение обоих интервалов/объединение обоих интервалов
  5. {interval, интервал}: если a terval содержит другое, затем 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. 

ответ

2

Я вижу несколько способов упрощения кода.

  1. Во-первых, я хотел бы предложить переместить код сравнения в отдельную функцию, которая принимает st1, ed1, st2 и ed2 в качестве аргументов. (В качестве примечания, почему 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.Тип bool Python является подклассом int, поэтому они должны автоматически преобразовываться в числа, когда вы назначаете их в массив numpy (вы также можете явно указать float, если хотите, чтобы функция всегда возвращала один и тот же тип).

  4. Обрабатывать все случаи с нулями в верхней части. Это простой случай, и если вы сначала справляетесь с ними (и возвращайтесь, так что остальная часть функции не запускается), вам не нужно проверять как можно больше вещей для null. Вам не нужно обрабатывать null/null, null/instantand null/interval отдельно, просто вернуть ноль, если начальное значение равно нулю: 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, но я не изменил их, так как я не знаю панд почти так же хорошо, как и я знаю.

  3. Говоря о моем недостатке знаний о пандах, возможно, существует возможность применить функцию сравнения непосредственно к «объединению» данных с самим собой. Я не знаю, как это сделать. Если он существует, он, вероятно, будет быстрее, чем вложенные циклы, которые я оставил, в основном, неизменным в вызывающем коде (даже если он не оптимизирует симметричные случаи).

+0

спасибо за улучшение кода. это min_interval, ссылающийся на «размер меньшего интервала (а не размер объединения)»? i обновил ed1, ed2 в вашем коде до et1, et2 .. – kitchenprinzessin

+0

Да, это то, что должна сделать эта часть кода. Я пропустил несколько переменных 'ed', поэтому я отредактировал их, чтобы исправить их. – Blckknght

+0

спасибо.Интересно, может ли этот случай (разрыв в сходстве) примениться к {мгновенному, мгновенному} тоже. – kitchenprinzessin

0

Не пытайтесь дать полный ответ здесь. Я бы создал класс, чтобы получить время начала и окончания, а затем выполнить все нулевые проверки недействительными на конструкторе. Позже в этом классе я перегружу операторов сравнения. Как eq для == и ne for! = В перегрузках вы можете попробовать вернуть целые значения вместо True и False. И со всем этим набором я буду переписывать, может быть, как Мгновенный (данные [i] [0], данные [i] [1]) == Мгновенный (данные [j] [0], данные [j] [1])

И получить то, что вам нужно оттуда