如何在 python Pandas 中执行/解决条件连接?

Posted

技术标签:

【中文标题】如何在 python Pandas 中执行/解决条件连接?【英文标题】:How to do/workaround a conditional join in python Pandas? 【发布时间】:2014-06-23 20:56:38 【问题描述】:

我正在尝试根据存储在单独表中的日期值来计算 Pandas 中基于时间的聚合。

第一个表table_a的顶部是这样的:

    COMPANY_ID  DATE            MEASURE
    1   2010-01-01 00:00:00     10
    1   2010-01-02 00:00:00     10
    1   2010-01-03 00:00:00     10
    1   2010-01-04 00:00:00     10
    1   2010-01-05 00:00:00     10

创建表格的代码如下:

    table_a = pd.concat(\
    [pd.DataFrame('DATE': pd.date_range("01/01/2010", "12/31/2010", freq="D"),\
    'COMPANY_ID': 1 , 'MEASURE': 10),\
    pd.DataFrame('DATE': pd.date_range("01/01/2010", "12/31/2010", freq="D"),\
    'COMPANY_ID': 2 , 'MEASURE': 10)])

第二个表,table_b 如下所示:

        COMPANY     END_DATE
        1   2010-03-01 00:00:00
        1   2010-06-02 00:00:00
        2   2010-03-01 00:00:00
        2   2010-06-02 00:00:00

创建它的代码是:

    table_b = pd.DataFrame('END_DATE':pd.to_datetime(['03/01/2010','06/02/2010','03/01/2010','06/02/2010']),\
                    'COMPANY':(1,1,2,2))

我希望能够在 table_b 中的 END_DATE 之前的每个 30 天期间获取每个 COMPANY_ID 的度量列的总和。

这是(我认为)SQL 等价物:

      select
 b.COMPANY_ID,
 b.DATE
 sum(a.MEASURE) AS MEASURE_TO_END_DATE
 from table_a a, table_b b
 where a.COMPANY = b.COMPANY and
       a.DATE < b.DATE and
       a.DATE > b.DATE - 30  
 group by b.COMPANY;

感谢您的帮助

【问题讨论】:

table_b 中的end_date 是否每个都有重叠窗口;例如,公司 1 的结束日期可以是 2010-03-01 和 2010-03-15。 嗨@KarlD 是的,可能。 【参考方案1】:

嗯,我能想到几个办法:

    基本上通过仅在确切字段 (company) 上合并来炸毁数据框...然后在合并后的 30 天窗口上进行过滤。
应该很快,但会占用大量内存
    将 30 天窗口上的合并和过滤移至 groupby()
导致每个组合并,速度较慢但应该使用更少的内存

选项#1

假设您的数据如下所示(我扩展了您的示例数据):

print df

    company       date  measure
0         0 2010-01-01       10
1         0 2010-01-15       10
2         0 2010-02-01       10
3         0 2010-02-15       10
4         0 2010-03-01       10
5         0 2010-03-15       10
6         0 2010-04-01       10
7         1 2010-03-01        5
8         1 2010-03-15        5
9         1 2010-04-01        5
10        1 2010-04-15        5
11        1 2010-05-01        5
12        1 2010-05-15        5

print windows

   company   end_date
0        0 2010-02-01
1        0 2010-03-15
2        1 2010-04-01
3        1 2010-05-15

为 30 天窗口创建开始日期:

windows['beg_date'] = (windows['end_date'].values.astype('datetime64[D]') -
                       np.timedelta64(30,'D'))
print windows

   company   end_date   beg_date
0        0 2010-02-01 2010-01-02
1        0 2010-03-15 2010-02-13
2        1 2010-04-01 2010-03-02
3        1 2010-05-15 2010-04-15

现在进行合并,然后根据date 是否在beg_dateend_date 范围内进行选择:

df = df.merge(windows,on='company',how='left')
df = df[(df.date >= df.beg_date) & (df.date <= df.end_date)]
print df

    company       date  measure   end_date   beg_date
2         0 2010-01-15       10 2010-02-01 2010-01-02
4         0 2010-02-01       10 2010-02-01 2010-01-02
7         0 2010-02-15       10 2010-03-15 2010-02-13
9         0 2010-03-01       10 2010-03-15 2010-02-13
11        0 2010-03-15       10 2010-03-15 2010-02-13
16        1 2010-03-15        5 2010-04-01 2010-03-02
18        1 2010-04-01        5 2010-04-01 2010-03-02
21        1 2010-04-15        5 2010-05-15 2010-04-15
23        1 2010-05-01        5 2010-05-15 2010-04-15
25        1 2010-05-15        5 2010-05-15 2010-04-15

您可以通过对companyend_date 进行分组来计算 30 天窗口总和:

print df.groupby(['company','end_date']).sum()

                    measure
company end_date           
0       2010-02-01       20
        2010-03-15       30
1       2010-04-01       10
        2010-05-15       15

选项 #2 将所有合并移动到一个 groupby 中。这在内存上应该会更好,但我认为要慢得多:

windows['beg_date'] = (windows['end_date'].values.astype('datetime64[D]') -
                       np.timedelta64(30,'D'))

def cond_merge(g,windows):
    g = g.merge(windows,on='company',how='left')
    g = g[(g.date >= g.beg_date) & (g.date <= g.end_date)]
    return g.groupby('end_date')['measure'].sum()

print df.groupby('company').apply(cond_merge,windows)

company  end_date  
0        2010-02-01    20
         2010-03-15    30
