Как ускорить вложенные циклы для groupby multiindex - PullRequest
2 голосов
/ 11 июля 2020

У меня есть два фрейма данных Multiindex, а именно, panel1 и panel2: оба имеют один и тот же индекс уровня 0 - даты, но другой индекс уровня 1; см. пример кода ниже:

# panel1:
idx1 = pd.MultiIndex.from_product([['2017-05-02', '2017-05-03', '2017-05-04'],['id1', 'id2', 'id3']],names=['Dates', 'id'])
panel1=pd.DataFrame(np.random.randn(9,2), index=idx1,columns=['ytm','mat'])
# panel2:
idx2 = pd.MultiIndex.from_product([['2017-05-02', '2017-05-03', '2017-05-04'],['0.5', '1.5', '2.5']],names=['Dates', 'yr'])
panel2=pd.DataFrame(np.random.randn(9), index=idx2,columns=['curve'])

Я хочу l oop по двум панелям по датам (индекс уровня 0). Поэтому для каждого дня (например, '2017-05-02') я ищу mat каждого идентификатора / строки (панели1) в столбце yr (панели2), если есть совпадение, я хочу получить соответствующие значения curve (для панели2) и добавить его в качестве нового столбца (с именем CDB ) в панель1.

My текущий код выглядит следующим образом:

group1=panel1.groupby(level=0)
group2=panel2.groupby(level=0)

lst=[]
for ytm in group1:              # loop over each day
    for yr in group2:           # loop over each day
        df_ytm=ytm[1]           # get df of id, yt & mat
        df_ytm=df_ytm.assign(CDB=np.nan)      # add a col of nan, later will be replaced by matched curve values
        df_curve=yr[1].reset_index()          # need get rid of index to match yr with t_mat
        df_curve.yr=df_curve.yr.astype(float) 
        for i in range(df_ytm.shape[0]):      # loop over each row
            if (df_ytm.iloc[i,1]==df_curve.yr).any()==True:      # search if each 'mat' value in 'yr' column
                df_ytm.iloc[i,2]=df_curve[df_curve.yr.isin([df_ytm.t_mat[i]])].curve.values   # if matched, set 'CDB' as curve value
    lst.append(df_ytm)      # need get modified 'df_ytm' (with matched 'CDB')  

Код работает так же, как я пытался с небольшим образцом, но у меня есть огромная панель 1 (размером 800 дней на 10000 идентификаторов) и большая панель 2. Итак, код работает более 24 часов.

Интересно, как я могу переписать код (использовать возможную векторизацию), чтобы ускорить его?

Любые комментарии были бы очень признательны!

Ответы [ 2 ]

0 голосов
/ 11 июля 2020

Чтобы сгенерировать любой непустой и повторяемый результат моего кода, я немного изменил способ создания обеих панелей:

np.random.seed(0)
idx1 = pd.MultiIndex.from_product([['2017-05-02', '2017-05-03', '2017-05-04'],
    ['id1', 'id2', 'id3']], names=['Dates', 'id'])
panel1 = pd.DataFrame({'ytm': np.random.randn(9),
    'mat': [0.5, 0.82, 1.06, -0.27, 1.5, 0.59, 0.62, 1.89, 2.5]}, index=idx1)
idx2 = pd.MultiIndex.from_product([['2017-05-02', '2017-05-03', '2017-05-04'],
    [0.5, 1.5, 2.5]], names=['Dates', 'yr'])
panel2 = pd.DataFrame(np.random.randn(9), index=idx2, columns=['curve'])

Изменения включают:

  • np.random.seed - получить воспроизводимые результаты.
  • Только ytm столбец panel1 создается как случайные числа. Чтобы иметь некоторые совпадающие значения в mat , я помещаю туда предопределенные значения, обеспечивая одно совпадение с yr для каждой даты.
  • Уровень 1 idx2 относится к типу float . Ваш образец включает строки , что, очевидно, не будет равно mat значениям.

Я также предполагаю, что для каждой группы из panel1 поиск совпадений должен выполняться в строках из panel2 с той же датой (не в группах для всех дат).

Для генерации результата ( CDB столбец), действуйте следующим образом:

  1. Определите функцию, генерирующую CDB столбец для текущей группы строк (для каждой даты):

     def getCDB(grp):
         cdb = panel2.xs(grp.index[0][0], level=0).reindex(grp.mat).curve
         return pd.Series(cdb.values, index=grp.index)
    
  2. Затем примените его и сохраните результат в новом столбце:

     panel1['CDB'] = panel1.groupby(level=0).apply(getCDB)\
         .reset_index(level=0, drop=True)
    

Для моих входных данных результат:

                     ytm   mat       CDB
