Как выровнять два массива numeries с неравными размерами?

У меня есть два массива numpy, содержащих временные ряды (временные метки unix).
Я хочу найти пары временных меток (1 из каждого массива), чья разница находится в пределах .

Для этого мне нужно выровнять два из данных временных рядов на два массива, так что каждый индекс имеет свою ближайшую пару. (В случае двух временных меток в массивах, одинаково близких к другой метке времени в другом массиве, я не против выбирать один из них, поскольку количество пар более важно, чем фактические значения.)

Таким образом, выровненный набор данных будет содержать два массива одного и того же размера, а также меньший массив, заполненный пустыми данными.

Я думал об использовании пакета timeseries и функции align.
Но я не уверен, что использовать выровненные для моих данных, которые являются таймсерами.

Пример рассмотрим два массива timeseries:

ts1=np.array([ 1311242821.0, 1311242882.0, 1311244025.0, 1311244145.0, 1311251330.0, 
               1311282555.0, 1311282614.0])
ts2=np.array([ 1311226761.0, 1311227001.0, 1311257033.0, 1311257094.0, 1311281265.0])

Выходной образец:

Теперь для ts2[2] (1311257033.0) его ближайшая пара должна быть ts1[4] (1311251330.0), потому что разница составляет 5703.0, которая находится внутри threshold, и она наименьшая. Теперь, когда ts2[2] и ts1[4] уже спарены, они должны быть исключены из других вычислений.

Такие пары должны быть найдены, поэтому массив Output может быть длиннее фактических массивов

abs (ts1 [0] -ts2 [0]) = 16060

abs (ts1 [0] -ts2 [1]) = 15820//пара
abs (ts1 [0] -ts2 [2]) = 14212
abs (ts1 [0] -ts2 [3]) = 14273
abs (ts1 [0] -ts2 [4]) = 38444


abs (ts1 [1] -ts2 [0]) = 16121
abs (ts1 [1] -ts2 [1]) = 15881
abs (ts1 [1] -ts2 [2]) = 14151
abs (ts1 [1] -ts2 [3]) = 14212
abs (ts1 [1] -ts2 [4]) = 38383


abs (ts1 [2] -ts2 [0]) = 17264
abs (ts1 [2] -ts2 [1]) = 17024
abs (ts1 [2] -ts2 [2]) = 13008
abs (ts1 [2] -ts2 [3]) = 13069
abs (ts1 [2] -ts2 [4]) = 37240


abs (ts1 [3] -ts2 [0]) = 17384
abs (ts1 [3] -ts2 [1]) = 17144
abs (ts1 [3] -ts2 [2]) = 12888
abs (ts1 [3] -ts2 [3]) = 17144
abs (ts1 [3] -ts2 [4]) = 37120


abs (ts1 [4] -ts2 [0]) = 24569
abs (ts1 [4] -ts2 [1]) = 24329
abs (ts1 [4] -ts2 [2]) = 5703//пара
abs (ts1 [4] -ts2 [3]) = 5764
abs (ts1 [4] -ts2 [4]) = 29935


abs (ts1 [5] -ts2 [0]) = 55794
abs (ts1 [5] -ts2 [1]) = 55554
abs (ts1 [5] -ts2 [2]) = 25522
abs (ts1 [5] -ts2 [3]) = 25461
abs (ts1 [5] -ts2 [4]) = 1290//пара


abs (ts1 [6] -ts2 [0]) = 55853
abs (ts1 [6] -ts2 [1]) = 55613
abs (ts1 [6] -ts2 [2]) = 25581
abs (ts1 [6] -ts2 [3]) = 25520
abs (ts1 [6] -ts2 [4]) = 1349


Итак, пары: (ts1[0],ts2[1]), (ts1[4],ts2[2]), (ts1[5],ts2[4])
Остальные элементы должны иметь null как их пару Последние два массива будут иметь размер 9.

Пожалуйста, дайте мне знать, ясно ли этот вопрос.

Ответ 1

