Условное сопоставление массива Numpy - PullRequest
5 голосов
/ 19 декабря 2011

Мне нужно сопоставить два очень больших массива Numpy (один - 20000 строк, другой - около 100000 строк), и я пытаюсь создать сценарий, чтобы сделать это эффективно. Простое зацикливание массивов невероятно медленно, может кто-нибудь предложить лучший способ? Вот что я пытаюсь сделать: массив datesSecondDict и массив pwfs2Dates содержат значения datetime, мне нужно взять каждое значение datetime из массива pwfs2Dates (меньший массив) и посмотреть, есть ли значение datetime, подобное этому (плюс минус 5 минут) в массиве datesSecondDict (может быть больше 1). Если есть один (или более), я заполняю новый массив (того же размера, что и массив pwfs2Dates) значением (одним из значений) из массива valsSecondDict (который является просто массивом с соответствующими числовыми значениями для datesSecondDict). Вот решение @unutbu и @joaquin, которое сработало для меня (спасибо, ребята!):

import time
import datetime as dt
import numpy as np

def combineArs(dict1, dict2):
   """Combine data from 2 dictionaries into a list.
   dict1 contains primary data (e.g. seeing parameter).
   The function compares each timestamp in dict1 to dict2
   to see if there is a matching timestamp record(s)
   in dict2 (plus/minus 5 minutes).
   ==If yes: a list called data gets appended with the
   corresponding parameter value from dict2.
   (Note that if there are more than 1 record matching,
   the first occuring value gets appended to the list).
   ==If no: a list called data gets appended with 0."""
   # Specify the keys to use    
   pwfs2Key = 'pwfs2:dc:seeing'
   dimmKey = 'ws:seeFwhm'

   # Create an iterator for primary dict 
   datesPrimDictIter = iter(dict1[pwfs2Key]['datetimes'])

   # Take the first timestamp value in primary dict
   nextDatePrimDict = next(datesPrimDictIter)

   # Split the second dictionary into lists
   datesSecondDict = dict2[dimmKey]['datetime']
   valsSecondDict  = dict2[dimmKey]['values']

   # Define time window
   fiveMins = dt.timedelta(minutes = 5)
   data = []
   #st = time.time()
   for i, nextDateSecondDict in enumerate(datesSecondDict):
       try:
           while nextDatePrimDict < nextDateSecondDict - fiveMins:
               # If there is no match: append zero and move on
               data.append(0)
               nextDatePrimDict = next(datesPrimDictIter)
           while nextDatePrimDict < nextDateSecondDict + fiveMins:
               # If there is a match: append the value of second dict
               data.append(valsSecondDict[i])
               nextDatePrimDict = next(datesPrimDictIter)
       except StopIteration:
           break
   data = np.array(data)   
   #st = time.time() - st    
   return data

Спасибо, Айна.

Ответы [ 3 ]

6 голосов
/ 19 декабря 2011

Сортируются ли даты в массиве?

  • Если да, вы можете ускорить сравнение, отрываясь от внутреннего сравнение цикла, когда его даты больше, чем дата, заданная внешняя петля. Таким образом, вы сделаете сравнение за один проход вместо цикл dimVals элементов len(pwfs2Vals) раз
  • Если нет, возможно, вам следует преобразовать текущий массив pwfs2Dates, например, в массив пар [(date, array_index),...], а затем вы можете отсортировать по дата всех ваших массивов, чтобы сделать сравнение за один проход, указанное выше и на в то же время, чтобы иметь возможность получить исходные индексы, необходимые для установки data[i]

например, если массивы уже отсортированы (я использую списки здесь, не уверен, что для этого вам нужны массивы): ( Отредактировано : теперь используется итератор, чтобы не зацикливать pwfs2Dates с начала на каждом шаге):

pdates = iter(enumerate(pwfs2Dates))
i, datei = pdates.next() 

for datej, valuej in zip(dimmDates, dimvals):
    while datei < datej - fiveMinutes:
        i, datei = pdates.next()
    while datei < datej + fiveMinutes:
        data[i] = valuej
        i, datei = pdates.next()

В противном случае, если они не были упорядочены, и вы создали отсортированные, проиндексированные списки, подобные этому:

pwfs2Dates = sorted([(date, idx) for idx, date in enumerate(pwfs2Dates)])
dimmDates = sorted([(date, idx) for idx, date in enumerate(dimmDates)])

