2015-04-02 2 views
8

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

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

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 значениями для поиска и замены)?

Комментарийизв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 и результат by swenzel 6.89 s.

+1

Здесь довольно много и тот же вопрос: http://stackoverflow.com/questions/3403973/fast-replacement-of-values-in-a-numpy-array В случае нужно родовое Непро- целочисленное решение действительно интересно, что для большого количества замен * Решение Shashank * является самым быстрым. Для небольшого количества замещений наилучшим является решение numpy принятого ответа в связанном вопросе. Замечательно, насколько быстрыми являются словари python и списки. – knedlsepp

ответ

2
>>> 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]) 
+1

Была также моя первая попытка ... немного сложнее, если 'a' содержит отрицательные числа. – swenzel

+0

Для отрицательного числа вычислите дополнительное смещение: 'offset = max (-a.min(), 0); arr = np.empty (a.max() + 1 + offset, dtype = val_new.dtype); arr [val_old + offset] = val_new; a_new = arr [a + offset] ' –

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.

+0

Можно было бы подумать, что существует более быстрое решение numpy, такое как общий ... – knedlsepp

0

Для замены значений в списке с использованием двух других списков в качестве пары «ключ: значение» существует несколько подходов. Все они используют «сжатие списка».

Использование list.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] 
+1

Подход« индекс »будет работать, но он будет медленнее, чем« dict »для хешируемых элементов. – TheBlackCat

0

Я пытался так:

>>> 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] 
0

Попробуйте это для вашего ожидаемого результата, работы даже если 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] 
0

В панд я бы создать Dict из 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 

Для серии из 80 000 элементов это занимает 3.4ms:

In [14]: 

%timeit df.map(d) 

100 loops, best of 3: 3.4 ms per loop 

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

+0

Этот подход не является векторизованным, 'map' использует итерацию. Для длинных списков он немного быстрее выполняет «карту», ​​но время, необходимое для построения «Серии», означает, что итерационный подход заканчивается быстрее. – TheBlackCat

2

Предполагая, что ваш массив 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 
0

Для 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)) 

Runtimes в каждой строке кода был -

%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 
1

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

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

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

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

Смежные вопросы