2011-12-19 14 views
5

Muszę dopasować dwie bardzo duże tablice Numpy (jedna to 20000 wierszy, a kolejne około 100000 wierszy) i próbuję zbudować skrypt, aby to zrobić wydajnie. Prosta pętla nad tablicami jest niesamowicie powolna, czy ktoś może zaproponować lepszy sposób? Oto, co próbuję zrobić: tablica datesSecondDict i tablica pwfs2Dates zawierają wartości datetime, muszę wziąć każdą wartość datetime z tablicy pwfs2Dates (mniejsza tablica) i sprawdzić, czy istnieje wartość datetime (plus minus 5 minut) w tablicy datesSecondDict (może być ich więcej niż 1). Jeśli istnieje jeden (lub więcej), zapełniam nową tablicę (o tym samym rozmiarze co tablica pwfs2Dates) z wartością (jedną z wartości) z tablicy valsSecondDict (która jest po prostu tablicą z odpowiednimi wartościami liczbowymi do datesSecondDict). Oto rozwiązanie przez @unutbu i @joaquin że pracował dla mnie (dzięki chłopaki!):Dopasowanie warunkowe tablicy Numpy

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 

Dzięki Aina.

Odpowiedz

6

Czy daty w tablicy są posortowane?

  • Jeśli tak, można przyspieszyć porównań autorem zerwania z wewnętrznej porównaniu pętli po jego daty są większe niż w terminie wskazanym przez zewnętrzną pętlę . W ten sposób zostanie dokonane porównanie jednym przejściu zamiast pętli dimVals przedmiotów len(pwfs2Vals) razy
  • Jeśli nie, być może trzeba przekształcić aktualną pwfs2Dates tablicę, na przykład, tablicą par [(date, array_index),...] a następnie można posortować według data wszystkie macierze aby dokonać porównania w jednym przejściu wskazaną powyżej w samym czasie, aby móc dostać oryginalne indeksy potrzebne, aby ustawić data[i]

na przykład jeśli tablice zostały już posortowane (używam list tutaj, nie wiesz, że potrzebujesz do tego tablic): (Zmieniano: teraz przy użyciu i iterator nie do pwfs2Dates pętli od początku na każdym kroku):

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() 

W przeciwnym razie, jeśli nie zostały one uporządkowane i stworzył sortowane, indeksowane list tak:

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

kod może być:
(Pod: teraz używania i nie pętli iteracyjnej pwfs2Dates od początku w każdym etapie):

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() 

świetnie!

..

  1. Zauważ, że dimVals:

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

    nie jest używany w kodzie i mogą być wyeliminowane.

  2. pamiętać, że kod zostanie znacznie uproszczone poprzez zapętlenie przez samego tablicy zamiast korzystania XRange

Edit: Odpowiedź z unutbu adres niektóre słabe części w kodzie powyżej. mi wskazać je tutaj dla kompletności:

  1. Zastosowanie next: next(iterator) jest preferowana do iterator.next(). iterator.next() to wyjątek od konwencjonalnej reguły nazewnictwa, która została zmieniona w py3k zmieniając nazwę tej metody na iterator.__next__().
  2. Sprawdź koniec iteratora za pomocą try/except. Po ukończeniu wszystkich elementów w iteratorze następne wywołanie do next() powoduje wyjątek zatrzymania. Użyj try/except, aby uprzejmie przerwać pętli, gdy tak się stanie. W konkretnym przypadku z pytaniem OP o numerze nie stanowi to problemu, ponieważ te dwie tablice mają ten sam rozmiar , więc pętla for kończy się w tym samym czasie, co iterator. Tak więc nie ma wyjątku wyjątek . Mogą jednak występować przypadki, gdy dict1 i dict2 nie mają tego samego rozmiaru. I w tym przypadku istnieje możliwość powstania wyjątku o numerze . Pytanie brzmi: co jest lepsze, użyj polecenia try/except lub przygotuj tablice przed zapętleniem, wyrównując je do krótszego.
+0

dzięki tak dużo, że całkowicie udało się! – Aina

0

myślę, że można to zrobić z jednego mniej pętli:

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()) 

Jest na pewno lepszy sposób, aby zebrać i scalić mecze, ale masz pomysł ... Można również użyć numpy.argwhere((a < plus) * (a > minus)) aby zwrócić indeks zamiast daty i użyć indeksu, aby pobrać cały wiersz i umieścić go w nowej tablicy.

4

Opierając się na 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])) 

daje

[0, 3, 0, 5] 
+0

+1 za to, że faktycznie działa – joaquin