【问题标题】:Applying several functions to each row of an array将多个函数应用于数组的每一行
【发布时间】:2015-05-28 12:47:03
【问题描述】:

我有一个 numpy 数组,它只有几个非零条目,可以是正数或负数。例如。像这样:

myArray = np.array([[ 0.        ,  0.        ,  0.        ],
       [ 0.32, -6.79,  0.        ],
       [ 0.        ,  0.        ,  0.        ],
       [ 0.        ,  1.5        ,  0.        ],
       [ 0.        ,  0.        , -1.71]])

最后,我想收到一个列表,其中该列表的每个条目对应于 myArray 的一行,并且是函数输出的累积乘积,函数输出取决于 myArray 的相应行和另一个列表的条目(在下面的例子称为 l)。 各个术语取决于 myArray 条目的符号:当它为正时,我应用“funPos”,当它为负时,我应用“funNeg”,如果条目为 0,则术语将为 1。所以在示例中上面的数组将是:

output = [1*1*1 , 
         funPos(0.32, l[0])*funNeg(-6.79,l[1])*1, 
         1*1*1, 
         1*funPos(1.5, l[1])*1, 
         1*1*funNeg(-1.71, l[2])]

我实现了这个,如下所示,它给了我想要的输出(注意:这只是一个高度简化的玩具示例;实际的矩阵要大得多,函数也更复杂)。我遍历数组的每一行,如果行的和为0,我不需要做任何计算,输出只是1。如果不等于0,我遍历这一行,检查符号每个值并应用适当的函数。

import numpy as np
def doCalcOnArray(Array1, myList):

    output = np.ones(Array1.shape[0]) #initialize output

    for indRow,row in enumerate(Array1):

    if sum(row) != 0: #only then calculations are needed
        tempProd = 1. #initialize the product that corresponds to the row
        for indCol, valCol in enumerate(row):

        if valCol > 0:
            tempVal = funPos(valCol, myList[indCol])

        elif valCol < 0:
            tempVal = funNeg(valCol, myList[indCol])

        elif valCol == 0:
            tempVal = 1

        tempProd = tempProd*tempVal

        output[indRow] = tempProd

    return output 

def funPos(val1,val2):
    return val1*val2

def funNeg(val1,val2):
    return val1*(val2+1)

myArray = np.array([[ 0.        ,  0.        ,  0.        ],
       [ 0.32, -6.79,  0.        ],
       [ 0.        ,  0.        ,  0.        ],
       [ 0.        ,  1.5        ,  0.        ],
       [ 0.        ,  0.        , -1.71]])     

l = [1.1, 2., 3.4]

op = doCalcOnArray(myArray,l)
print op

输出是

[ 1.      -7.17024  1.       3.      -7.524  ]

这是想要的。
我的问题是是否有更有效的方法来做到这一点,因为这对于大型阵列来说非常“昂贵”。

编辑: 我接受了 gabhijit 的回答,因为他提出的纯 numpy 解决方案似乎是我正在处理的数组中最快的解决方案。请注意,RaJa 还提供了一个不错的工作解决方案,它需要 panda,并且 dave 的解决方案也可以正常工作,可以作为如何使用生成器和 numpy 的“apply_along_axis”的一个很好的例子。

