Оптимизация интерполяции значений в Pandas

Я боролся с проблемой оптимизации с Pandas.

Я разработал скрипт для применения вычислений к каждой строке относительно небольшого DataFrame (~ несколько тысяч строк, несколько десятков столбцов). Я сильно полагался на функцию apply(), которая в большинстве случаев явно была плохим выбором.

После раунда оптимизации у меня есть только метод, который требует времени, и я не нашел простого решения для:

По сути, мой фрейм данных содержит список статистики просмотра видео с количеством людей, которые смотрели видео для каждого квартиля (сколько просмотрели 0%, 25%, 50% и т. д.), например:

video_name видео_длина видео_0 видео_25 видео_50 видео_75 видео_100 видео_1 6 1000 500 300 250 5 видео_2 30 1000 500 300 250 5

Я пытаюсь интерполировать статистику, чтобы иметь возможность ответить «сколько людей посмотрело бы каждый квартиль видео, если бы оно длилось X секунд».

Прямо сейчас моя функция принимает фрейм данных и параметр «new_length» и вызывает apply() в каждой строке.

Функция, которая обрабатывает каждую строку, вычисляет временные метки для каждого квартиля (то есть 0, 7,5, 15, 22,5 и 30 для 30-секундного видео) и временные метки для каждого квартиля с учетом новой длины (таким образом, чтобы уменьшить 30-секундное видео до 6 с, новые метки времени будут 0, 1,5, 3, 4,5 и 6). Я создаю фрейм данных, содержащий временные метки в качестве индекса и статистику в виде значений в первом столбце:

индекс (метки времени) view_stats 0 1000 7,5 500 15 300 22,5 250 30 5 1,5 NaN 3 NaN 4,5 NaN

Затем я вызываю DataFrame.interpolate(method="index") для заполнения значений NaN.

Это работает и дает мне ожидаемый результат, но для фрейма данных из 3 тыс. строк требуется колоссальные 11 с, и я считаю, что это связано с использованием метода apply () в сочетании с созданием нового фрейма данных для интерполяции данных для каждой строки.

Есть ли очевидный способ добиться того же результата «на месте», например, избегая метода применения/нового фрейма данных непосредственно в исходном фрейме данных?

Обновлено: ожидаемый результат при вызове функции с 6 в качестве нового параметра длины:

video_name видео_длина видео_0 видео_25 видео_50 видео_75 видео_100 новое_видео_0 новое_видео_25 новое_видео_50 новое_видео_75 новое_видео_100 видео_1 6 1000 500 300 250 5 1000 500 300 250 5 видео_2 6 1000 500 300 250 5 1000 900 800 700 600

Первая строка останется нетронутой, потому что видео уже длится 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

Не могли бы вы добавить ожидаемый результат для вашего примера?

Dani Mesejo 14.12.2020 16:36

@DaniMesejo конечно! Я только что сделал. Я не являюсь носителем английского языка, поэтому скажите мне, если вы что-то не понимаете.

ErGo_404 14.12.2020 16:55
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
2
99
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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]

Кажется, это именно то, что я ищу. Я попробую это завтра и отмечу ваш ответ как решение после моей попытки. Большое спасибо

ErGo_404 14.12.2020 18:13

С моей точки зрения, вы гений pandas/np. Большая благодарность.

ErGo_404 15.12.2020 16:43

Спасибо @ErGo_404, я обычный парень, который любит Python :)

Dani Mesejo 15.12.2020 16:44

Другие вопросы по теме