Pandas - 在包含区间的多级索引上进行索引

3

我有一个名为df1的pandas数据框,其MultiIndex由user_id值和start_dateend_dateIntervalIndex组成。 我希望根据第二个名为df2的数据框中相应的值选择来自df1的行。

数据框df1df2如下所示:

In [1]: df1
Out [1]:
                                 start_date   end_date status  score
user_id                                                             
A       [2017-03-07, 2017-03-11] 2017-03-07 2017-03-11     S1   1000
        [2017-03-12, 2017-04-03] 2017-03-12 2017-04-03     S2   1000
        [2017-04-04, 2017-05-21] 2017-04-04 2017-05-21     S1   1000
        [2017-05-22, 2222-12-31] 2017-05-22 2222-12-31     S3   1000
B       [2018-12-01, 2018-12-22] 2018-12-01 2018-12-22     S1    900
        [2018-12-23, 2018-12-28] 2018-12-23 2018-12-28     S2    900
        [2018-12-29, 2222-12-31] 2018-12-29 2222-12-31     S1   1500


In [2]: df2
Out [2]:
  user_id   ref_date
0       A 2017-04-24
1       B 2018-12-25

我希望选择df1中也在df2中的user_id以及相应的df2.ref_date所在的df1区间。在这个例子中,我期望得到第三行和第六行。

如果我使用df2的单行数据,可以通过运行以下代码找到对应的df1行:

In [3]: df1.loc[['A']].index.get_level_values(1).get_indexer([pd.to_datetime('2017-04-24')])
Out [3]: array([2])

有没有一种方法可以使用数组一次性完成索引操作?

以下是创建数据框的代码:

users = {'user_id': ['A','A','A','A', 'B','B','B'],
         'start_date': ['2017-03-07', '2017-03-12', '2017-04-04', '2017-05-22', '2018-12-01', '2018-12-23', '2018-12-29'],
         'end_date': ['2017-03-11', '2017-04-03', '2017-05-21', '2222-12-31', '2018-12-22', '2018-12-28', '2222-12-31'],
         'status': ['S1', 'S2', 'S1', 'S3', 'S1', 'S2', 'S1'],
         'score': [1000, 1000, 1000, 1000, 900, 900, 1500]
        }

df1 = pd.DataFrame(users, columns = ['user_id', 'start_date', 'end_date', 'status', 'score'])

for col in ['start_date', 'end_date']:
    df1[col] = pd.to_datetime(df1[col])

df1.set_index(['user_id', pd.IntervalIndex.from_arrays(df1['start_date'], df1['end_date'], closed='both')], drop=True, inplace=True)



df2 = pd.DataFrame({'user_id': ['A', 'B'], 
                   'ref_date': ['2017-04-24', '2018-12-25']})

df2['ref_date'] = pd.to_datetime(df2['ref_date'])
1个回答

4

一种解决方法是合并这两个数据框然后进行查询:

df1.index.names = ['user_id', 'date_ranges']

df_merged = df1.merge(df2, on='user_id', how='left').\
    query('start_date <= ref_date <= end_date')

df_merged.head()

#   user_id start_date  end_date    status  score   ref_date
# 2 A   2017-04-04  2017-05-21  S1  1000    2017-04-24
# 5 B   2018-12-23  2018-12-28  S2  900 2018-12-25

合并数据帧的缺点是会丢失多重索引。然而,如果在合并时保持how='left',则可以在df1中使用iloc来使用df_merged的整数索引:
df1.iloc[df_merged.index].head()

#     user_id       start_date  end_date    status  score
# A [2017-04-04, 2017-05-21]    2017-04-04  2017-05-21  S1  1000
# B [2018-12-23, 2018-12-28]    2018-12-23  2018-12-28  S2  900

另外一个缺点是你会创建行的笛卡尔积,因此如果你有大量的行,你可能会遇到内存问题。 - Scott Boston
非常感谢!新手问题可能是:您有任何想法如何为“df1”的一个“选定”行分配一个值,而不会冒险修改视图(链式赋值)? - notiv
df_merged 是一个独立的数据框,如果您在那里进行任何更改,df1 中不会更新任何内容。通常方法会返回一个新的数据框,但请始终检查文档 https://pandas.pydata.org/pandas-docs/stable/reference/api。 - jcaliz
抱歉,让我重新表达一下。如果我使用iloc选择df1的行,然后给一个列赋一个新值,我会得到一个SettingWithCopyWarning警告: df1.iloc[df_merged.index].start_date = '2020-04-24' 有什么解决方法吗? - notiv
1
iloc 中选择行和列,例如 df1.iloc[1, df1.columns == 'user_id'] =0,请记住 iloc 是基于整数的,因此您不能通过名称选择列,这就是为什么我使用了布尔遮罩。您也可以使用范围:df1.iloc[[0:2], df1.columns == 'user_id'] =0 - jcaliz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接