Я не знаю, что вы имеете в виду с выравниванием временных меток. Но вы можете использовать временной модуль для представления временных меток как плавающих или целых чисел. На первом этапе вы можете преобразовать любой пользовательский формат в массив, определенный time.struct_time. На втором этапе вы можете преобразовать это в начало формы эпохи в секундах. Тогда у вас есть integervalues ​​для выполнения вычислений с отметками времени.

Как преобразовать формат пользователя с помощью time.strptime() хорошо объясняется в docs:

    >>> import time
    >>> t = time.strptime("30 Nov 00", "%d %b %y")
    >>> t
    time.struct_time(tm_year=2000, tm_mon=11, tm_mday=30, tm_hour=0, tm_min=0,
             tm_sec=0, tm_wday=3, tm_yday=335, tm_isdst=-1)
    >>> time.mktime(t)
    975538800.0

Ответ 2

Помимо небольших ошибок в вопросе, я мог догадаться, в чем проблема, на самом деле проблема.

То, что вы смотрите, является классическим примером Задача назначения. Scipy предоставляет вам реализацию венгерский алгоритм, проверьте документ здесь. Это не должно быть timestamps, может быть любое число (целое или плавающее).

Ниже фрагмент будет работать с 2 массивами numpy разных размеров вместе с порогом, чтобы дать вам либо массив затрат (отфильтрованный по пороговому значению), либо пары индексов, соответствующие массивам с двумя numpy (опять же, пары, стоимость которых фильтруется порог).

Комментарии пройдут вас, используя пример массивов временных меток в качестве примера.

import numpy as np
from scipy.optimize import linear_sum_assignment


def closest_pairs(inp1, inp2, threshold=np.inf):
    cost = np.zeros((inp1.shape[0], inp2.shape[0]), dtype=np.int64)

    for x in range(ts1.shape[0]):
        for y in range(ts2.shape[0]):
            cost[x][y] = abs(ts1[x] - ts2[y])

    print(cost)
    # cost for the above example:
    # [[16060 15820 14212 14273 38444]
    # [16121 15881 14151 14212 38383]
    # [17264 17024 13008 13069 37240]
    # [17384 17144 12888 12949 37120]
    # [24569 24329  5703  5764 29935]
    # [55794 55554 25522 25461  1290]
    # [55853 55613 25581 25520  1349]]

    # hungarian algorithm implementation provided by scipy
    row_ind, col_ind = linear_sum_assignment(cost)
    # row_ind = [0 1 3 4 5], col_ind = [1 0 3 2 4] 
    # where (ts1[5] - ts2[4]) = 1290

    # if you want the distances only
    out = [item 
           for item in cost[row_ind, col_ind] 
           if item < threshold]

    # if you want the pair of indices filtered by the threshold
    pairs = [(row, col) 
             for row, col in zip(row_ind, col_ind) 
             if cost[row, col] < threshold]

    return out, pairs


if __name__ == '__main__':
    out, pairs = closest_pairs(ts1, ts2, 6000)
    print(out, pairs)
    # out = [5703, 1290] 
    # pairs = [(4, 2), (5, 4)]

    out, pairs = closest_pairs(ts1, ts2)
    print(out, pairs)
    # out = [15820, 16121, 12949, 5703, 1290] 
    # pairs = [(0, 1), (1, 0), (3, 3), (4, 2), (5, 4)]

Ответ 3

Решение с использованием numpy Mask arrays выводит выровненные Timeseries (_ts1, _ts2).
Результат - 3 пары и только Пары с расстоянием 1 могут использоваться для выравнивания Timeseries the Thfore Threshold = 1.

def compute_diffs(threshold):
    dtype = [('diff', int), ('ts1', int), ('ts2', int), ('threshold', int)]
    diffs = np.empty((ts1.shape[0], ts2.shape[0]), dtype=dtype)
    pairs = np.ma.make_mask_none(diffs.shape)

    for i1, t1 in enumerate(ts1):
        for i2, t2 in enumerate(ts2):
            diffs[i1, i2] = (abs(t1 - t2), i1, i2, abs(i1-i2))

        d1 = diffs[i1][diffs[i1]['threshold'] == threshold]
        if d1.size == 1:
            (diff, y, x, t) = d1[0]
            pairs[y, x] = True
    return diffs, pairs

