2013-07-26 13 views
7

Mam tutaj określony problem z wydajnością. Pracuję z prognoz meteorologicznych timeseries, które skompilować w numpy 2d tablicy tak, żeElegancka, zmienna zmiana szyku i wypełnianie NaN?

  • dim0 = czas, w którym rozpoczyna serię prognozy
  • dim1 = horyzont prognozy, np. 0 do 120 godzin

Teraz chciałbym, aby dim0 miał godzinne interwały, ale niektóre źródła podają prognozy tylko co N godzin. Jako przykład powiedz N = 3, a czas w dim1 to M = 1 godzina. Potem otrzymuję coś w rodzaju:

12:00 11.2 12.2 14.0 15.0 11.3 12.0 
13:00 nan nan nan nan nan nan 
14:00 nan nan nan nan nan nan 
15:00 14.7 11.5 12.2 13.0 14.3 15.1 

Ale oczywiście jest informacja o 13:00 i 14:00, ponieważ można ją wypełnić od godziny 12 z prognozą. Więc chciałbym skończyć z czymś takim:

12:00 11.2 12.2 14.0 15.0 11.3 12.0 
13:00 12.2 14.0 15.0 11.3 12.0 nan 
14:00 14.0 15.0 11.3 12.0 nan nan 
15:00 14.7 11.5 12.2 13.0 14.3 15.1 

Co to najszybszy sposób, aby dostać się tam, zakładając dim0 jest w porządku 1E4 i dim1 rzędu 1E2? Teraz robię to wiersz po wierszu, ale to jest bardzo powolny:

nRows, nCols = dat.shape 
if N >= M: 
    assert(N % M == 0) # must have whole numbers 
    for i in range(1, nRows): 
     k = np.array(np.where(np.isnan(self.dat[i, :]))) 
     k = k[k < nCols - N] # do not overstep 
     self.dat[i, k] = self.dat[i-1, k+N] 

Jestem pewien, że musi być bardziej elegancki sposób to zrobić? Wszelkie wskazówki będą mile widziane.

+2

Mógłbyś tłumacząc to inaczej, zgubiłem w zdaniu „Ale oczywiście. .. ". W jaki sposób różne źródła są reprezentowane w tablicy? Czy dim0 oznacza rzędy i dim1 = dimension1 = columns? – elyase

+1

@lyase: Liczby są przesuwane w dół iw lewo, ponieważ na przykład, jeśli prognoza wynosi 12,2 godziny od teraz (o 12:00), to za godzinę prognoza będzie wynosiła 12,2 godziny zero od tego czasu (o 13:00). – unutbu

Odpowiedz

2

Krojenie danych za pomocą a=yourdata[:,1:].

def shift_time(dat): 

    #Find number of required iterations 
    check=np.where(np.isnan(dat[:,0])==False)[0] 
    maxiters=np.max(np.diff(check))-1 

    #No sense in iterations where it just updates nans 
    cols=dat.shape[1] 
    if cols<maxiters: maxiters=cols-1 

    for iters in range(maxiters): 
     #Find nans 
     col_loc,row_loc=np.where(np.isnan(dat[:,:-1])) 

     dat[(col_loc,row_loc)]=dat[(col_loc-1,row_loc+1)] 


a=np.array([[11.2,12.2,14.0,15.0,11.3,12.0], 
[np.nan,np.nan,np.nan,np.nan,np.nan,np.nan], 
[np.nan,np.nan,np.nan,np.nan,np.nan,np.nan], 
[14.7,11.5,12.2,13.0,14.3,15.]]) 

shift_time(a) 
print a 

[[ 11.2 12.2 14. 15. 11.3 12. ] 
[ 12.2 14. 15. 11.3 12. nan] 
[ 14. 15. 11.3 12. nan nan] 
[ 14.7 11.5 12.2 13. 14.3 15. ]] 

Aby korzystać z danych, jak jest lub może być nieznacznie zmienione, aby wziąć go bezpośrednio, ale to wydaje się być jasny sposób pokazać to:

shift_time(yourdata[:,1:]) #Updates in place, no need to return anything. 

testem Tiago za:

tmp = np.random.uniform(-10, 20, (1e4, 1e2)) 
nan_idx = np.random.randint(30, 1e4 - 1,1e4) 
tmp[nan_idx] = np.nan 

t=time.time() 
shift_time(tmp,maxiter=1E5) 
print time.time()-t 

0.364198923111 (seconds) 

Jeśli jesteś naprawdę sprytny powinieneś być w stanie uciec z jednym np.where.

0

Każda iteracja tego klocka, rolki, rolki combo zasadniczo robi to, czego szukasz:

import numpy as np 
from numpy import nan as nan 

# Startup array 
A = np.array([[11.2, 12.2, 14.0, 15.0, 11.3, 12.0], 
       [nan, nan, nan, nan, nan, nan], 
       [nan, nan, nan, nan, nan, nan], 
       [14.7, 11.5, 12.2, 13.0, 14.3, 15.1]]) 

def pad_nan(v, pad_width, iaxis, kwargs): 
    v[:pad_width[0]] = nan 
    v[-pad_width[1]:] = nan 
    return v 

def roll_data(A): 
    idx = np.isnan(A) 
    A[idx] = np.roll(np.roll(np.pad(A,1, pad_nan),1,0), -1, 1)[1:-1,1:-1][idx] 
    return A 

print A 
print roll_data(A) 
print roll_data(A) 

Wyjście daje:

[[ 11.2 12.2 14. 15. 11.3 12. ] 
[ nan nan nan nan nan nan] 
[ nan nan nan nan nan nan] 
[ 14.7 11.5 12.2 13. 14.3 15.1]] 

