【问题标题】:Numpy array conditional matchingNumpy 数组条件匹配
【发布时间】:2011-12-19 17:33:58
【问题描述】:

我需要匹配两个非常大的 Numpy 数组(一个是 20000 行,另一个是大约 100000 行),我正在尝试构建一个脚本来有效地完成它。简单地循环数组非常慢,有人可以提出更好的方法吗?这是我想要做的:数组datesSecondDict和数组pwfs2Dates包含日期时间值,我需要从数组pwfs2Dates(较小的数组)中获取每个日期时间值,看看是否有这样的日期时间值(加上减去 5 分钟)在数组 datesSecondDict 中(可能超过 1 个)。如果有一个(或多个)我用数组valsSecondDict 中的值(其中一个值)填充一个新数组(与数组pwfs2Dates 的大小相同)(这只是具有相应数值的数组) 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

谢谢, 艾娜。

【问题讨论】:

    标签: python arrays numpy


    【解决方案1】:

    数组日期是否排序?

    • 如果是,您可以通过从内部突破来加快比较 一旦其日期大于给出的日期,循环比较 外循环。这样,您将进行一次性比较,而不是 循环dimVals项目len(pwfs2Vals)
    • 如果不是,也许您应该将当前的pwfs2Dates 数组转换为,例如, 一对数组 [(date, array_index),...] 然后你可以排序 为所有数组添加日期以进行上面和 同时能够获取设置data[i]所需的原始索引

    例如,如果数组已经排序(我在这里使用列表,不确定您是否需要数组): (已编辑:现在使用 and 迭代器不在每一步从头开始循环 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)])
    

    代码是:
    已编辑:现在使用 and 迭代器不在每一步从头开始循环 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. 使用nextnext(iterator) 优于iterator.next()iterator.next() 是传统命名规则的一个例外,它 已在 py3k 中修复,将此方法重命名为 iterator.__next__()
    2. 使用try/except 检查迭代器的结尾。毕竟 迭代器中的项目在下次调用 next() 时完成 产生一个 StopIteration 异常。使用try/except 好意 发生这种情况时跳出循环。针对具体情况 OP问题这不是问题,因为这两个数组是相同的 大小,因此 for 循环与迭代器同时完成。所以不行 异常上升。但是,可能有 dict1 和 dict2 大小不一样。在这种情况下,有可能 异常正在上升。 问题是:什么更好,使用 try/except 或准备数组 在循环之前将它们与较短的相等。

    【讨论】:

      【解决方案2】:

      joaquin's idea 为基础:

      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]
      

      【讨论】:

        【解决方案3】:

        我认为你可以用更少的循环来做到这一点:

        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 &lt; plus) * (a &gt; minus)) 返回索引而不是日期,并使用索引来获取整行并放置将其放入您的新数组中。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-05-02
          • 1970-01-01
          • 2016-03-24
          相关资源
          最近更新 更多