Как я могу оптимизировать следующую операцию:
df[(df.start <= x) & (df.end >= y)]
Я попытался использовать MultiIndex, но не увидел значительного ускорения.
df = df.set_index(['start', 'end'])
df[(df.index.get_level_values('start') <= end) & (discon_df.index.get_level_values('end') >= start)]
Пример данных:
'<table border = "1" class = "dataframe">\n <thead>\n <tr style = "text-align: right;">\n <th></th>\n <th>start</th>\n <th>end</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <th>0</th>\n <td>2018-11-13 10:28:30.304287</td>\n <td>2018-11-13 10:46:28.663868</td>\n </tr>\n <tr>\n <th>1</th>\n <td>2018-11-13 12:27:32.226550</td>\n <td>2018-11-13 13:09:02.723869</td>\n </tr>\n <tr>\n <th>2</th>\n <td>2018-11-13 13:29:29.981659</td>\n <td>2018-11-13 13:54:01.138963</td>\n </tr>\n <tr>\n <th>3</th>\n <td>2018-11-13 14:30:49.380554</td>\n <td>2018-11-13 14:48:50.627830</td>\n </tr>\n <tr>\n <th>4</th>\n <td>2018-11-13 14:59:26.799017</td>\n <td>2018-11-13 15:24:00.453983</td>\n </tr>\n <tr>\n <th>5</th>\n <td>2018-11-13 16:30:16.824188</td>\n <td>2018-11-13 16:48:35.346318</td>\n </tr>\n <tr>\n <th>6</th>\n <td>2018-11-13 17:15:25.486287</td>\n <td>2018-11-13 17:59:30.774629</td>\n </tr>\n <tr>\n <th>7</th>\n <td>2018-11-13 18:27:41.915379</td>\n <td>2018-11-13 18:47:26.528320</td>\n </tr>\n <tr>\n <th>8</th>\n <td>2018-11-13 19:28:12.835576</td>\n <td>2018-11-13 19:52:15.448146</td>\n </tr>\n <tr>\n <th>9</th>\n <td>2018-11-13 20:41:41.210849</td>\n <td>2018-11-13 21:07:52.249831</td>\n </tr>\n <tr>\n <th>10</th>\n <td>2018-11-13 21:11:23.529623</td>\n <td>2018-11-13 21:42:10.106951</td>\n </tr>\n </tbody>\n</table>'@Chirag - он медленнее :(
Я бы легко решил проблему, если бы был только один столбец, сделав этот столбец DateTimeIndex.
Можете ли вы добавить образцы данных?
Если я столкнусь с этой ситуацией, я разделю это условие. Сначала примените (df.start <= x), затем сохраните его в том же df, а затем примените следующее условие: (df.end >= y) (теперь размер df уменьшен). Может я ошибаюсь, подождем ответов эксперта
@jezrael Я добавил несколько примеров данных.






Узким местом является построение булевой серии / массива, используемого для индексации.
Переход на NumPy дает разумное (~ 2x) улучшение производительности. См. Также: pd.Timestamp против np.datetime64: взаимозаменяемы ли они для отдельных целей?
# boundaries for testing
mindt = pd.to_datetime('2016-01-01')
maxdt = pd.to_datetime('2017-01-01')
x = ((df['start'] <= mindt) & (df['end'] >= maxdt)).values
y = (df['start'].values <= mindt.to_datetime64()) & (df['end'].values >= maxdt.to_datetime64())
# check results are the same
assert np.array_equal(x, y)
%timeit (df['start'].values <= mindt.to_datetime64()) & (df['end'].values >= maxdt.to_datetime64())
# 55.6 ms per loop
%timeit (df['start'] <= mindt) & (df['end'] >= maxdt)
# 108 ms per loop
Настраивать
np.random.seed(0)
def random_dates(start, end, n):
start_u = start.value//10**9
end_u = end.value//10**9
cols = ['start', 'end']
df = pd.DataFrame({col: pd.to_datetime(np.random.randint(start_u, end_u, n), unit='s') for col in cols})
df = pd.DataFrame(np.sort(df.values, axis=1), columns=cols)
df[cols] = df[cols].apply(pd.to_datetime, errors='raise')
return df
# construct a dataframe of random dates
df = random_dates(pd.to_datetime('2015-01-01'), pd.to_datetime('2018-01-01'), 10**7)
Большое улучшение. Но я думаю, что основная проблема вызвана тем, что и start, и index неиндексированы и требуют времени O(n). Если бы мы могли проиндексировать их обоих, это было бы намного быстрее. Что вы думаете?
@Lokesh, O (п) неизбежно, вам нужно перебирать каждое значение в обеих сериях. Просто сделать это в NumPy более эффективно. Вы также можете стать быстрее с numba, если этого недостаточно.
Вы уверены, что O(n) неизбежен, потому что я заметил значительное улучшение после того, как переместил столбец на DateTimeIndex, а затем запросил его с помощью df[date_time_object:]? Раньше я делал df[df.time > date_time_object], и это было мучительно медленно.
@Lokesh, да я уверен. Подумайте об этом теоретически. Чтобы сравнить каждое значение в массиве размера п со скаляром, вы должен повторяете каждое значение в п один раз. Не усложняйте работу. Они не такие. [O (1) может быть медленнее, чем O (п) в определенных случаях, что является еще одной проблемой с этой логикой.]
Но если мы будем использовать двоичный поиск, сравнение сократится до O(log n), и я подозреваю, что DateTimeIndex делает именно это, но я не уверен.
Итак, то, что вы предлагаете, работает только если ваши входы отсортированы, а это не ваше предположение. Если ваши входные данные не отсортированы, вам нужно перебрать все элементы. Это неизбежно. Если они отсортированы, я предлагаю вам задать новый вопрос с указанием вашего особого случая и того, что вы пробовали с точки зрения оптимизации.
Нет, мои данные не отсортированы. Я имел в виду, что мы можем сами это отсортировать.
@Lokesh, Сортировка будет иметь сложность больше O (п), обычно O (п log п). Но это другой вопрос.
Да, но мне нужно сделать этот запрос примерно 100 000 раз, так что в конечном итоге это неплохо.
@Lokesh, конечно: к сожалению, вы не упомянули ни один из этих аспектов в своем исходном вопросе. Итак, задайте новый вопрос с указанием ваших конкретных требований.
Я провел эксперимент, чтобы узнать, использует ли DateTimeIndex двоичный поиск или нет. Оказывается, нет и берет тот же O(n). Если это так, то нет смысла задавать новый вопрос. Было бы слишком сложно построить структуру данных, которая бы выполняла двоичный поиск по дате. Я принимаю этот ответ. Большое спасибо! Ты жжешь.
Попробуйте
df.query()