【问题标题】:Find and replace multiple values in python在python中查找和替换多个值
【发布时间】:2015-04-02 08:04:51
【问题描述】:

我想在一维数组/列表中查找多个值并将其替换为新值。

以列表为例

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 ms,swenzel 的结果为6.89 s

【问题讨论】:

  • 这是几乎相同的问题:stackoverflow.com/questions/3403973/… 如果需要一个通用的非整数解决方案,那么对于大量替换 Shashank 的解决方案真的很有趣是最快的。对于少量替换,链接问题中接受的答案的 numpy 解决方案是最好的。 Python 字典和列表推导的速度真是太棒了。

标签: python numpy pandas


【解决方案1】:
>>> 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])

【讨论】:

  • 这也是我的第一次尝试...如果a 包含负数会有点棘手。
  • 对于负数计算额外的偏移量: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]
【解决方案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

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?你不会注意到差异!

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

In [27]: timed()
0.019248008728 Seconds

【讨论】:

    【解决方案3】:

    在 vanilla Python 中,没有numpypandas 的速度,这是一种方法:

    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】:

      试试这个以获得预期的输出,即使 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]
      

      【讨论】:

        【解决方案5】:

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

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

        实现的方法是基于swenzel的searchsorted,应该有类似的良好性能,但更通用。例如,数组的项不需要是整数,而是可以是任何类型,甚至是 nd-subarrays 本身。

        如果 'a' 中的所有值都应出现在 'val_old' 中,您可以将可选的 'missing' kwarg 设置为 'raise' (默认为 'ignore')。性能会稍微好一些,如果不满足这个假设,你会得到一个 KeyError。

        【讨论】:

          【解决方案6】:

          要使用其他两个列表作为键:值对替换列表中的值,有几种方法。他们都使用“列表压缩”。

          使用 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]
          

          【讨论】:

          • 'index' 方法会起作用,但它会比 dict 可散列项的方法慢。
          【解决方案7】:

          我试过这样:

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

          【讨论】:

            【解决方案8】:

            在 pandas 中,我会从 2 个列表中创建一个 dict,然后调用 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 所需的时间意味着基于迭代的方法最终会更快。
            【解决方案9】:

            对于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
            

            【讨论】:

              【解决方案10】:
              list(map(lambda x:val_new[val_old.index(x)], a))
              

              【讨论】:

                猜你喜欢
                • 2019-08-14
                • 2020-03-22
                • 2011-09-25
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2014-03-01
                • 1970-01-01
                • 2020-06-16
                相关资源
                最近更新 更多