在pandas中按范围加入/合并的最佳方式

Dim*_*old 20 python join numpy pandas

我经常使用范围条件将pandas用于合并(join).

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

A(A_id,A_value)

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
Run Code Online (Sandbox Code Playgroud)

这将非常简单,简短而有效.

同时在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)]
Run Code Online (Sandbox Code Playgroud)

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

是否有更优雅和/或更有效的方式来执行此操作?

piR*_*red 31

设置
考虑数据帧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
Run Code Online (Sandbox Code Playgroud)

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.DataFrame(
    np.column_stack([A.values[i], B.values[j]]),
    columns=A.columns.append(B.columns)
)

   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
Run Code Online (Sandbox Code Playgroud)

为了解决这些评论并给出类似于左连接的内容,我附加了A一些不匹配的内容.

pd.DataFrame(
    np.column_stack([A.values[i], B.values[j]]),
    columns=A.columns.append(B.columns)
).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
Run Code Online (Sandbox Code Playgroud)

  • 惊人的解决方案.. 我们可以说这是一个交叉连接...如果我只想保留所有的`A`行(基本上是在`A`上左连接)那么我需要做哪些改变? (2认同)
  • 这是一个绝妙的解决方案!比丑陋的熊猫骇客,使用昂贵的笛卡尔产品或使用诸如sqlite之类的其他软件包要好得多! (2认同)
  • 对于具有可能行和列的非常大的数据集,我得到了这个答案的内存溢出。=/ (2认同)
  • 您应该使用“.iloc”而不是“.loc”,因为从 numpy 接收的索引始终引用“iloc”。`loc` 可能会导致意外行为 (2认同)

Ado*_*nis 8

不确定这是否更有效,但是您可以将 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)
Run Code Online (Sandbox Code Playgroud)

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