Mam więc problem głównie z wydajnością, ponieważ faktycznie mogę wyświetlić oczekiwany wynik, ale zajmuje to dużo czasu. Szukam lepszych sposobów wykonania poniższych czynności, które pozwolą na szybsze wdrożenie.

Problem polega na tym, aby wypełnić null w pandach DataFrame wierszami, ale biorąc pod uwagę indeks początkowy i końcowy dla każdej kolumny (więc celem nie jest wypełnienie całej kolumny, ale tylko między podanymi indeksami)

Przykład:

Zaczynamy od zdefiniowania naszej ramki danych do wypełnienia oraz kolejnej z indeksami dla każdego wiersza

a = pd.DataFrame(index=range(3), columns=range(10))
values = {0: [3, 7], 1: [2, 4], 2: [1, 5]}
for k, v in values.items():
    a.iloc[k, v] = 1

b = pd.DataFrame({'start': [1, 2, 1], 'end': [7,6,8]})


>>>a
     0    1    2    3    4    5    6    7    8    9
0  NaN  NaN  NaN    1  NaN  NaN  NaN    1  NaN  NaN
1  NaN  NaN    1  NaN    1  NaN  NaN  NaN  NaN  NaN
2  NaN    1  NaN  NaN  NaN    1  NaN  NaN  NaN  NaN

>>>b
   start  end
0      1    7
1      2    6
2      1    8

Oczekiwany wynik (indeks końcowy nie obejmuje)

    0    1  2  3  4  5    6    7   8   9
0 NaN  0.0  0  1  0  0  0.0  1.0 NaN NaN
1 NaN  NaN  1  0  1  0  NaN  NaN NaN NaN
2 NaN  1.0  0  0  0  1  0.0  0.0 NaN NaN

W tej chwili stworzyłem funkcję, która pobiera zip z każdego wiersza ramki danych i wykonuje wypełnienie w wierszu i zwraca jej wartości z powrotem, a następnie ponownie tworzę ramkę danych

def _fill_slice(row_ind, value=0):
    row, ind = row_ind
    row[1].iloc[int(ind[0]):int(ind[1])].fillna(value, inplace=True)
    return row[1].values

>>>pd.DataFrame(map(_fill_slice, zip(a.iterrows(), b.values)))
    0    1  2  3  4  5    6    7   8   9
0 NaN  0.0  0  1  0  0  0.0  1.0 NaN NaN
1 NaN  NaN  1  0  1  0  NaN  NaN NaN NaN
2 NaN  1.0  0  0  0  1  0.0  0.0 NaN NaN

Dzięki temu mogę później wysyłać wiersze do wielu procesów za pośrednictwem programu imap

pd.DataFrame(pool.imap(_fill_slice, zip(a.iterrows(), b.values), chunksize=chunksize))

W tej chwili moja wydajność wynosi 15 minut dla ~ 4 mln rzędów, ale uważam, że powinien być lepszy sposób na zrobienie tego.

Jednym z rozwiązań, które spróbuję, jest grupowanie podobnych początków i końców oraz przekazywanie za każdym razem fragmentów zbioru danych. Inną możliwością jest uruchomienie dwóch pętli z powyższą ideą porcjowania, wypełnienie wszystkiego do końca indeksów, a następnie przywrócenie np.nan do indeksów początkowych. Może to prawdopodobnie zmniejszyć liczbę ogólnych iteracji.

Masz inny pomysł? Z góry dziękuję.

1
TPereira 24 marzec 2020, 19:16

2 odpowiedzi

Najlepsza odpowiedź

Oto jeden do wykorzystania broadcasting + masking -

s = b.start.values
e = b.end.values
R = np.arange(a.shape[1])
a.values[a.isnull().values & (s[:,None]<=R) & (e[:,None]>R)] = value
1
Divakar 24 marzec 2020, 16:39

Oto inny sposób z pandami, ale oczywiście nie tak skuteczny jak numpy:

s = b.agg(tuple,1).map(lambda x: range(*x)).explode().to_frame()
a = a.fillna(s.assign(val=0).set_index(0,append=True)['val'].unstack(0))

print(a)

     0    1  2  3  4  5    6    7    8    9
0  NaN    0  0  1  0  0    0    1  NaN  NaN
1  NaN  NaN  1  0  1  0  NaN  NaN  NaN  NaN
2  NaN    1  0  0  0  1    0    0  NaN  NaN
0
anky 24 marzec 2020, 17:32