def align_timeseries(diffs):
    def _sync(ts, ts1, ts2, i1, i2, ii):
        while i1 < i2:
            ts1[ii] = ts[i1]; i1 +=1
            ts2[ii] = DTNULL
            ii += 1
        return ii, i1

    _ts1 = np.array([DTNULL]*9)
    _ts2 = np.copy(_ts1)
    ii = _i1 = _i2 = 0

    for n, (diff, i1, i2, t) in enumerate(np.sort(diffs, order='ts1')):
        ii, _i1 = _sync(ts1, _ts1, _ts2, _i1, i1, ii)
        ii, _i2 = _sync(ts2, _ts2, _ts1, _i2, i2, ii)

        if _i1 == i1:
            _ts1[ii] = ts1[i1]; _i1 += 1
            _ts2[ii] = ts2[i2]; _i2 += 1
            ii += 1

    ii, _i1 = _sync(ts1, _ts1, _ts2, _i1, ts1.size, ii)
    return _ts1, _ts2

главная:

diffs, pairs = compute_diffs(threshold=1)
print('diffs[pairs]:{}'.format(diffs[pairs]))
_ts1, _ts2 = align_timeseries(diffs[pairs])
pprint(ts1, ts2, _ts1, _ts2)

Выход

diffs[pairs]:[(15820, 0, 1) ( 5703, 4, 2) ( 1290, 5, 4)]
           ts1                  ts2                    _ts1          diff          _ts2
0: 2011-07-21 12:07:01  2011-07-21 07:39:21     ---- -- -- -- -- --  ----   2011-07-21 07:39:21
1: 2011-07-21 12:08:02  2011-07-21 07:43:21     2011-07-21 12:07:01 15820   2011-07-21 07:43:21
2: 2011-07-21 12:27:05  2011-07-21 16:03:53     2011-07-21 12:08:02  ----   ---- -- -- -- -- --
3: 2011-07-21 12:29:05  2011-07-21 16:04:54     2011-07-21 12:27:05  ----   ---- -- -- -- -- --
4: 2011-07-21 14:28:50  2011-07-21 22:47:45     2011-07-21 12:29:05  ----   ---- -- -- -- -- --
5: 2011-07-21 23:09:15  ---- -- -- -- -- --     2011-07-21 14:28:50  5703   2011-07-21 16:03:53
6: 2011-07-21 23:10:14  ---- -- -- -- -- --     ---- -- -- -- -- --  ----   2011-07-21 16:04:54
7: ---- -- -- -- -- --  ---- -- -- -- -- --     2011-07-21 23:09:15  1290   2011-07-21 22:47:45
8: ---- -- -- -- -- --  ---- -- -- -- -- --     2011-07-21 23:10:14  ----   ---- -- -- -- -- --

Протестировано с помощью Python: 3.4.2

Ответ 4

Чтобы иметь пары временных рядов, я советую вам сначала вычислить ваши пары индексов (get_pairs). А затем вычислить пару временных рядов (get_tspairs).

В get_pairs я сначала вычисляю матрицу m, которая воспроизводит разницу между каждой точкой между двумя временными рядами. Таким образом, матрица формы (len(ts1), len(ts2)). Затем я выбираю наименьшее расстояние среди всех. Чтобы не выбирать несколько раз один и тот же индекс, я устанавливал на np.inf расстояние для выбранных индексов. Я продолжаю этот процесс, пока мы не сможем выбрать больше кортежей индексов. Если минимальное расстояние выше порогового значения, процесс прерывается.

Как только я получил свои пары индексов, я вызываю get_tspairs, чтобы генерировать пары временных рядов. Первым шагом здесь является объединение временных рядов с выбранным набором индексов, затем добавление индексов, которые не были выбраны, и связать их с None (эквивалент NULL в Python).

Что дает:

import numpy as np
import operator

ts1=np.array([ 1311242821.0, 1311242882.0, 1311244025.0, 1311244145.0, 1311251330.0, 
               1311282555.0, 1311282614.0])
