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ę.
2 odpowiedzi
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
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
Podobne pytania
Nowe pytania
python
Python to wielozadaniowy, wielozadaniowy język programowania dynamicznie typowany. Został zaprojektowany tak, aby był szybki do nauczenia się, zrozumienia i użycia oraz wymuszania czystej i jednolitej składni. Należy pamiętać, że Python 2 oficjalnie nie jest obsługiwany od 01-01-2020. Mimo to, w przypadku pytań Pythona specyficznych dla wersji, dodaj znacznik [python-2.7] lub [python-3.x]. Korzystając z wariantu Pythona (np. Jython, PyPy) lub biblioteki (np. Pandas i NumPy), należy umieścić go w tagach.