2011-12-19 16 views
5

Necesito unir dos matrices Numpy muy grandes (una es 20000 filas, otra alrededor de 100000 filas) y estoy tratando de construir una secuencia de comandos para hacerlo de manera eficiente. El simple bucle sobre las matrices es increíblemente lento, ¿alguien puede sugerir una mejor manera? Esto es lo que estoy tratando de hacer: array datesSecondDict y array pwfs2Dates contienen valores de fecha y hora, necesito tomar cada valor de datetime del array pwfs2Dates (matriz más pequeña) y ver si hay un valor de fecha y hora como ese (más menos 5 minutos) en matriz datesSecondDict (puede haber más de 1). Si hay uno (o más) llenó una nueva matriz (del mismo tamaño que la matriz pwfs2Dates) con el valor (uno de los valores) de la matriz valsSecondDict (que es solo la matriz con los valores numéricos correspondientes a datesSecondDict). Aquí es una solución por @unutbu y @joaquin que trabajó para mí (gracias chicos!):Coincidencia condicional de Numpy array

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 

Gracias, Aina.

Respuesta

6

¿Están ordenadas las fechas de la matriz?

  • Si es así, se puede acelerar sus comparaciones mediante la ruptura del interior comparación bucle una vez que sus fechas son más grandes que la fecha propuesta por el bucle externo . De esta manera se hizo una comparación de un solo paso en lugar de bucle dimVals artículos len(pwfs2Vals) veces
  • Si no, tal vez usted debe transformar la corriente pwfs2Dates matriz para, por ejemplo, un array de pares [(date, array_index),...] y luego se puede ordenar por fecha todas las matrices para hacer la comparación de una sola pasada se indicó anteriormente y al mismo tiempo ser capaz de obtener los índices originales necesarios para establecer data[i]

por ejemplo, si las matrices ya estaban ordenados (utilizo listas aquí, no estoy seguro de que necesite arreglos para eso): (Editado: ahora el uso y iterador no a pwfs2Dates bucle desde el principio en cada paso):

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

De lo contrario, si no fueron ordenados y se ha creado el ordenados, listas indexadas de esta manera:

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

el código sería:
(Editado: ahora que usa y iterador no bucle pwfs2Dates desde el principio en cada paso):

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

genial!

..

  1. Tenga en cuenta que dimVals:

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

    no se utiliza en el código y pueden ser eliminados.

  2. Tenga en cuenta que el código se simplifica en gran medida por el bucle a través de la matriz sí en lugar de utilizar xrange

Editar: La respuesta de unutbu dirección de algunas partes débiles en el código de seguridad. les indico aquí por completitud:

  1. El uso de next: next(iterator) se prefiere a iterator.next(). iterator.next() es una excepción a una regla de nomenclatura convencional que se ha solucionado en py3k renombrando este método como iterator.__next__().
  2. Compruebe el final del iterador con un try/except. Después de que todos los elementos en el iterador hayan finalizado, la siguiente llamada al next() produce una excepción StopIteration. Use try/except amablemente salga del lazo cuando eso suceda. Para el caso específico de la pregunta OP esto no es un problema, porque las dos carpetas son del mismo tamaño , por lo que el bucle for finaliza al mismo tiempo que el iterador. Por lo tanto, no se ha producido ninguna excepción . Sin embargo, podría haber casos donde dict1 y dict2 no son del mismo tamaño. Y en este caso existe la posibilidad de que se haya incrementado una excepción . La pregunta es: ¿qué es mejor, usar try/except o preparar las matrices antes de hacer bucles igualando a la más corta?
+0

gracias tanto, es totalmente funcionaba! – Aina

0

Creo que se puede hacer con un menor número de bucles:

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

seguramente hay una mejor manera de reunir y fusionar los partidos, pero se entiende la idea ... También es posible usar numpy.argwhere((a < plus) * (a > minus)) para devolver el índice en lugar de la fecha y usar el índice para tomar toda la fila y colocarla en su nueva matriz.

4

Basándose en 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])) 

produce

[0, 3, 0, 5] 
+0

+1 para hacerlo funcionar – joaquin