【问题讨论】:

    标签: python arrays performance numpy


    【解决方案1】:

    这是我尝试过的 - 使用 reduce、map。我不确定这有多快 - 但这是你想要做的吗?

    编辑 4:最简单且最具可读性 - 将 l 设为 numpy 数组,然后大大简化 where。

    import numpy as np
    import time
    
    l = np.array([1.0, 2.0, 3.0])
    
    def posFunc(x,y):
        return x*y
    
    def negFunc(x,y):
        return x*(y+1)
    
    def myFunc(x, y):
        if x > 0:
            return posFunc(x, y)
        if x < 0:
            return negFunc(x, y)
        else:
            return 1.0
    
    myArray = np.array([
            [ 0.,0.,0.],
            [ 0.32, -6.79,  0.],
            [ 0.,0.,0.],
            [ 0.,1.5,0.],
            [ 0.,0., -1.71]])
    
    t1 = time.time()
    a = np.array([reduce(lambda x, (y,z): x*myFunc(z,l[y]), enumerate(x), 1) for x in myArray])
    t2 = time.time()
    print (t2-t1)*1000000
    print a
    

    基本上让我们看一下它在enumerate(xx) 中的最后一行,它表示从 1 开始(reduce 的最后一个参数)。 myFunc 只取 myArray(row) 中的元素和l 中的元素@index row 并根据需要将它们相乘。

    我的输出与你的不同 - 所以我不确定这是否正是你想要的,但也许你可以遵循逻辑。

    另外,我不太确定这对于大型数组来说有多快。

    编辑:以下是执行此操作的“纯 numpy 方式”。

    my = myArray # just for brevity
    
    t1 = time.time() 
    # First set the positive and negative values
    # complicated - [my.itemset((x,y), posFunc(my.item(x,y), l[y])) for (x,y) in zip(*np.where(my > 0))]
    # changed to 
    my = np.where(my > 0, my*l, my)
    # complicated - [my.itemset((x,y), negFunc(my.item(x,y), l[y])) for (x,y) in zip(*np.where(my < 0))]
    # changed to 
    my = np.where(my < 0, my*(l+1), my)
    # print my - commented out to time it.
    
    # Now set the zeroes to 1.0s
    my = np.where(my == 0.0, 1.0, my)
    # print my  - commented out to time it
    
    a = np.prod(my, axis=1)
    t2 = time.time()
    print (t2-t1)*1000000
    
    print a
    

    让我尽量解释zip(*np.where(my != 0)) 部分。 np.where 只返回两个 numpy 数组,第一个数组是行索引,第二个数组是匹配条件 (my != 0) 在这种情况下的列索引。我们获取这些索引的元组,然后使用array.itemset 和array.item,谢天谢地,列索引对我们是免费的,所以我们可以在列表l 中获取该索引的元素@。这应该比以前更快(并且可读的数量级!!)。需要timeit 来查明是否确实如此。

    编辑2:不必单独调用正面和负面可以通过一个电话np.where(my != 0)来完成。

    【讨论】:

    • 似乎工作得很好,谢谢!您的输出与我的不同,因为您选择了不同的 l;在我的示例中,它是 l = [1.1, 2., 3.4]。用这个替换你的列表,给出所需的输出。我也会赞成您的解决方案。如果我有任何问题,我会尽量得到你的最后一行并回复你:)
    • 我试着计时——两个版本——有趣的是,'pure numpy' 版本并不比 reduce 快。我无法解释——为什么?有趣的。 Debian x86_64 上的 Python 版本 2.7.3。 Numpy 版本 1.6.2。编辑两个版本以添加它。
    • 事实上,平均而言,“减少”一个是迄今为止我尝试过的所有方法中“最快的”。
    • 精简版无法在 Python 3.x 上运行,因为 lambda 中的元组解包已被移除。我注意到当你对我的解决方案运行时。只要输入数组的形状小于 (100, 100),您的纯 numpy 就会更快。之后,您对每个元素的 if 语句会消耗更多时间。但我承认,您的解决方案效果很好。
    • @RaJa - 我认为您使用 pandas 编写的内容可以在纯 numpy 中完成。而不是np.where(my != 0),我会为np.where(my &gt; 0) 和np.where(my &lt; 0) 单独计算并且根本不调用myFunc。分别直接调用posFunc和negFunc。让我编辑代码以添加它,看看它是如何进行的。那时不需要“如果有”。感谢您指出reduce 的问题。
    【解决方案2】:

    那么,让我们看看我是否理解你的问题。

    1. 您希望将矩阵的元素映射到一个新矩阵,这样:
      • 0 映射到 1
      • x&gt;0 映射到 funPos(x)
      • x&lt;0 映射到 funNeg(x)
    2. 您想要计算此新矩阵中行中所有元素的乘积。

    所以,我会这样做:

    1:

    def myFun(a):
        if a==0:
            return 1
        if a>0:
            return funPos(a)
        if a<0:
            return funNeg(a)
    
    newFun = np.vectorize(myFun)
    newArray = newFun(myArray)
    

    对于 2:

    np.prod(newArray, axis = 1)
    

    编辑:要将索引传递给 funPos、funNeg,您可能可以这样做:

    # Python 2.7
    r,c = myArray.shape
    ctr = -1       # I don't understand why this should be -1 instead of 0
    def myFun(a):
        global ctr
        global c
        ind = ctr % c
        ctr += 1
        if a==0:
            return 1
        if a>0:
            return funPos(a,l[ind])
        if a<0:
            return funNeg(a,l[ind])
    

    【讨论】:

    • 这看起来已经像我要找的了。但我还必须合并需要以某种方式传递给“myFun”的列表 l(见上文)。您是否看到如何将其合并到您的示例中的简单方法?我从上面编辑了我的示例,以明确 funPos,funNeg 不仅采用一个参数,而且实际上采用两个参数:矩阵的值和 l 的值。
    • 我想不出一个简单的方法来做到这一点。我正在添加一种涉及全局列表的复杂方式。
    • 酷,期待看到:)。
    • 完成。 flat 给出了一个迭代器,我们基本上创建了一个数组,其中包含每个数字的 col 索引。我们将它传递给函数
    • 我还没有得到这个。在这种情况下如何通过列表 l ?之后跑什么?如果我在编辑中的代码下方添加“newFun = np.vectorize(myFun) newArray = newFun(myArray)”,我会在创建 newArray 时收到错误消息。
    【解决方案3】:

    我认为这个 numpy 函数会对你有所帮助

    numpy.apply_along_axis

    这是一种实现。此外,我会警告不要检查数组的总和是否为 0。由于机器精度限制,将浮点数与 0 进行比较可能会产生意外行为。此外,如果您有 -5 和 5,则总和为零,我不确定这就是您想要的。我使用 numpy 的 any() 函数来查看是否有任何非零值。为简单起见,我还将您的列表 (my_list) 拉入了全局范围。

    import numpy as np
    
    
    my_list = 1.1, 2., 3.4
    
    def func_pos(val1, val2):
        return val1 * val2
    
    def func_neg(val1, val2):
        return val1 *(val2 + 1)
    
    
    def my_generator(row):
        for i, a in enumerate(row):
            if a > 0:
                yield func_pos(a, my_list[i])
            elif a < 0:
                yield func_neg(a, my_list[i])
            else:
                yield 1
    
    
    def reduce_row(row):
        if not row.any():
            return 1.0
        else:
            return np.prod(np.fromiter(my_generator(row), dtype=float))
    
    
    def main():
        myArray = np.array([
                [ 0.        ,  0.        ,  0.        ],
                [ 0.32, -6.79,  0.        ],
                [ 0.        ,  0.        ,  0.        ],
                [ 0.        ,  1.5        ,  0.        ],
                [ 0.        ,  0.        , -1.71]])
        return np.apply_along_axis(reduce_row, axis=1, arr=myArray)
    

    可能有更快的实现,我认为 apply_along_axis 实际上只是一个隐藏的循环。

    我没有测试,但我敢打赌这比你开始的更快,并且应该更节省内存。

    【讨论】:

    • 效果很好,看来并感谢有关总和的警告!我现在投赞成票,并等待几天接受接受,以防出现其他解决方案(例如,一种有效的矢量化方式)。
    • 我可能是错的,但我不认为 numpy 的 vectorize 函数将有助于加快这里的速度,因为每个值都需要逻辑,同时还映射到列表索引。如果我被证明是错误的,我会很高兴也很高兴!
    • 确实,我也想不出这样的解决方案。也许 shashwat 找到了一个(见他的回答)。
    【解决方案4】:

    我已经使用 numpy 数组的屏蔽功能尝试了您的示例。但是,我找不到将数组中的值替换为 funPos 或 funNeg 的解决方案。

    所以我的建议是尝试使用 pandas 来代替它,因为它在屏蔽时会保存索引。

    看我的例子:

    import numpy as np
    import pandas as pd
    
    def funPos(a, b):
        return a * b
    def funNeg(a, b):
        return a * (b + 1)
    
    myPosFunc = np.vectorize(funPos) #vectorized form of funPos
    myNegFunc = np.vectorize(funNeg) #vectorized form of funNeg
    
    #Input
    I = [1.0, 2.0, 3.0]    
    x = pd.DataFrame([
        [ 0.,0.,0.],
        [ 0.32, -6.79,  0.],
        [ 0.,0.,0.],
        [ 0.,1.5,0.],
        [ 0.,0., -1.71]])
    
    b = pd.DataFrame(myPosFunc(x[x>0], I)) #calculate all positive values
    c = pd.DataFrame(myNegFunc(x[x<0], I)) #calculate all negative values   
    b = b.combineMult(c) #put values of c in b
    b = b.fillna(1) #replace all missing values that were '0' in the raw array
    y = b.product() #multiply all elements in one row
    
    #Output
    print ('final result')
    print (y)
    print (y.tolist())
    

    【讨论】:

    • 与 shashwat 的回答一样,您的回答并未考虑列表 l。你有什么方法可以整合它吗?谢谢!
    • 我已经编辑了我的示例来实现您的列表。您只需要通过将列与您的列表相乘来重新计算临时解决方案 b。
    • 我可能会遗漏一些东西——如果我遗漏了请纠正我——但我仍然认为这个解决方案不适合这个问题。 b、c 和 d 的计算不仅取决于 x,还取决于我命名为 l 的另一个列表。
    • 不,你是对的。我认为我错过了这一点。必须考虑一下。
    • 我已编辑我的答案以考虑您的列表“我”。我看到 gabhijit 的答案更优雅,但我认为在处理非常大的数组时使用 Pandas 胜过“if”案例。
    猜你喜欢
    • 1970-01-01
    • 2018-06-06
    • 2011-11-02
    • 1970-01-01
    • 1970-01-01
    • 2012-03-07
    • 2020-10-13
    • 2013-03-18
    • 2014-04-30
    相关资源
    最近更新 更多