Dates      id                           
2017-05-02 id1  1.764052  0.50  0.410599
           id2  0.400157  0.82       NaN
           id3  0.978738  1.06       NaN
2017-05-03 id1  2.240893 -0.27       NaN
           id2  1.867558  1.50  0.121675
           id3 -0.977278  0.59       NaN
2017-05-04 id1  0.950088  0.62       NaN
           id2 -0.151357  1.89       NaN
           id3 -0.103219  2.50 -0.205158
0 голосов
/ 11 июля 2020

Если я вас правильно понял, вам нужно построить новый MultiIndex из Dates индекса и mat столбца и получить значения curve для этого индекса.

import pandas as pd
import numpy as np

np.random.seed(12)
idx1 = pd.MultiIndex.from_product(
    [["2017-05-02", "2017-05-03", "2017-05-04"], ["id1", "id2", "id3"]],
    names=["Dates", "id"],
)
panel1 = pd.DataFrame(
    np.random.randint(3, size=(9, 2)), index=idx1, columns=["ytm", "mat"]
)
idx2 = pd.MultiIndex.from_product(
    [["2017-05-02", "2017-05-03", "2017-05-04"], ["0", "1", "2"]], names=["Dates", "yr"]
)
panel2 = pd.DataFrame(np.random.randint(3, size=9), index=idx2, columns=["curve"])
print(panel1)
#                 ytm  mat
# Dates      id
# 2017-05-02 id1    2    1
#            id2    1    2
#            id3    0    0
# 2017-05-03 id1    2    1
#            id2    0    1
#            id3    1    1
# 2017-05-04 id1    2    2
#            id2    2    0
#            id3    1    0
print(panel2)
#                curve
# Dates      yr
# 2017-05-02 0       0
#            1       1
#            2       2
# 2017-05-03 0       1
#            1       2
#            2       0
# 2017-05-04 0       1
#            1       2
#            2       0
panel1["CDM"] = panel2.loc[
    pd.MultiIndex.from_arrays(
        [panel1.index.get_level_values(0), panel1.mat.astype(str).rename("yr")]
    )
].to_numpy()
print(panel1)
#                 ytm  mat  CDM
# Dates      id
# 2017-05-02 id1    2    1    1
#            id2    1    2    2
#            id3    0    0    0
# 2017-05-03 id1    2    1    2
#            id2    0    1    2
#            id3    1    1    2
# 2017-05-04 id1    2    2    0
#            id2    2    0    1
#            id3    1    0    1

EDIT

Для сравнения mat и yr как с плавающей точкой и использования .reindex вместо .loc.

import pandas as pd
import numpy as np

np.random.seed(12)
idx1 = pd.MultiIndex.from_product(
    [["2017-05-02", "2017-05-03", "2017-05-04"], ["id1", "id2", "id3"]],
    names=["Dates", "id"],
)
panel1 = pd.DataFrame(
    np.random.randint(3, size=(9, 2)), index=idx1, columns=["ytm", "mat"]
)
panel1.iloc[0, 1] = np.nan
idx2 = pd.MultiIndex.from_product(
    [["2017-05-02", "2017-05-03", "2017-05-04"], ["0", "1", "2"]], names=["Dates", "yr"]
)
panel2 = pd.DataFrame(np.random.randint(3, size=9), index=idx2, columns=["curve"])
panel2 = panel2.rename(float, level=1)
print(panel1)
#                 ytm  mat
# Dates      id
# 2017-05-02 id1    2  NaN
#            id2    1  2.0
#            id3    0  0.0
# 2017-05-03 id1    2  1.0
#            id2    0  1.0
#            id3    1  1.0
# 2017-05-04 id1    2  2.0
#            id2    2  0.0
#            id3    1  0.0
print(panel2)
#                 curve
# Dates      yr
# 2017-05-02 0.0      0
#            1.0      1
#            2.0      2
# 2017-05-03 0.0      1
#            1.0      2
#            2.0      0
# 2017-05-04 0.0      1
#            1.0      2
#            2.0      0
panel1["CDM"] = panel2.reindex(
    pd.MultiIndex.from_arrays(
        [panel1.index.get_level_values(0), panel1.mat.rename("yr")]
    )
).to_numpy()
print(panel1)
#                 ytm  mat  CDM
# Dates      id
# 2017-05-02 id1    2  NaN  NaN
#            id2    1  2.0  2.0
#            id3    0  0.0  0.0
# 2017-05-03 id1    2  1.0  2.0
#            id2    0  1.0  2.0
#            id3    1  1.0  2.0
# 2017-05-04 id1    2  2.0  0.0
#            id2    2  0.0  1.0
#            id3    1  0.0  1.0
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...