Python Pandas 多索引数据帧的高效合并
Efficient Merging of Python Pandas Multiindex Dataframes
我有两个数据框:第一个是包含 returns 两只股票的多索引框,表示为:
import pandas as pd
from pandas import IndexSlice as idx
import numpy as np
dates = pd.date_range('2000-12-31', periods=6, freq='M', name='Date')
arrays = [dates.tolist()+dates.tolist(),["10000"]*6+["10001"]*6]
index = pd.MultiIndex.from_tuples(list(zip(*arrays)), names=["Date", "Stock"])
df1 = pd.Series(np.random.randn(12), index=index).to_frame('Return').sort_index()
第二帧表示为:
并包含给定时间跨度的股票代码。
data = {'Stock':['10000','10000','10000','10001'],
'Start':['1990-12-31', '2001-03-05', '2001-05-19', '1991-03-31'],
'End':['2001-03-04', '2001-05-18', '2002-01-31', '2001-04-03'],
'Code':['10','11','10','10']}
df2 = pd.DataFrame(data)
df2 = df2.set_index('Stock').sort_index()
df2['Start'] = pd.to_datetime(df2['Start'])
df2['End'] = pd.to_datetime(df2['End'])
各个跨度的开始日期在 'Start' 列中给出,结束日期在 'End' 列中。我想将股票代码(在 df2 的 'Code' 列中给出)添加到 df1,这样如果股票的时间索引(df1)在 'Start' 和 'End' 列之间df2,则 df1 的 'Code' 列中的相应条目包含 df2 中给出的代码。如果没有指定代码或者 df2 中的时间跨度没有覆盖 df1 中的时间索引,则应分配 np.nan。
考虑示例 Date='2001-03-31' 和 Stock='10000'。根据 df2 的第二行,我们看到股票 10000 从 2001-03-05 到 2001-05-18 的代码为 11。因此,行中的条目 ('2001-03-31','10000')在 df1 的 'Code' 列中应包含 11.
整个df1显示在这里:
以下函数具有所需的功能,但(即使使用并行化)非常慢:
df1s = df1.swaplevel().sort_index().copy() # reorder s.t. date is first
df1['Code'] = np.nan
for p_tmp in df2.index.drop_duplicates().values:
d_tmp = df1s.loc[idx[p_tmp,:]].index.get_level_values(0) # End of each month
output = np.array([np.nan]*df1s.loc[idx[p_tmp,:]].index.size)
if isinstance(df2.loc[p_tmp], pd.Series):
b_mask = np.array((d_tmp >= pd.to_datetime(df2.loc[p_tmp]['Start'])) & (d_tmp <= pd.to_datetime(df2.loc[p_tmp]['End'])))
output[b_mask] = df2.loc[p_tmp]['Code']
else:
for index, row in df2.loc[p_tmp].iterrows():
b_mask = np.array((d_tmp >= pd.to_datetime(row['Start'])) & (d_tmp <= pd.to_datetime(row['End'])))
output[b_mask] = row['Code']
df1s.loc[p_tmp,'Code'] = output.copy()
df1 = df1s.swaplevel().sort_index().copy()
有人可以给我提示如何实现加速吗? :)
解决方案可能是这样的:
df_tmp = pd.DataFrame(index=df1.index).join(df2, on='Stock', how='left')
blist_validlink = (df_tmp['Start']<=df_tmp.index.get_level_values('Date'))&(df_tmp['End']>=df_tmp.index.get_level_values('Date'))
df_tmp = df_tmp[blist_validlink]
df1 = df1.join(df_tmp['Code'], on=['Date','Stock'])
我有两个数据框:第一个是包含 returns 两只股票的多索引框,表示为:
import pandas as pd
from pandas import IndexSlice as idx
import numpy as np
dates = pd.date_range('2000-12-31', periods=6, freq='M', name='Date')
arrays = [dates.tolist()+dates.tolist(),["10000"]*6+["10001"]*6]
index = pd.MultiIndex.from_tuples(list(zip(*arrays)), names=["Date", "Stock"])
df1 = pd.Series(np.random.randn(12), index=index).to_frame('Return').sort_index()
第二帧表示为:
并包含给定时间跨度的股票代码。
data = {'Stock':['10000','10000','10000','10001'],
'Start':['1990-12-31', '2001-03-05', '2001-05-19', '1991-03-31'],
'End':['2001-03-04', '2001-05-18', '2002-01-31', '2001-04-03'],
'Code':['10','11','10','10']}
df2 = pd.DataFrame(data)
df2 = df2.set_index('Stock').sort_index()
df2['Start'] = pd.to_datetime(df2['Start'])
df2['End'] = pd.to_datetime(df2['End'])
各个跨度的开始日期在 'Start' 列中给出,结束日期在 'End' 列中。我想将股票代码(在 df2 的 'Code' 列中给出)添加到 df1,这样如果股票的时间索引(df1)在 'Start' 和 'End' 列之间df2,则 df1 的 'Code' 列中的相应条目包含 df2 中给出的代码。如果没有指定代码或者 df2 中的时间跨度没有覆盖 df1 中的时间索引,则应分配 np.nan。
考虑示例 Date='2001-03-31' 和 Stock='10000'。根据 df2 的第二行,我们看到股票 10000 从 2001-03-05 到 2001-05-18 的代码为 11。因此,行中的条目 ('2001-03-31','10000')在 df1 的 'Code' 列中应包含 11.
整个df1显示在这里:
以下函数具有所需的功能,但(即使使用并行化)非常慢:
df1s = df1.swaplevel().sort_index().copy() # reorder s.t. date is first
df1['Code'] = np.nan
for p_tmp in df2.index.drop_duplicates().values:
d_tmp = df1s.loc[idx[p_tmp,:]].index.get_level_values(0) # End of each month
output = np.array([np.nan]*df1s.loc[idx[p_tmp,:]].index.size)
if isinstance(df2.loc[p_tmp], pd.Series):
b_mask = np.array((d_tmp >= pd.to_datetime(df2.loc[p_tmp]['Start'])) & (d_tmp <= pd.to_datetime(df2.loc[p_tmp]['End'])))
output[b_mask] = df2.loc[p_tmp]['Code']
else:
for index, row in df2.loc[p_tmp].iterrows():
b_mask = np.array((d_tmp >= pd.to_datetime(row['Start'])) & (d_tmp <= pd.to_datetime(row['End'])))
output[b_mask] = row['Code']
df1s.loc[p_tmp,'Code'] = output.copy()
df1 = df1s.swaplevel().sort_index().copy()
有人可以给我提示如何实现加速吗? :)
解决方案可能是这样的:
df_tmp = pd.DataFrame(index=df1.index).join(df2, on='Stock', how='left')
blist_validlink = (df_tmp['Start']<=df_tmp.index.get_level_values('Date'))&(df_tmp['End']>=df_tmp.index.get_level_values('Date'))
df_tmp = df_tmp[blist_validlink]
df1 = df1.join(df_tmp['Code'], on=['Date','Stock'])