簡體   English   中英

如何有效地迭代pandas DataFrame並在這些值上增加NumPy數組?

[英]How to efficiently iterate a pandas DataFrame and increment a NumPy array on these values?

我的熊貓/ numpy生銹了,我寫的代碼感覺效率低下。

我正在Python3.x初始化一個numpy零的數組,長度為1000.為了我的目的,這些只是整數:

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

我還有以下DataFrame(比我的實際數據小得多)

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

DataFrame有兩列, startend 這些值表示一系列值,即start始終是小於end整數。 在上面,我們看到第一行的范圍是100-400 ,接下來是200-500 ,然后是300-600

我的目標是逐行遍歷pandas DataFrame,並根據這些索引位置遞增numpy數組array_of_zeros 因此,如果數據幀中有一行為1020 ,我想將索引10-20的零增加+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循環! 如果輸入數據幀非常大,則此解決方案效率非常低

是否有更有效(即更多基於numpy)的方法來避免這種for循環?

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

也許有一個以熊貓為導向的解決方案?

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)

您可以使用NumPy數組索引來避免內部循環,即res[np.arange(A[i][0], A[i][1]+1)] += 1 ,但這不是有效的,因為它涉及創建一個新數組並使用高級索引。

相反,您可以使用numba 1來優化您的算法,就像它的情況一樣。 下面的示例通過將性能關鍵邏輯移動到JIT編譯的代碼來顯示出巨大的性能提升。

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關於為什么numba在這里幫助的優秀評論:

numba的優勢在於非常有效地循環。 雖然它可以理解NumPy的大部分API,但通常最好避免在循環中創建NumPy對象。 我的代碼是為數據幀中的每一行創建一個NumPy數組。 然后在使用bincount之前連接它們。 @jpp的numba代碼創建了很少的額外對象,並利用了已有的大部分內容。 我的NumPy解決方案和@jpp的numba解決方案之間的差異大約是4-5倍。 兩者都是線性的,應該很快。

我的解決方案

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM