代码之家  ›  专栏  ›  技术社区  ›  ShanZhengYang

如何有效地迭代数据帧并在这些值上增加NumPy数组?

  •  6
  • ShanZhengYang  · 技术社区  · 7 年前

    我的pandas/numpy已经生锈了,我编写的代码感觉效率低下。

    我正在初始化Python3.x中的一个零数组,长度为1000。就我而言,这些只是整数:

    import numpy as np
    array_of_zeros =  np.zeros((1000, ), )
    

    我还有下面的数据帧(比我的实际数据小得多)

    import pandas as pd
    dict1 = {'start' : [100, 200, 300], 'end':[400, 500, 600]}
    df = pd.DataFrame(dict1)
    print(df)
    ##
    ##    start     end
    ## 0    100     400
    ## 1    200     500
    ## 2    300     600
    

    start end . 这些值代表一系列值,即。 开始 结束 . 在上面,我们看到第一行有一个范围 100-400 200-500 ,然后 300-600

    我的目标是逐行遍历pandas数据帧,并增加numpy数组 array_of_zeros 基于这些指数位置。所以,如果 10 20 ,对于索引10-20,我想将0增加+1。

    import numpy as np
    array_of_zeros =  np.zeros((1000, ), )
    
    import pandas as pd
    dict1 = {'start' : [100, 200, 300], 'end':[400, 500, 600]}
    df = pd.DataFrame(dict1)
    print(df)
    
    for idx, row in df.iterrows():
        for i in range(int(row.start), int(row.end)+1):
            array_of_zeros[i]+=1
    

    而且有效!

    print(array_of_zeros[15])
    ## output: 0.0
    print(array_of_zeros[600])
    ## output: 1.0
    print(array_of_zeros[400])
    ## output: 3.0
    print(array_of_zeros[100])
    ## output: 1.0
    print(array_of_zeros[200])
    ## output: 2.0
    

    我的问题是:这是非常笨拙的代码!我不应该在numpy数组中使用这么多for循环!如果输入数据帧很大,这个解决方案将非常低效

    有没有更有效的方法来避免这种for循环?

    for i in range(int(row.start), int(row.end)+1):
        array_of_zeros[i]+=1
    

    或许有一个面向熊猫的解决方案?

    3 回复  |  直到 7 年前
        1
  •  4
  •   jpp    7 年前

    res[np.arange(A[i][0], A[i][1]+1)] += 1 ,但这并不高效,因为它涉及到创建新数组和使用高级索引。

    相反,你可以使用 numba 1

    from numba import jit
    
    @jit(nopython=True)
    def jpp(A):
        res = np.zeros(1000)
        for i in range(A.shape[0]):
            for j in range(A[i][0], A[i][1]+1):
                res[j] += 1
        return res
    

    一些基准测试结果:

    # Python 3.6.0, NumPy 1.11.3
    
    # check result the same
    assert (jpp(df[['start', 'end']].values) == original(df)).all()
    assert (pir(df) == original(df)).all()
    assert (pir2(df) == original(df)).all()
    
    # time results
    df = pd.concat([df]*10000)
    
    %timeit jpp(df[['start', 'end']].values)  # 64.6 µs per loop
    %timeit original(df)                      # 8.25 s per loop
    %timeit pir(df)                           # 208 ms per loop
    %timeit pir2(df)                          # 1.43 s per loop
    

    用于基准测试的代码:

    def original(df):
        array_of_zeros = np.zeros(1000)
        for idx, row in df.iterrows():
            for i in range(int(row.start), int(row.end)+1):
                array_of_zeros[i]+=1   
        return array_of_zeros
    
    def pir(df):
        return np.bincount(np.concatenate([np.arange(a, b + 1) for a, b in \
                           zip(df.start, df.end)]), minlength=1000)
    
    def pir2(df):
        a = np.zeros((1000,), np.int64)
        for b, c in zip(df.start, df.end):
            np.add.at(a, np.arange(b, c + 1), 1)
        return a
    

    1 为了子孙后代,我在这里加入了@piRSquared关于原因的精彩评论 麻木 这里有帮助:

    麻木 了解很多NumPy的API,避免创建 数据帧中的每一行。然后在使用 宾尼计数。@jpp的 麻木 代码只创建很少的额外对象和 NumPy解决方案和@jpp's 溶液约为4-5倍。两者都是

        2
  •  4
  •   piRSquared    7 年前

    numpy.bincount

    np.bincount(np.concatenate(
        [np.arange(a, b + 1) for a, b in zip(df.start, df.end)]
    ), minlength=1000)
    

    numpy.add.at

    a = np.zeros((1000,), np.int64)
    for b, c in zip(df.start, df.end):
      np.add.at(a, np.arange(b, c + 1), 1)
    
        3
  •  3
  •   BENY    7 年前

    我的解决方案

    for x, y in zip(df.start, df.end):
        array_of_zeros[x:y+1]+=1