在熊猫中按范围连接/合并的最佳方法

2025-01-14 08:50:00
admin
原创
81
摘要:问题描述:我经常使用 pandas 按照范围条件进行合并(连接)。例如,如果有2个数据框:A(A_id,A_值)B(B_id、B_low、B_high、B_name)它们很大并且大约具有相同的大小(假设每个有 2M 条记录)。我想在 A 和 B 之间建立内连接,因此 A_value 将位于 B_low 和 B...

问题描述:

我经常使用 pandas 按照范围条件进行合并(连接)。

例如,如果有2个数据框:

A(A_id,A_值)

B(B_id、B_low、B_high、B_name)

它们很大并且大约具有相同的大小(假设每个有 2M 条记录)。

我想在 A 和 B 之间建立内连接,因此 A_value 将位于 B_low 和 B_high 之间。

使用 SQL 语法如下:

SELECT *
FROM A,B
WHERE A_value between B_low and B_high

这真的非常简单、简短而且高效。

同时,在 Pandas 中,唯一的方法(我发现不是使用循环)是在两个表中创建一个虚拟列,然后连接它(相当于交叉连接),然后过滤掉不需要的行。这听起来很繁琐和复杂:

A['dummy'] = 1
B['dummy'] = 1
Temp = pd.merge(A,B,on='dummy')
Result = Temp[Temp.A_value.between(Temp.B_low,Temp.B_high)]

我的另一个解决方案是通过使用B[(x>=B.B_low) & (x<=B.B_high)]掩码对每个 A 值应用 B 上的搜索函数,但这听起来效率也很低,可能需要索引优化。

有没有更优雅和/或更有效的方法来执行此操作?


解决方案 1:

设置

考虑数据框AB

A = pd.DataFrame(dict(
        A_id=range(10),
        A_value=range(5, 105, 10)
    ))
B = pd.DataFrame(dict(
        B_id=range(5),
        B_low=[0, 30, 30, 46, 84],
        B_high=[10, 40, 50, 54, 84]
    ))

A

   A_id  A_value
0     0        5
1     1       15
2     2       25
3     3       35
4     4       45
5     5       55
6     6       65
7     7       75
8     8       85
9     9       95

B

   B_high  B_id  B_low
0      10     0      0
1      40     1     30
2      50     2     30
3      54     3     46
4      84     4     84

numpy
✌最简单✌的
方法是使用numpy广播。

我们寻找A_value大于或等于的每个实例B_low,同时又A_value小于或等于B_high

a = A.A_value.values
bh = B.B_high.values
bl = B.B_low.values

i, j = np.where((a[:, None] >= bl) & (a[:, None] <= bh))

pd.concat([
    A.loc[i, :].reset_index(drop=True),
    B.loc[j, :].reset_index(drop=True)
], axis=1)

   A_id  A_value  B_high  B_id  B_low
0     0        5      10     0      0
1     3       35      40     1     30
2     3       35      50     2     30
3     4       45      50     2     30

为了解决评论并提供类似于左连接的内容,我附加了A不匹配的部分。

pd.concat([
    A.loc[i, :].reset_index(drop=True),
    B.loc[j, :].reset_index(drop=True)
], axis=1).append(
    A[~np.in1d(np.arange(len(A)), np.unique(i))],
    ignore_index=True, sort=False
)

    A_id  A_value  B_id  B_low  B_high
0      0        5   0.0    0.0    10.0
1      3       35   1.0   30.0    40.0
2      3       35   2.0   30.0    50.0
3      4       45   2.0   30.0    50.0
4      1       15   NaN    NaN     NaN
5      2       25   NaN    NaN     NaN
6      5       55   NaN    NaN     NaN
7      6       65   NaN    NaN     NaN
8      7       75   NaN    NaN     NaN
9      8       85   NaN    NaN     NaN
10     9       95   NaN    NaN     NaN

解决方案 2:

不确定这是否更有效,但是你可以直接使用 sql(例如从模块 sqlite3 中)和 pandas(受此问题的启发),例如:

conn = sqlite3.connect(":memory:") 
df2 = pd.DataFrame(np.random.randn(10, 5), columns=["col1", "col2", "col3", "col4", "col5"])
df1 = pd.DataFrame(np.random.randn(10, 5), columns=["col1", "col2", "col3", "col4", "col5"])
df1.to_sql("df1", conn, index=False)
df2.to_sql("df2", conn, index=False)
qry = "SELECT * FROM df1, df2 WHERE df1.col1 > 0 and df1.col1<0.5"
tt = pd.read_sql_query(qry,conn)

