在Python中查找和替换多个值

12
我想在一个一维数组/列表中查找和替换多个值为新的值。
例如,对于一个列表:
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个值要查找和替换)?

答案评论

感谢所有人快速回复!我用以下方法检查了提出的解决方案:

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

随着更大的N,性能差异会增加,例如如果N=10**7,则由Ashwini_Chaudhary得到的结果为207毫秒,而由swenzel得到的结果为6.89秒


1
这里有一个几乎相同的问题:https://dev59.com/tXA75IYBdhLWcg3wPmZ8 如果需要通用的非整数解决方案,对于大量替换,Shashank的解决方案是最快的。对于少量替换,链接问题中被接受的答案的numpy解决方案是最好的。Python字典和列表理解的速度非常快,这很棒。 - knedlsepp
10个回答

8
>>> 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
对于负数计算额外的偏移量: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] - David Boho

4
在普通的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的长度。


4
假设你的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

50k个数字?没问题!

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? You won't notice the difference!

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

In [27]: timed()
0.019248008728 Seconds

2

numpy_indexed包(免责声明:我是它的作者)为这种类型的问题提供了一种优雅而高效的向量化解决方案:

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

实现的方法基于searchsorted,类似于swenzel的方法,应该具有类似的良好性能,但更通用。例如,数组的项不需要是整数,而可以是任何类型,甚至是nd-subarrays本身。
如果预计'a'中的所有值都存在于'val_old'中,则可以将可选的'missing' kwarg设置为'raise'(默认值为'ignore')。性能会略有提高,并且如果该假设未得到满足,则会收到KeyError。

1
尝试使用以下代码以获得您期望的输出,即使 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]

1
对于numpy数组,这可能是一种方法 -
%// 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

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

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
“index”方法可以使用,但对于可哈希的项目来说,它比“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
在 pandas 中,我会从这两个列表创建一个字典,然后调用 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

这是一种矢量化方法,比任何基于迭代的方法都要更具可扩展性。


这种方法没有向量化,map使用迭代。对于长列表,使用map会稍微快一些,但构建Series所需的时间意味着基于迭代的方法最终更快。 - TheBlackCat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接