Используя профилировщик, вы можете видеть, что большая часть времени проводится в np.where
.К сожалению, сейчас мы мало что можем с этим поделать.
Следующим по величине временным поглотителем, похоже, являются преобразования Панд, которые являются медленными.Таким образом, мы можем сэкономить время, делая код более упорядоченным (и более читабельным):
import numpy as np
import pandas as pd
def make_data():
data_raw = np.random.randint(low=0, high=10, size=(700000, 3))
Data = pd.DataFrame(data_raw, columns=['a', 'b', 'c'])
Data['f'] = (Data.index % 60) + 1
Data['column_-1'] = 100
return Data
def run1(Data):
""" Original """
for i in range(COLUMNS):
Data['column_' + str(i)] = np.where( # Condition 1
Data['f'] == 1,
1000 + i,
np.where( # Condition 2
i < Data['f'],
0,
np.where( # Condition 3
Data['a'] > Data['b'],
Data['column_' + str(-1)] * Data['c'],
Data['column_' + str(-1)]
)
)
)
def run2(Data):
""" Cleaned up """
f = Data['f'].values
a = Data['a'].values
b = Data['b'].values
c = Data['c'].values
for i in range(COLUMNS):
col = f'column_{i}'
colm1 = f'column_{i-1}'
colm1 = Data[colm1].values
Data[col] = np.where(f == 1, 1000 + i,
np.where(f > i, 0,
np.where(a > b, colm1*c, colm1)))
%timeit run1(make_data())
# 1.31 s ± 101 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit run2(make_data())
# 1.22 s ± 26.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop
Но мы все еще используем np.where
3 раза.Numpy-функции очень стремятся, и np.where
будет заканчивать циклическим циклом данных при каждом запуске.
Так что давайте сделаем лучше!Мы можем «сплющить» все это и сделать все это за один цикл:
def run3(Data):
def _run3(f, a, b, c, x, i):
results = np.zeros_like(x)
for k, (fval, aval, bval, cval, xval) in enumerate(zip(f, a, b, c, x)):
if fval == 1:
results[k] = i + 1000
elif fval > i:
results[k] = 0
elif aval > bval:
results[k] = xval*cval
else:
results[k] = xval
return results
fabc = Data[['f', 'a', 'b', 'c']].values.astype(np.dtype('int64'))
f, a, b, c = [fabc[:,j] for j in range(4)]
col = 'column_-1'
for i in range(COLUMNS):
colm1 = col
col = f'column_{i}'
x = Data[colm1].values
Data[col] = _run3(f, a, b, c, x, i)
%timeit run3(make_data())
# 34.3 s ± 1.4 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
О ... неважно.Это то, что люди имеют в виду, когда говорят «Python медленный».Циклирование в C в 3 раза в 25 раз быстрее, чем в Python один раз!
Итак, давайте сделаем цикл в C:
import numba
@numba.jit(nopython=True)
def _run4(f, a, b, c, x, i):
results = np.zeros_like(x)
for k in range(len(x)):
fval = f[i]
aval = a[i]
bval = b[i]
cval = c[i]
xval = x[i]
if fval == 1:
results[k] = i + 1000
elif fval > i:
results[k] = 0
elif aval > bval:
results[k] = xval*cval
else:
results[k] = xval
return results
def run4(Data):
fabc = Data[['f', 'a', 'b', 'c']].values.astype(np.dtype('int64'))
f, a, b, c = [fabc[:,j] for j in range(4)]
col = 'column_-1'
for i in range(COLUMNS):
colm1 = col
col = f'column_{i}'
x = Data[colm1].values
Data[col] = _run4(f, a, b, c, x, i)
%timeit run4(make_data())
# 496 ms ± 70.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
И это, вероятно, достаточно быстро на сегодняшний день.+ лучший алгоритм - огромные накладные расходы = быстрая.