您可以根据需要在应用程序中调整查询

解决方案 3:

我不知道它有多高效,但有人写了一个包装器,允许你将 SQL 语法与 pandas 对象一起使用。这个包装器叫做pandasql。文档明确指出支持连接。这可能至少更容易阅读,因为 SQL 语法非常易读。

解决方案 4:

我知道这是一个老问题,但对于新手来说,现在有pandas.merge_asof函数可以根据最接近的匹配执行连接。

如果你想进行合并,以便一个 DataFrame ( df_right) 的一列位于另一个 DataFrame ( df_left) 的两列之间,你可以执行以下操作:

df_left = pd.DataFrame({
    "time_from": [1, 4, 10, 21],
    "time_to": [3, 7, 15, 27]
})

df_right = pd.DataFrame({
    "time": [2, 6, 16, 25]
})

df_left

  time_from time_to
0        1       3
1        4       7
2       10      15
3       21      27


df_right

  time
0    2
1    6
2   16
3   25

首先,找到右侧 DataFrame 中与左侧 DataFrame 的左边界()最接近但最大的匹配项time_from

merged = pd.merge_asof(
    left=df_1,
    right=df_2.rename(columns={"time": "candidate_match_1"}),
    left_on="time_from",
    right_on="candidate_match_1",
    direction="forward"
)

merged

  time_from time_to candidate_match_1
0        1       3                 2
1        4       7                 6
2        10      15               16
3        21      27               25

如您所见,索引 2 中的候选匹配是错误匹配的,因为 16 不在 10 和 15 之间。

然后,找到右侧 DataFrame 中距离最近但小于左侧time_toDataFrame 右边界()的匹配项:

merged = pd.merge_asof(
    left=merged,
    right=df_2.rename(columns={"time": "candidate_match_2"}),
    left_on="time_to",
    right_on="candidate_match_2",
    direction="backward"
)

merged

  time_from time_to candidate_match_1   candidate_match_2
0        1        3                2                   2
1        4        7                6                   6
2        10      15               16                   6
3        21      27               25                  25

最后,保留候选匹配相同的匹配,这意味着右侧 DataFrame 的值位于左侧 DataFrame 两列的值之间:

merged["match"] = None
merged.loc[merged["candidate_match_1"] == merged["candidate_match_2"], "match"] = \n    merged.loc[merged["candidate_match_1"] == merged["candidate_match_2"], "candidate_match_1"]

merged

  time_from time_to candidate_match_1   candidate_match_2   match
0        1       3                 2                   2       2
1        4       7                 6                   6       6
2        10     15                16                   6    None
3        21     27                25                   25     25

解决方案 5:

pyjanitor的conditional_join以一种高效的方式解决了这个问题,它使用二分搜索而不是交叉连接:

# pip install pyjanitor
import pandas as pd
import janitor

内连接

A.conditional_join(B, 
                   ('A_value', 'B_low', '>='), 
                   ('A_value', 'B_high', '<='),
                   # you may get more performance 
                   # with numba
                   use_numba=False
                  )

   A_id  A_value  B_id  B_low  B_high
0     0        5     0      0      10
1     3       35     1     30      40
2     3       35     2     30      50
3     4       45     2     30      50

左连接

A.conditional_join(
       B, 
       ('A_value', 'B_low', '>='), 
       ('A_value', 'B_high', '<='), 
       how = 'left'
    )

    A_id  A_value  B_id  B_low  B_high
0      0        5   0.0    0.0    10.0
1      1       15   NaN    NaN     NaN
2      2       25   NaN    NaN     NaN
3      3       35   1.0   30.0    40.0
4      3       35   2.0   30.0    50.0
5      4       45   2.0   30.0    50.0
6      5       55   NaN    NaN     NaN
7      6       65   NaN    NaN     NaN
8      7       75   NaN    NaN     NaN
9      8       85   NaN    NaN     NaN
10     9       95   NaN    NaN     NaN

解决方案 6:

让我们举一个简单的例子:

df=pd.DataFrame([2,3,4,5,6],columns=['A'])

返回

    A
0   2
1   3
2   4
3   5
4   6

现在让我们定义第二个数据框

df2=pd.DataFrame([1,6,2,3,5],columns=['B_low'])
df2['B_high']=[2,8,4,6,6]

结果是

    B_low   B_high
0   1       2
1   6       8
2   2       4
3   3       6
4   5       6

开始吧;我们希望输出索引为 3,A 值为 5

df.where(df['A']>=df2['B_low']).where(df['A']<df2['B_high']).dropna()

结果是

    A
3   5.0
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用