[[ 11.2 12.2 14. 15. 11.3 12. ] 
[ 12.2 14. 15. 11.3 12. nan] 
[ nan nan nan nan nan nan] 
[ 14.7 11.5 12.2 13. 14.3 15.1]] 

[[ 11.2 12.2 14. 15. 11.3 12. ] 
[ 12.2 14. 15. 11.3 12. nan] 
[ 14. 15. 11.3 12. nan nan] 
[ 14.7 11.5 12.2 13. 14.3 15.1]] 

Wszystko jest czyste numpy tak powinno być bardzo szybkie każda iteracja. Nie jestem jednak pewien kosztu tworzenia wyściełanej tablicy i uruchamiania wielu iteracji, jeśli spróbujesz, daj mi znać wyniki!

+0

Myślę, że wiele iteracji tego zabije wydajność. Przetestowałem to używając podobnej konfiguracji do mojej odpowiedzi (z nowymi wersjami NY), i to w moim systemie zajęło 33.85s dla kształtu tablicy (10000, 100), około 20 razy wolniej niż moje rozwiązanie (które jest podobne do Ophiona). – tiago

1

Wydaje się to zrobić trick:

import numpy as np 

def shift_time(dat): 
    NX, NY = dat.shape 
    for i in range(NY): 
     x, y = np.where(np.isnan(dat)) 
     xr = x - 1 
     yr = y + 1 
     idx = (xr >= 0) & (yr < NY) 
     dat[x[idx], y[idx]] = dat[xr[idx], yr[idx]] 
    return 

Teraz z jakiegoś danych testowych:

In [1]: test_data = array([[ 11.2, 12.2, 14. , 15. , 11.3, 12. ], 
          [ nan, nan, nan, nan, nan, nan], 
          [ nan, nan, nan, nan, nan, nan], 
          [ 14.7, 11.5, 12.2, 13. , 14.3, 15.1], 
          [ nan, nan, nan, nan, nan, nan], 
          [ 15.7, 16.5, 17.2, 18. , 14. , 12. ]]) 
In [2]: shift_time(test_data) 
In [3]: print test_data 
Out [3]: 
array([[ 11.2, 12.2, 14. , 15. , 11.3, 12. ], 
     [ 12.2, 14. , 15. , 11.3, 12. , nan], 
     [ 14. , 15. , 11.3, 12. , nan, nan], 
     [ 14.7, 11.5, 12.2, 13. , 14.3, 15.1], 
     [ 11.5, 12.2, 13. , 14.3, 15.1, nan], 
     [ 15.7, 16.5, 17.2, 18. , 14. , 12. ]]) 

i testowanie z (1E4, 1E2) tablica:

In [1]: tmp = np.random.uniform(-10, 20, (1e4, 1e2)) 
In [2]: nan_idx = np.random.randint(30, 1e4 - 1,1e4) 
In [3]: tmp[nan_idx] = nan 
In [4]: time test3(tmp) 
CPU times: user 1.53 s, sys: 0.06 s, total: 1.59 s 
Wall time: 1.59 s 
5

Oto , moc indeksowania boolowskiego !!!

def shift_nans(arr) : 
    while True: 
     nan_mask = np.isnan(arr) 
     write_mask = nan_mask[1:, :-1] 
     read_mask = nan_mask[:-1, 1:] 
     write_mask &= ~read_mask 
     if not np.any(write_mask): 
      return arr 
     arr[1:, :-1][write_mask] = arr[:-1, 1:][write_mask] 

Myślę, że nazywanie wyjaśnia, co się dzieje. Pierwsze prawo krojenia jest ból, ale wydaje się działać:

In [214]: shift_nans_bis(test_data) 
Out[214]: 
array([[ 11.2, 12.2, 14. , 15. , 11.3, 12. ], 
     [ 12.2, 14. , 15. , 11.3, 12. , nan], 
     [ 14. , 15. , 11.3, 12. , nan, nan], 
     [ 14.7, 11.5, 12.2, 13. , 14.3, 15.1], 
     [ 11.5, 12.2, 13. , 14.3, 15.1, nan], 
     [ 15.7, 16.5, 17.2, 18. , 14. , 12. ]]) 

I taktowania:

tmp1 = np.random.uniform(-10, 20, (1e4, 1e2)) 
nan_idx = np.random.randint(30, 1e4 - 1,1e4) 
tmp1[nan_idx] = np.nan 
tmp1 = tmp.copy() 

import timeit 

t1 = timeit.timeit(stmt='shift_nans(tmp)', 
        setup='from __main__ import tmp, shift_nans', 
        number=1) 
t2 = timeit.timeit(stmt='shift_time(tmp1)', # Ophion's code 
        setup='from __main__ import tmp1, shift_time', 
        number=1) 

In [242]: t1, t2 
Out[242]: (0.12696346416487359, 0.3427293070417363) 
+0

Możesz zaktualizować nan_mask używając 'nan_mask [1:,: - 1]^= write_mask', więc musisz tylko raz obliczyć' np.isnan (arr) '. Minusem jest to, że maska ​​write_mask musi zostać skopiowana, aby nie zmieniała wartości w nan_mask. Może być znacznie szybszy lub nieco wolniejszy w zależności od maksymalnej wymaganej iteracji. – Daniel

+0

Dziękuję wszystkim za inteligentne rozwiązania! Wiedziałem, że musi być na to wiele sposobów i wydaje się, że pobraliśmy już sporą część z nich ... Pójdę po ten, który powinien dobrze rozwiązać mój problem. – marfel