код будет:
( Отредактировано : теперь используется итератор, чтобы не зацикливать pwfs2Dates с начала на каждом шаге):

pdates = iter(pwfs2Dates)
datei, i = pdates.next()

for datej, j in dimmDates:
    while datei < datej - fiveMinutes:
        datei, i = pdates.next()
    while datei < datej + fiveMinutes:
        data[i] = dimVals[j]
        datei, i = pdates.next()

большой!

..

  1. Обратите внимание, что dimVals:

    dimVals  = np.array(dict1[dimmKey]['values'])
    

    не используется в вашем коде и может быть исключен.

  2. Обратите внимание, что ваш код значительно упрощается благодаря циклу сам массив вместо использования xrange

Редактировать: Ответ от unutbu устранить некоторые слабые места в коде выше. Я указываю их здесь для полноты:

  1. Использование next: next(iterator) предпочтительнее iterator.next(). iterator.next() является исключением из обычного правила именования, которое было исправлено в py3k, переименовывающем этот метод в iterator.__next__().
  2. Проверьте конец итератора с помощью try/except. После всего элементы в итераторе завершаются при следующем вызове next() создает исключение StopItate. Используйте try/except, чтобы любезно вырваться из цикла, когда это произойдет. Для конкретного случая ОП вопрос это не проблема, потому что два массива одинаковы размер, поэтому цикл for завершается в то же время, что и итератор. Так нет Возникло исключение. Тем не менее, могут быть случаи были dict1 и dict2 не одинакового размера. И в этом случае есть возможность исключение восстания. Вопрос в том, что лучше использовать, попробовать / исключить или подготовить массивы перед циклом, сравнивая их с более коротким.
4 голосов
/ 19 декабря 2011

Опираясь на идею Хоакина :

import datetime as dt
import itertools

def combineArs(dict1, dict2, delta = dt.timedelta(minutes = 5)):
    marks = dict1['datetime']
    values = dict1['values']
    pdates = iter(dict2['datetime'])

    data = []
    datei = next(pdates)
    for datej, val in itertools.izip(marks, values):
        try:
            while datei < datej - delta:
                data.append(0)
                datei = next(pdates)
            while datei < datej + delta:
                data.append(val)
                datei = next(pdates)
        except StopIteration:
            break
    return data

dict1 = { 'ws:seeFwhm':
          {'datetime': [dt.datetime(2011, 12, 19, 12, 0, 0),
                        dt.datetime(2011, 12, 19, 12, 1, 0),
                        dt.datetime(2011, 12, 19, 12, 20, 0),
                        dt.datetime(2011, 12, 19, 12, 22, 0),
                        dt.datetime(2011, 12, 19, 12, 40, 0), ],
           'values': [1, 2, 3, 4, 5] } }
dict2 = { 'pwfs2:dc:seeing':
          {'datetime': [dt.datetime(2011, 12, 19, 12, 9),
                         dt.datetime(2011, 12, 19, 12, 19),
                         dt.datetime(2011, 12, 19, 12, 29),
                         dt.datetime(2011, 12, 19, 12, 39),
                        ], } }

if __name__ == '__main__':
    dimmKey = 'ws:seeFwhm'
    pwfs2Key = 'pwfs2:dc:seeing'    
    print(combineArs(dict1[dimmKey], dict2[pwfs2Key]))

выходы

[0, 3, 0, 5]
0 голосов
/ 19 декабря 2011

Я думаю, что вы можете сделать это с одним меньшим количеством циклов:

import datetime
import numpy

# Test data

# Create an array of dates spaced at 1 minute intervals
m = range(1, 21)
n = datetime.datetime.now()
a = numpy.array([n + datetime.timedelta(minutes=i) for i in m])

# A smaller array with three of those dates
m = [5, 10, 15]
b = numpy.array([n + datetime.timedelta(minutes=i) for i in m])

# End of test data

def date_range(date_array, single_date, delta):
    plus = single_date + datetime.timedelta(minutes=delta)
    minus = single_date - datetime.timedelta(minutes=delta)
    return date_array[(date_array < plus) * (date_array > minus)]

dates = []
for i in b:
    dates.append(date_range(a, i, 5))

all_matches = numpy.unique(numpy.array(dates).flatten())

Конечно, есть лучший способ собрать и объединить спички, но вы поймете идею ... Вы также можете использовать numpy.argwhere((a < plus) * (a > minus)) вернуть индекс вместо даты и использовать индекс, чтобы захватить всю строку и поместить ее в ваш новый массив.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...