ts2=np.array([ 1311226761.0, 1311227001.0, 1311257033.0, 1311257094.0, 1311281265.0])

def get_pairs(ts1, ts2, threshold=np.inf):
    m = np.abs(np.subtract.outer(ts1, ts2))
    indices = []
    while np.ma.masked_invalid(m).sum() != 'masked':
        ind = np.unravel_index(np.argmin(m), m.shape)
        if m[ind] < threshold:
            indices.append(ind)
            m[:,ind[1]] = np.inf
            m[ind[0],:] = np.inf
        else:
            m= np.inf
    return indices

def get_tspairs(pairs, ts1, ts2):
    ts_pairs = [(ts1[p[0]], ts2[p[1]]) for p in pairs]

    # We separate the selected indices from ts1 and ts2, then sort them
    ind_ts1 = sorted(map(operator.itemgetter(0), pairs))
    ind_ts2 = sorted(map(operator.itemgetter(1), pairs))

    # We only keep the non-selected indices
    l1 = np.delete(np.arange(len(ts1), dtype=np.int64), ind_ts1)
    l2 = np.delete(np.arange(len(ts2), dtype=np.int64), ind_ts1)

    ts_pairs.extend([(ts1[i], None) for i in l1])
    ts_pairs.extend([(ts2[i], None) for i in l2])

    return ts_pairs

if __name__ == '__main__':
    pairs = get_pairs(ts1, ts2)
    print(pairs)
    # [(5, 4), (4, 2), (3, 3), (0, 1), (1, 0)]

    ts_pairs = get_tspairs(pairs, ts1, ts2)
    print(ts_pairs)
    # [(1311282555.0, 1311281265.0), (1311251330.0, 1311257033.0), (1311244145.0, 1311257094.0), (1311242821.0, 1311227001.0), (1311242882.0, 1311226761.0), (1311244025.0, None), (1311282614.0, None), (1311257033.0, None)]

Ответ 5

У вас есть два отсортированных списка временных меток, и вам нужно объединить их в один, сохраняя элементы каждого списка отдельно друг от друга, вычисляя разницу, когда есть переключатель или изменение списка.

Мое первое решение без использования numpy и состоит из 1) добавления к каждому элементу идентификатора списка, к которому он принадлежит, 2) сортировки по метке времени, 3) группы по идентификатору списка, 4 ) построить новый список, разделяющий каждый элемент и вычисляющий разницу при необходимости:

import numpy as np
from itertools import groupby
from operator import itemgetter

ts1 = np.array([1311242821.0, 1311242882.0, 1311244025.0, 1311244145.0, 1311251330.0, 1311282555.0, 1311282614.0])               
ts2 = np.array([1311226761.0, 1311227001.0, 1311257033.0, 1311257094.0, 1311281265.0])

def without_numpy():
    # 1) Add the list id to each element
    all_ts = [(_, 0) for _ in ts1] + [(_, 1) for _ in ts2] 
    merged_ts = [[], [], []]
    # 2) Sort by timestamp and 3) Group by list id
    groups = groupby(sorted(all_ts), key=itemgetter(1))
    # 4) Construct the new list
    diff = False
    for key, g in groups:
        group = list(g)
        ### See Note    
        for ts, k in group:
            if diff:
                merged_ts[key] = merged_ts[key][:-1]
                merged_ts[2][-1] = abs(end - ts)
                diff = False
            else:
                merged_ts[not key].append(None)
                merged_ts[2].append(None)
            merged_ts[key].append(ts)
        end = ts
        diff = True
    return merged_ts

Используя numpy, процедура немного отличается и состоит в 1) добавлении к каждому элементу идентификатора списка, к которому он принадлежит, и некоторых вспомогательных индексов, 2) сортировки по метке времени, 3) пометить каждый коммутатор или изменить список, 4) выполнить сканирование суммы предыдущих флагов, 5) рассчитать собственный индекс каждого элемента в объединенном списке:

import numpy as np

