Найти и заменить несколько значений в Python

Я хочу найти и заменить несколько значений в массиве / списке 1D новыми.

в примере для списка

a=[2, 3, 2, 5, 4, 4, 1, 2]

Я хотел бы заменить

val_old=[1, 2, 3, 4, 5] 

С

val_new=[2, 3, 4, 5, 1]

поэтому новый массив:

a_new=[3, 4, 3, 1, 5, 5, 2, 3]

какой самый быстрый способ сделать это (для очень больших списков, т. е. с 50000 значений поиск и замена)?

комментарий of the anwsers

спасибо всем за быстрый ответ! Я проверил предлагаемые решения следующим образом:

N = 10**4
N_val = 0.5*N
a = np.random.randint(0, N_val, size=N)
val_old = np.arange(N_val, dtype=np.int)
val_new = np.arange(N_val, dtype=np.int)
np.random.shuffle(val_new)

a1 = list(a)
val_old1 = list(val_old)
val_new1 = list(val_new)

def Ashwini_Chaudhary(a, val_old, val_new):
    arr = np.empty(a.max()+1, dtype=val_new.dtype)
    arr[val_old] = val_new
    return arr[a]

def EdChum(a, val_old, val_new):
    df = pd.Series(a, dtype=val_new.dtype)
    d = dict(zip(val_old, val_new))
    return df.map(d).values   

def xxyzzy(a, val_old, val_new):
    return [val_new[val_old.index(x)] for x in a]

def Shashank_and_Hackaholic(a, val_old, val_new):
    d = dict(zip(val_old, val_new))
    return [d.get(e, e) for e in a]

def itzmeontv(a, val_old, val_new):
    return [val_new[val_old.index(i)] if i in val_old else i for i in a]

def swenzel(a, val_old, val_new):
    return val_new[np.searchsorted(val_old,a)]

def Divakar(a, val_old, val_new):
    C,R = np.where(a[:,np.newaxis] == val_old[np.newaxis,:])
    a[C] = val_new[R]
    return a

результаты:

%timeit -n100 Ashwini_Chaudhary(a, val_old, val_new)
100 loops, best of 3: 77.6 µs per loop

%timeit -n100 swenzel(a, val_old, val_new)
100 loops, best of 3: 703 µs per loop

%timeit -n100 Shashank_and_Hackaholic(a1, val_old1, val_new1)
100 loops, best of 3: 1.7 ms per loop

%timeit -n100 EdChum(a, val_old, val_new)
100 loops, best of 3: 17.6 ms per loop

%timeit -n10 Divakar(a, val_old, val_new)
10 loops, best of 3: 209 ms per loop

%timeit -n10 xxyzzy(a1, val_old1, val_new1)
10 loops, best of 3: 429 ms per loop

%timeit -n10 itzmeontv(a1, val_old1, val_new1)
10 loops, best of 3: 847 ms per loop

относительная разница в производительности увеличивается с biger N , т. е. N=10**7, то результат по Ashwini_Chaudhary принимает 207 ms и результат по swenzel 6.89 s.

10 ответов


>>> arr = np.empty(a.max() + 1, dtype=val_new.dtype)
>>> arr[val_old] = val_new
>>> arr[a]
array([3, 4, 3, 1, 5, 5, 2, 3])

в ванильном Python, без скорости numpy или pandas, это один из способов:

a = [2, 3, 2, 5, 4, 4, 1, 2]
val_old = [1, 2, 3, 4, 5]
val_new = [2, 3, 4, 5, 1]
expected_a_new = [3, 4, 3, 1, 5, 5, 2, 3]
d = dict(zip(val_old, val_new))
a_new = [d.get(e, e) for e in a]
print a_new # [3, 4, 3, 1, 5, 5, 2, 3]
print a_new == expected_a_new # True

на в среднем сложность времени для этого алгоритма O(M + N) здесь M это длина вашего "списка переводов" и N это длина списка a.


предполагая, что ваш val_old массив отсортирован (что имеет место здесь, но если позже это не так, то не забудьте отсортировать val_new вместе с ней!), вы можете использовать numpy.searchsorted, а затем val_new С результатами.
это не работает, если число не имеет сопоставления, вам придется предоставить сопоставления 1to1 в этом случае.

In [1]: import numpy as np

In [2]: a = np.array([2, 3, 2, 5, 4, 4, 1, 2])

In [3]: old_val = np.array([1, 2, 3, 4, 5])

