Numpy数组条件匹配

5
我需要匹配两个非常大的Numpy数组(一个有20000行,另一个有大约100000行),并尝试构建一个脚本以高效地完成它。对数组进行简单循环非常慢,有人能提出更好的方法吗?这是我想做的:数组datesSecondDict和数组pwfs2Dates包含日期时间值,我需要从数组pwfs2Dates(较小的数组)中取出每个日期时间值,并查看是否存在类似于数组datesSecondDict中的日期时间值(加减5分钟)。如果有一个或多个,我将使用数组valsSecondDict(只是与datesSecondDict相应的数字值的数组)中的值之一填充一个新数组(大小与数组pwfs2Dates相同)。这是@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

感谢您,Aina。
3个回答

6

这个数组的日期是否已经排序?

  • 如果是,你可以通过在内部循环比较时一旦发现该日期大于外部循环给出的日期就停止循环,从而加快比较速度。这样你就只需要一次循环比较而不是多次循环dimValslen(pwfs2Vals)次。
  • 如果没有排序,也许你应该将当前的pwfs2Dates数组转换为一个元素为[(date, array_index),...]的数组对,然后你可以按日期对所有数组进行排序,以进行上述单次比较,并且能够获得设置data[i]所需的原始索引。

例如,如果数组已经排序(我在此处使用列表,不确定您是否需要数组):

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. Note that dimVals:

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

    is not used in your code and can be eliminated.

  2. Note that your code gets greatly simplified by looping through the array itself instead of using xrange
编辑:unutbu的答案指出了上面代码中一些薄弱的部分,我在这里列出来以保证完整性:
  1. 使用next:优先使用next(iterator)而非iterator.next()iterator.next()是一个例外,不符合常规命名规则,在py3k中已经被重命名为iterator.__next__()
  2. 通过try/except检查迭代器是否到达末尾。当迭代器中的所有项都完成后,下一次调用next()会产生一个StopIteration异常。 当发生这种情况时,请使用try/except退出循环。 对于OP问题的特定情况,这不是一个问题,因为两个数组的大小相同,所以for循环与迭代器同时结束。 因此,不会引发任何异常。 但是,dict1和dict2的大小可能不相同。 在这种情况下,可能会引发异常。 问题是:哪种方法更好,使用try/except还是在循环之前通过将它们等同于较短的那个来准备数组。

4

Joaquin的想法 上进一步提升:

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

我认为你可以少用一个循环来完成它:

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))来返回索引而不是日期,并使用索引来获取整行数据并将其放入新数组中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接