ts1 = np.array([1311242821.0, 1311242882.0, 1311244025.0, 1311244145.0, 1311251330.0, 1311282555.0, 1311282614.0])
ts2 = np.array([1311226761.0, 1311227001.0, 1311257033.0, 1311257094.0, 1311281265.0])

    def with_numpy(): 
        dt = np.dtype([('ts', np.float), ('key', np.int), ('idx', np.int)])

        all_ts = np.sort(
                np.array(
                    [(_, 0, 1, 0) for _ in ts1] + [(_, 1, 1, 0) for _ in ts2], 
                    dtype=np.dtype([('ts', np.float), 
                                    ('key', np.int),    # list id
                                    ('index', np.int),  # index in result list
                                    ('single', np.int), # flag groups with only one element
                                   ])
                ), 
                order='ts'
            )


        #### See NOTE

        sh_dn = np.roll(all_ts, 1)

        all_ts['index'] = np.add.accumulate(all_ts['index']) - np.cumsum(
                            np.not_equal(all_ts['key'], sh_dn['key']))

        merged_ts = np.full(shape=(3, all_ts['index'][-1]+1), fill_value=np.nan)
        merged_ts[all_ts['key'], all_ts['index']] = all_ts['ts']
        merged_ts[2] = np.abs(merged_ts[0] - merged_ts[1])
        merged_ts = np.delete(merged_ts, -1, axis=1)
        merged_ts = np.transpose(merged_ts)

        return merged_ts

Обе функции, с или без numpy, дают одинаковый результат. Печать и форматирование могут быть выполнены по мере необходимости. Какая функция лучше зависит от данных, которые у вас есть.

ПРИМЕЧАНИЕ.. В случае, если есть переключатель в другой список, и после того, как только одно значение вернется к предыдущему списку, функции, как они выше, будут содержать только последние разница, возможно, потеряет меньшую разницу. В этом случае вы можете вставить следующие разделы в том месте, где "#### См. Примечание":

Для функции without_numpy вставьте:

if len(group) == 1:
    group.append(group[0])

Для функции with_numpy вставьте:

sh_dn = np.roll(all_ts, 1)
sh_up = np.roll(all_ts, -1)
all_ts['single'] = np.logical_and( np.not_equal(all_ts['key'], sh_dn['key']), 
                                           np.equal(sh_dn['key'], sh_up['key']))
singles = np.where(all_ts['single']==1)[0]
all_ts = np.insert(all_ts, singles, all_ts[singles])

Ответ 6

Я не уверен, правильно ли получил ваш вопрос. Если это так и предполагается, что ваши данные уже отсортированы, вы можете сделать это за один проход при использовании итераторов. просто адаптируйте пример к вашим потребностям.

left = iter(range(15, 60, 3))
right = iter(range(0, 50, 5))

try:
    i = next(left)
    j = next(right)
    while True:
        if abs(i-j) < 1:
            print("pair", i, j)
            i = next(left)
            j = next(right)
        elif i <= j:
            print("left", i, None)
            i = next(left)
        else:
            print("right", None, j)
            j = next(right)
except StopIteration:
    pass
# one of the iterators may have leftover elements
for i in left:
    print("left", i, None)
for j in right:
    print("right", None, j)

печатает

('right', None, 0)                                                                                                                                                                      
('right', None, 5)                                                                                                                                                                      
('right', None, 10)                                                                                                                                                                     
('pair', 15, 15)                                                                                                                                                                        
('left', 18, None)                                                                                                                                                                      
('right', None, 20)                                                                                                                                                                     
('left', 21, None)                                                                                                                                                                      
('left', 24, None)                                                                                                                                                                      
('right', None, 25)                                                                                                                                                                     
('left', 27, None)                                                                                                                                                                      
('pair', 30, 30)                                                                                                                                                                        
('left', 33, None)                                                                                                                                                                      
('right', None, 35)                                                                                                                                                                     
('left', 36, None)                                                                                                                                                                      
('left', 39, None)                                                                                                                                                                      
('right', None, 40)                                                                                                                                                                     
('left', 42, None)                                                                                                                                                                      
('pair', 45, 45)                                                                                                                                                                        
('left', 51, None)                                                                                                                                                                      
('left', 54, None)                                                                                                                                                                      
('left', 57, None)