簡體   English   中英

使用numpy廣播/矢量化從其他數組構建新數組

[英]using numpy broadcasting / vectorization to build new array from other arrays

我正在研究Quantopian模型的股票排名因子。 他們建議避免在自定義因素中使用循環。 但是,我不確定在這種情況下如何避免循環。

def GainPctInd(offset=0, nbars=2):  
    class GainPctIndFact(CustomFactor):  
        window_length = nbars + offset  
        inputs = [USEquityPricing.close, ms.asset_classification.morningstar_industry_code]  
        def compute(self, today, assets, out, close, industries):
            # Compute the gain percents for all stocks
            asset_gainpct = (close[-1] - close[offset]) / close[offset] * 100  

            # For each industry, build a list of the per-stock gains over the given window  
            gains_by_industry = {}  
            for i in range(0, len(industries)):  
                industry = industries[0,i]  
                if industry in gains_by_industry:  
                    gains_by_industry[industry].append(asset_gainpct[i])  
                else:  
                    gains_by_industry[industry] = [asset_gainpct[i]]

            # Loop through each stock's industry and compute a mean value for that  
            # industry (caching it for reuse) and return that industry mean for  
            # that stock  
            mean_cache = {}  
            for i in range(0, len(industries)):  
                industry = industries[0,i]  
                if not industry in mean_cache:  
                    mean_cache[industry] = np.mean(gains_by_industry[industry])  
                out[i] = mean_cache[industry]  
    return GainPctIndFact()

當計算函數被調用, 資產是資產名稱的1-d陣列, 靠近是多維numpy的陣列,其中存在用於在資產中列出的每個資產window_length靠近價格(使用相同的索引號),和工業是與1-d數組中的每個資產關聯的行業代碼列表。 我知道numpy將此行中的gainpct的計算向量化:

asset_gainpct = (close[-1] - close[offset]) / close[offset] * 100

結果是asset_gainpct是每個股票的所有計算收益的一維數組。 我不清楚的部分是如何在沒有手動循環數組的情況下使用numpy來完成計算。

基本上,我需要做的是根據他們所在的行業匯總所有股票的所有收益,然后計算這些價值的平均值,然后將平均值反匯總回到完整的資產清單。

現在,我正在遍歷所有行業並將增益百分比推進到行業索引字典中,該字典存儲了每個行業的收益列表。 然后我計算這些列表的平均值並執行逆向行業查找,以根據行業將行業收益映射到每個資產。

在我看來,這應該可以在numpy中使用一些高度優化的數組遍歷,但我似乎無法弄明白。 我從來沒有在今天使用numpy,而且我對Python很新,所以這可能沒有幫助。


更新:

我修改了我的行業代碼循環,嘗試使用行業數組來處理帶掩碼數組的計算,以掩蓋asset_gainpct數組,如下所示:

    # For each industry, build a list of the per-stock gains over the given window 
    gains_by_industry = {}
    for industry in industries.T:
        masked = ma.masked_where(industries != industry[0], asset_gainpct)
        np.nanmean(masked, out=out)

它給了我以下錯誤:

IndexError:條件和輸入之間的不一致形狀(got(20,8412)和(8412,))

此外,作為旁注, 行業以20x8412陣列形式出現,因為window_length設置為20.額外值是前幾天股票的行業代碼,除非它們通常不會改變,因此它們可以是忽略。 我現在正在迭代行業.T(行業的轉置),這意味着行業是一個20元素的陣列,每個元素都有相同的行業代碼。 因此,我只需要元素0。

上面的錯誤來自ma.masked_where()調用。 industries數組是20x8412所以我認為asset_gainpct是列為(8412,)的那個。 如何使這些呼叫兼容?


更新2:

我已經修改了代碼,修復了我遇到的其他幾個問題。 它現在看起來像這樣:

    # For each industry, build a list of the per-stock gains over the given window 
    unique_ind = np.unique(industries[0,])
    for industry in unique_ind:
        masked = ma.masked_where(industries[0,] != industry, asset_gainpct)
        mean = np.full_like(masked, np.nanmean(masked), dtype=np.float64, subok=False)
        np.copyto(out, mean, where=masked)

基本上,這里的新前提是我必須構建一個與我的輸入數據中的股票數量相同大小的平均值填充數組,然后在應用我之前的掩碼時將值復制到我的目標變量( out )中,以便只有未屏蔽的索引用平均值填充。 此外,我意識到我在之前的化身中不止一次地重復行業,所以我也解決了這個問題。 但是,copyto()調用會產生此錯誤:

TypeError:根據規則'safe',無法將數組數據從dtype('float64')轉換為dtype('bool')

顯然,我做錯了; 但通過文檔查看,我看不出它是什么。 這看起來應該是從均值 (這是np.float64 dtype)復制到out (我之前沒有定義),它應該使用masked作為布爾數組來選擇復制哪些索引。 任何人對這個問題有什么想法?


更新3:

首先,感謝所有貢獻者的反饋。

在進一步深入研究這段代碼之后,我想出了以下內容:

def GainPctInd(offset=0, nbars=2):
    class GainPctIndFact(CustomFactor):
        window_length = nbars + offset
        inputs = [USEquityPricing.close, ms.asset_classification.morningstar_industry_code]
        def compute(self, today, assets, out, close, industries):
            num_bars, num_assets = close.shape
            newest_bar_idx = (num_bars - 1) - offset
            oldest_bar_idx = newest_bar_idx - (nbars - 1)

            # Compute the gain percents for all stocks
            asset_gainpct = ((close[newest_bar_idx] - close[oldest_bar_idx]) / close[oldest_bar_idx]) * 100

            # For each industry, build a list of the per-stock gains over the given window 
            unique_ind = np.unique(industries[0,])
            for industry in unique_ind:
                ind_view = asset_gainpct[industries[0,] == industry]
                ind_mean = np.nanmean(ind_view)
                out[industries[0,] == industry] = ind_mean
    return GainPctIndFact()

出於某種原因,基於蒙版視圖的計算沒有產生正確的結果。 此外,將這些結果輸入out變量是行不通的。 在某個地方,我偶然發現了一篇關於numpy(默認情況下)如何在執行切片時創建數組視圖而不是副本的帖子以及您可以根據布爾條件執行稀疏切片的帖子。 在這樣的視圖上運行計算時,就計算而言,它看起來像一個完整的數組,但所有的值仍然實際上在基本數組中。 它有點像指針數組,計算發生在指針所指向的數據上。 同樣,您可以為稀疏視圖中的所有節點分配一個值,並讓它更新所有節點的數據。 這實際上大大簡化了邏輯。

我仍然會對任何人有關如何刪除行業的最終循環以及向量化該過程的任何想法感興趣。 我想知道是否可能有一個map / reduce方法可行,但是我仍然不熟悉numpy來弄清楚如何比這個FOR循環更有效地做到這一點。 從好的方面來看,剩余的循環只有大約140次迭代才能通過兩個先前的循環,每個循環將通過8000次。 除此之外,我現在避免構建gain_by_industrymean_cache dict並避免隨之而來的所有數據復制。 因此,它不僅速度更快,而且內存效率更高。


更新4:

有人給了我一個更簡潔的方法來完成這個,最后消除了額外的FOR循環。 它基本上隱藏了Pandas DataFrame組中的循環,但它更簡潔地描述了所需的步驟:

def GainPctInd2(offset=0, nbars=2):
    class GainPctIndFact2(CustomFactor):
        window_length = nbars + offset
        inputs = [USEquityPricing.close, ms.asset_classification.morningstar_industry_code]
        def compute(self, today, assets, out, close, industries):
            df = pd.DataFrame(index=assets, data={
                    "gain": ((close[-1 - offset] / close[(-1 - offset) - (nbars - 1)]) - 1) * 100,
                    "industry_codes": industries[-1]
                 })
            out[:] = df.groupby("industry_codes").transform(np.mean).values.flatten()
    return GainPctIndFact2()

根據我的基准測試,它根本沒有提高效率,但驗證正確性可能更容易。 他們的例子的一個問題是它使用np.mean而不是np.nanmean ,並且np.nanmean會丟棄NaN值,如果你嘗試使用它會導致形狀不匹配。 為了解決NaN問題,其他人建議:

def GainPctInd2(offset=0, nbars=2):
    class GainPctIndFact2(CustomFactor):
        window_length = nbars + offset
        inputs = [USEquityPricing.close, ms.asset_classification.morningstar_industry_code]
        def compute(self, today, assets, out, close, industries):
            df = pd.DataFrame(index=assets, data={
                    "gain": ((close[-1 - offset] / close[(-1 - offset) - (nbars - 1)]) - 1) * 100,
                    "industry_codes": industries[-1]
                 })
            nans = isnan(df['industry_codes'])
            notnan = ~nans
            out[notnan] = df[df['industry_codes'].notnull()].groupby("industry_codes").transform(np.nanmean).values.flatten()
            out[nans] = nan
    return GainPctIndFact2()

有人給了我一個更簡潔的方法來完成這個,最后消除了額外的FOR循環。 它基本上隱藏了Pandas DataFrame組中的循環,但它更簡潔地描述了所需的步驟:

def GainPctInd2(offset=0, nbars=2):
    class GainPctIndFact2(CustomFactor):
        window_length = nbars + offset
        inputs = [USEquityPricing.close, ms.asset_classification.morningstar_industry_code]
        def compute(self, today, assets, out, close, industries):
            df = pd.DataFrame(index=assets, data={
                    "gain": ((close[-1 - offset] / close[(-1 - offset) - (nbars - 1)]) - 1) * 100,
                    "industry_codes": industries[-1]
                 })
            out[:] = df.groupby("industry_codes").transform(np.mean).values.flatten()
    return GainPctIndFact2()

根據我的基准測試,它根本沒有提高效率,但驗證正確性可能更容易。 他們的例子的一個問題是它使用np.mean而不是np.nanmean ,並且np.nanmean會丟棄NaN值,如果你嘗試使用它會導致形狀不匹配。 為了解決NaN問題,其他人建議:

def GainPctInd2(offset=0, nbars=2):
    class GainPctIndFact2(CustomFactor):
        window_length = nbars + offset
        inputs = [USEquityPricing.close, ms.asset_classification.morningstar_industry_code]
        def compute(self, today, assets, out, close, industries):
            df = pd.DataFrame(index=assets, data={
                    "gain": ((close[-1 - offset] / close[(-1 - offset) - (nbars - 1)]) - 1) * 100,
                    "industry_codes": industries[-1]
                 })
            nans = isnan(df['industry_codes'])
            notnan = ~nans
            out[notnan] = df[df['industry_codes'].notnull()].groupby("industry_codes").transform(np.nanmean).values.flatten()
            out[nans] = nan
    return GainPctIndFact2()

- user36048

暫無
暫無

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

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