1        2010-04-01    10
         2010-05-15    15

另一种选择现在,如果您的窗口从不重叠(如示例数据中所示),您可以执行以下操作作为替代方案,它不会破坏数据框但速度非常快:

windows['date'] = windows['end_date']

df = df.merge(windows,on=['company','date'],how='outer')
print df

    company       date  measure   end_date
0         0 2010-01-01       10        NaT
1         0 2010-01-15       10        NaT
2         0 2010-02-01       10 2010-02-01
3         0 2010-02-15       10        NaT
4         0 2010-03-01       10        NaT
5         0 2010-03-15       10 2010-03-15
6         0 2010-04-01       10        NaT
7         1 2010-03-01        5        NaT
8         1 2010-03-15        5        NaT
9         1 2010-04-01        5 2010-04-01
10        1 2010-04-15        5        NaT
11        1 2010-05-01        5        NaT
12        1 2010-05-15        5 2010-05-15

这种合并实质上是将您的窗口结束日期插入到数据框中,然后(按组)回填结束日期将为您提供一个结构来轻松创建汇总窗口:

df['end_date'] = df.groupby('company')['end_date'].apply(lambda x: x.bfill())

print df

    company       date  measure   end_date
0         0 2010-01-01       10 2010-02-01
1         0 2010-01-15       10 2010-02-01
2         0 2010-02-01       10 2010-02-01
3         0 2010-02-15       10 2010-03-15
4         0 2010-03-01       10 2010-03-15
5         0 2010-03-15       10 2010-03-15
6         0 2010-04-01       10        NaT
7         1 2010-03-01        5 2010-04-01
8         1 2010-03-15        5 2010-04-01
9         1 2010-04-01        5 2010-04-01
10        1 2010-04-15        5 2010-05-15
11        1 2010-05-01        5 2010-05-15
12        1 2010-05-15        5 2010-05-15

df = df[df.end_date.notnull()]
df['beg_date'] = (df['end_date'].values.astype('datetime64[D]') -
                   np.timedelta64(30,'D'))

print df

   company       date  measure   end_date   beg_date
0         0 2010-01-01       10 2010-02-01 2010-01-02
1         0 2010-01-15       10 2010-02-01 2010-01-02
2         0 2010-02-01       10 2010-02-01 2010-01-02
3         0 2010-02-15       10 2010-03-15 2010-02-13
4         0 2010-03-01       10 2010-03-15 2010-02-13
5         0 2010-03-15       10 2010-03-15 2010-02-13
7         1 2010-03-01        5 2010-04-01 2010-03-02
8         1 2010-03-15        5 2010-04-01 2010-03-02
9         1 2010-04-01        5 2010-04-01 2010-03-02
10        1 2010-04-15        5 2010-05-15 2010-04-15
11        1 2010-05-01        5 2010-05-15 2010-04-15
12        1 2010-05-15        5 2010-05-15 2010-04-15

df = df[(df.date >= df.beg_date) & (df.date <= df.end_date)]
print df.groupby(['company','end_date']).sum()

                    measure
company end_date           
0       2010-02-01       20
        2010-03-15       30
1       2010-04-01       10
        2010-05-15       15

另一种选择是将您的第一个数据帧重新采样为每日数据,然后以 30 天的窗口计算 rolling_sums;并在末尾选择您感兴趣的日期。这也可能会占用大量内存。

【讨论】:

+1 用于展示两种策略及其优势/劣势。【参考方案2】:

有一种非常简单实用(或者可能是唯一直接的方法)在 pandas 中进行条件连接。由于在 pandas 中没有直接的方法来进行条件连接,因此您将需要一个额外的库,即pandasql

使用命令 pip install pandasql 从 pip 安装库 pandasql。该库允许您使用 SQL 查询来操作 pandas 数据帧。

import pandas as pd
from pandasql import sqldf

df = pd.read_excel(r'play_data.xlsx')
df

    id    Name  Amount
0   A001    A   100
1   A002    B   110
2   A003    C   120
3   A005    D   150

现在让我们做一个条件连接来比较 ID 的数量

# Make your pysqldf object:
pysqldf = lambda q: sqldf(q, globals())

# Write your query in SQL syntax, here you can use df as a normal SQL table
cond_join= '''
    select 
        df_left.*,
        df_right.*
    from df as df_left
    join df as df_right
    on
        df_left.[Amount] > (df_right.[Amount]+10)

'''

# Now, get your queries results as dataframe using the sqldf object that you created
pysqldf(cond_join)

    id  Name    Amount  id    Name  Amount
0   A003    C   120    A001   A   100
1   A005    D   150    A001   A   100
2   A005    D   150    A002   B   110
3   A005    D   150    A003   C   120

【讨论】:

这正是我想要的。感谢您的回答!! PS - 我在一个函数中调用它并使用locals() 而不是globals()cond_join = """ select df_left.*, df_right.* from t_p as df_left join t_s as df_right on df_left.short_key = df_right.short_key """combo = sqldf(cond_join, locals()) 任何人都知道 pandas 在幕后实际上做了什么。我想它会遵循上述策略之一。

以上是关于如何在 python Pandas 中执行/解决条件连接?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 python pandas 循环中对数据帧执行操作

如何在 FOR 循环中对 Python Pandas 列表中的元素执行字符串更改

2021-02-09 pip -i 更新失败后的解决方法

在 Pandas/Matplotlib 上输入图例后命名堆积条

Python下载numpy和pandas踩过的大坑

如何在 pandas python 中显示多个图表