Я боролся с проблемой оптимизации с Pandas.
Я разработал скрипт для применения вычислений к каждой строке относительно небольшого DataFrame (~ несколько тысяч строк, несколько десятков столбцов). Я сильно полагался на функцию apply(), которая в большинстве случаев явно была плохим выбором.
После раунда оптимизации у меня есть только метод, который требует времени, и я не нашел простого решения для:
По сути, мой фрейм данных содержит список статистики просмотра видео с количеством людей, которые смотрели видео для каждого квартиля (сколько просмотрели 0%, 25%, 50% и т. д.), например:
Я пытаюсь интерполировать статистику, чтобы иметь возможность ответить «сколько людей посмотрело бы каждый квартиль видео, если бы оно длилось X секунд».
Прямо сейчас моя функция принимает фрейм данных и параметр «new_length» и вызывает apply() в каждой строке.
Функция, которая обрабатывает каждую строку, вычисляет временные метки для каждого квартиля (то есть 0, 7,5, 15, 22,5 и 30 для 30-секундного видео) и временные метки для каждого квартиля с учетом новой длины (таким образом, чтобы уменьшить 30-секундное видео до 6 с, новые метки времени будут 0, 1,5, 3, 4,5 и 6). Я создаю фрейм данных, содержащий временные метки в качестве индекса и статистику в виде значений в первом столбце:
Затем я вызываю DataFrame.interpolate(method="index") для заполнения значений NaN.
Это работает и дает мне ожидаемый результат, но для фрейма данных из 3 тыс. строк требуется колоссальные 11 с, и я считаю, что это связано с использованием метода apply () в сочетании с созданием нового фрейма данных для интерполяции данных для каждой строки.
Есть ли очевидный способ добиться того же результата «на месте», например, избегая метода применения/нового фрейма данных непосредственно в исходном фрейме данных?
Обновлено: ожидаемый результат при вызове функции с 6 в качестве нового параметра длины:
Первая строка останется нетронутой, потому что видео уже длится 6 секунд. Во второй строке видео будет сокращено с 30 с до 6 с, поэтому новые квартили будут равны 0, 1,5, 3, 4,5, 6 с, а статистика будет интерполирована между 1000 и 500, которые были значениями при старом 0%. и 25% отметки времени
EDIT2: мне все равно, нужно ли мне добавлять временные столбцы, время - проблема, а память - нет.
В качестве справки, это мой код:
def get_value(marks, asset, mark_index) -> int:
value = marks["count"][asset["new_length_marks"][mark_index]]
if isinstance(value, pandas.Series):
res = value.iloc(0)
else:
res = value
return math.ceil(res)
def length_update_row(row, assets, **kwargs):
asset_name = row["asset_name"]
asset = assets[asset_name]
# assets is a dict containing the list of files and the old and "new" video marks
# pre-calculated
marks = pandas.DataFrame(data=[int(row["video_start"]), int(row["video_25"]), int(row["video_50"]), int(row["video_75"]), int(row["video_completed"])],
columns=["count"],
index=asset["old_length_marks"])
marks = marks.combine_first(pandas.DataFrame(data=NaN, columns=["count"], index=asset["new_length_marks"][1:]))
marks = marks.interpolate(method = "index")
row["video_25"] = get_value(marks, asset, 1)
row["video_50"] = get_value(marks, asset, 2)
row["video_75"] = get_value(marks, asset, 3)
row["video_completed"] = get_value(marks, asset, 4)
return row
def length_update_stats(report: pandas.DataFrame,
assets: dict) -> pandas.DataFrame:
new_report = new_report.apply(lambda row: length_update_row(row, assets), axis=1)
return new_report
@DaniMesejo конечно! Я только что сделал. Я не являюсь носителем английского языка, поэтому скажите мне, если вы что-то не понимаете.
IIUC, вы можете использовать np.interp:
# get the old x values
xs = df['video_length'].values[:, None] * [0, 0.25, 0.50, 0.75, 1]
# the corresponding y values
ys = df.iloc[:, 2:].values
# note that 6 is the new value
nxs = np.repeat(np.array(6), 2)[:, None] * [0, 0.25, 0.50, 0.75, 1]
res = pd.DataFrame(data=np.array([np.interp(nxi, xi, yi) for nxi, xi, yi in zip(nxs, xs, ys)]), columns = "new_" + df.columns[2:] )
print(res)
Выход
new_video_0 new_video_25 new_video_50 new_video_75 new_video_100
0 1000.0 500.0 300.0 250.0 5.0
1 1000.0 900.0 800.0 700.0 600.0
А затем объедините вторую ось:
output = pd.concat((df, res), axis=1)
print(output)
Выход (конкат)
video_name video_length video_0 ... new_video_50 new_video_75 new_video_100
0 video_1 6 1000 ... 300.0 250.0 5.0
1 video_2 30 1000 ... 800.0 700.0 600.0
[2 rows x 12 columns]
Кажется, это именно то, что я ищу. Я попробую это завтра и отмечу ваш ответ как решение после моей попытки. Большое спасибо
С моей точки зрения, вы гений pandas/np. Большая благодарность.
Спасибо @ErGo_404, я обычный парень, который любит Python :)
Не могли бы вы добавить ожидаемый результат для вашего примера?