Можно ли объединить два метода «замены» в приведенном ниже примере в один? Целью является масштабируемость.
import pandas as pd
custom_groups = {"odd": [1, 3, 5], "even": [2, 4]}
s = pd.Series([1, 2, 3, 4, 5])
s.replace(custom_groups["odd"], "odd").replace(custom_groups["even"], "even")
Привет, Генри. Вы случайно не знаете, есть ли что-нибудь вроде s.replace({[1, 3, 5]: "нечетный", [2, 4]: "четный"}), чтобы пропустить шаг создания таблицы "один к одному"? -одни отношения?
Не существует способа сделать это, поскольку это не имеет смысла, поскольку списки не могут быть ключами словаря. Вы не можете напрямую сравнивать значения в своей серии со списком без проверки членства. Как бы вы ни смотрели на замену, вам в конечном итоге понадобится сопоставление 1 к 1. Даже если вы выполняете сравнения value in N
, чтобы оно выглядело как сопоставление N-к-1.
Когда вы передаете список в replace
, этой функции все равно придется в конечном итоге проверять отдельные значения. Технически, он создает временный список, содержащий значение замены той же длины, что и ваша серия, затем маскирует его на основе списка to_replace, а затем заполняет из временного списка (по крайней мере, в текущей версии Pandas 2.2.2). В этом случае для первого replace
это будет временный список ["нечетный", "нечетный", "нечетный", "нечетный", "нечетный"], а затем создайте маскирующий массив [True, False, True, False, True], затем заполните значения True в массиве маскирования из нечетного списка.
С другой стороны, преобразователь, похожий на dict, очень эффективен при его замене, хотя map
может потребовать обратного заполнения любых неучтенных значений. Вот почему я снова спросил, каков ваш фактический вариант использования, потому что в этом примере инвертирование ваших custom_groups и передача их в map
, вероятно, будет быстрее и требует меньше места, чем объединение замен в цепочку или создание отдельного объекта Series.
Спасибо, Генри, за подтверждение того, что при замене значений необходимо предоставление отношений «один к одному». Мне нужно немного времени, чтобы понять, как писать код, но теперь я понимаю весь процесс.
Вы можете написать цикл:
for key in custom_groups:
s.replace(custom_groups[key], key)
Спасибо, Джон. Это умно! Однако мне очень хотелось бы знать, есть ли способ сделать это без цикла. Я понимаю, что параметр «to_replace» может принимать словарь, позволяя заменять несколько отдельных значений соответствующими одиночными значениями одновременно, например. s.replace({1: «нечетный», 2: «четный»}). Я надеюсь заменить несколько списков отдельных значений соответствующими отдельными значениями за один раз, например. что-то вроде s.replace({[1, 3, 5]: «нечетный», [2, 4]: «четный»}).
Код
tmp = pd.Series(custom_groups).explode()
out = s.replace(pd.Series(tmp.index, index=tmp.values))
вне:
0 odd
1 even
2 odd
3 even
4 odd
dtype: object
Спасибо, Панда. У меня такое чувство, что это может быть лучшим решением. Я просто дам немного больше времени, чтобы посмотреть, есть ли еще какие-либо идеи по синтаксису, прежде чем отмечать ваше как ответ.
Самый питонический/эффективный подход: переверните словарь с помощью словарного понимания и замените один раз:
d = {v:k for k, l in custom_groups.items()
for v in l}
s.replace(d)
Выход:
0 odd
1 even
2 odd
3 even
4 odd
dtype: object
Промежуточный d
:
{1: 'odd', 3: 'odd', 5: 'odd', 2: 'even', 4: 'even'}
Это примерно в 2 раза быстрее, чем использование Series+deplode для небольших наборов данных, и имеет тенденцию к той же скорости для больших наборов данных.
## OP's example
# dictionary comprehension
745 µs ± 31.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
# Series explode
1.58 ms ± 213 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
## 1000 keys dictionary / 10 items per list / 10_000 items Series
# dictionary comprehension
163 ms ± 6.61 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
# Series explode
203 ms ± 64.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Если вам приходится часто делать это с разными словарями, вы даже можете использовать функцию инвертирования словаря.
def invert(d):
return {v:k for k, l in custom_groups.items()
for v in (l if isinstance(l, list) else [l])}
s.replace(invert(custom_groups))
Спасибо Mozway за подробное объяснение. dict_comprehension действительно кричит по-питонски. Если вы с Генри согласны, я считаю, что это «очевидный способ сделать это».
Не совсем уверен, какой у вас вариант использования, но почему бы вам просто не перестроить
custom_groups
в отображаемую форму?s.map({1: 'odd', 2: 'even', 3: 'odd', 4: 'even', 5: 'odd'})