In [4]: new_val = np.array([2, 3, 4, 5, 1])

In [5]: a_new = np.array([3, 4, 3, 1, 5, 5, 2, 3])

In [6]: i = np.searchsorted(old_val,a)

In [7]: a_replaced = new_val[i]

In [8]: all(a_replaced == a_new)
Out[8]: True

50к цифры? Нет проблем!

In [23]: def timed():
    t0 = time.time()
    i = np.searchsorted(old_val, a)
    a_replaced = new_val[i]
    t1 = time.time()
    print('%s Seconds'%(t1-t0))
   ....: 

In [24]: a = np.random.choice(old_val, 50000)

In [25]: timed()
0.00288081169128 Seconds

500k? Вы не заметите разницы!

In [26]: a = np.random.choice(old_val, 500000)

In [27]: timed()
0.019248008728 Seconds

попробуйте это для ожидаемого выхода, работает, даже если elements не в value_old.

>>>[val_new[val_old.index(i)] if i in val_old else i for i in a]
[3, 4, 3, 1, 5, 5, 2, 3]

на numpy_indexed пакет (отказ от ответственности: я его автор) обеспечивает элегантное и эффективное векторизованное решение этого типа проблемы:

import numpy_indexed as npi
remapped_a = npi.remap(a, val_old, val_new)

реализованный метод основан на searchsorted как у swenzel и должен иметь аналогичную хорошую производительность, но более общую. Например, элементы массива не обязательно должны быть целыми, а могут быть любого типа, даже НД-подмассивов себя.

Если ожидается, что все значения в 'a' будут присутствовать в "val_old", вы можете установить необязательный "отсутствующий" кварг в "raise" (по умолчанию "ignore"). Производительность будет немного лучше, и вы получаете KeyError, если это предположение не выполняется.


чтобы заменить значения в списке, используя два других списка в качестве пар ключ:значение Существует несколько подходов. Все они используют сжатие "список".

используя список.index ():

a=[2, 3, 2, 5, 4, 4, 1, 2]
val_old=[1, 2, 3, 4, 5] 
val_new=[2, 3, 4, 5, 1]
a_new=[val_new[val_old.index(x)] for x in a]

используя ваш особый случай:

a=[2, 3, 2, 5, 4, 4, 1, 2]
a_new=[x % 5 + 1 for x in a]

Я пробовал так:

>>> val_old=[1, 2, 3, 4, 5]
>>> val_new=[2, 3, 4, 5, 1]
>>> a=[2, 3, 2, 5, 4, 4, 1, 2]
>>> my_dict = dict(zip(val_old, val_new))
>>> [my_dict.get(x,x) for x in a]
[3, 4, 3, 1, 5, 5, 2, 3]

в панд я бы создал дикт из 2 списков, а затем вызовите map который выполнит поиск и заменит значения:

In [6]:

df = pd.Series([2, 3, 2, 5, 4, 4, 1, 2])
df
Out[6]:
0    2
1    3
2    2
3    5
4    4
5    4
6    1
7    2
dtype: int64
In [7]:

val_old=[1, 2, 3, 4, 5] 
val_new=[2, 3, 4, 5, 1]
d = dict(zip(val_old,val_new ))
d
Out[7]:
{1: 2, 2: 3, 3: 4, 4: 5, 5: 1}
In [9]:

df.map(d)

Out[9]:
0    3
1    4
2    3
3    1
4    5
5    5
6    2
7    3
dtype: int64

для серии 80000 элементов это занимает 3,4 МС:

In [14]:

%timeit df.map(d)

100 loops, best of 3: 3.4 ms per loop

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


на numpy arrays, это может быть один подход -

%// Find row and column IDs for matches between "a" and "val_old"
C,R = np.where(a[:,np.newaxis] == val_old[np.newaxis,:])

%// Index into "a" with the column indices and 
%// set those to "val_new" elements indexed by "R"
a[C] = val_new[R]

пробный запуск и синхронизация

для входа:

a = np.random.randint(10000,size=(100000))
val_old = np.random.randint(10000,size=(1000))
val_new = np.random.randint(10000,size=(1000))

время выполнения в каждой строке кода было -

%timeit C,R = np.where(a[:,np.newaxis] == val_old[np.newaxis,:])
1 loops, best of 3: 292 ms per loop

%timeit a[C] = val_new[R]
10000 loops, best of 3: 43 µs per loop

list(map(lambda x:val_new[val_old.index(x)], a))