Pandas中的SQL查询连接

xuo3flqw  于 2023-01-11  发布在  其他
关注(0)|答案(2)|浏览(139)

我想在Pandas餐厅坐两张table.

    • df_types**包含product类型的范围大小(5000行)
| Table: TYPES |          |      |
|--------------|----------|------|
| size_max     | size_min | type |
| 1            | 5        | S    |
| 6            | 16       | M    |
| 16           | 24       | L    |
| 25           | 50       | XL   |

Pandas中的 Dataframe 代码:

df_types = pd.DataFrame([[1,5,'S'],
                         [6,16,'M'],
                         [16,24,'L'],
                         [25,50,'XL']],
                        columns = ['size_min','size_max','type'])
    • df_products**包含产品ID和大小(12000行)
| Table: Products |      |
|-----------------|------|
| id_product      | size |
| A               | 6    |
| B               | 25   |
| C               | 7    |
| D               | 2    |
| F               | 45   |
| E               | 10   |
| G               | 16   |

Pandas中的 Dataframe 代码:

df_products = pd.DataFrame([['A',6,],
                            ['B',25],
                            ['C',7],
                            ['D',2],
                            ['F',45],
                            ['E',10],
                            ['G',16]],columns = ['id_product','size'])

我想在Pandas中创建这个SQL连接:

SELECT  *.df_products
        type.df_types
FROM    df_products     LEFT JOIN df_types
                        ON  df_products.size >= df_types.size_min
                            AND df_products.size <= df_types.size_max
    • 结果:**
| id_product | size | type |
|------------|------|------|
| A          | 6    | M    |
| B          | 25   | XL   |
| C          | 7    | M    |
| D          | 2    | S    |
| F          | 45   | XL   |
| E          | 10   | M    |
| G          | 16   | M    |

谢谢! -)

mv1qrgav

mv1qrgav1#

方法1:outer joinpd.merge

虽然这是SQL的常见操作,但对于pandas,没有直接的方法。
这里的解决方案之一是执行outer join以匹配所有行,然后使用DataFrame.query过滤size介于size_minsize_max之间的行。
但这会导致行爆炸,因此在您的示例中为12000*5000 = 60 000 000行。

dfn = (
    df_products.assign(key=1)
      .merge(df_types.assign(key=1), on='key')
      .query('size >= size_min & size < size_max')
      .drop(columns='key')
)

   id_product  size  size_min  size_max type
1           A     6         6        16    M
7           B    25        25        50   XL
9           C     7         6        16    M
12          D     2         1         5    S
19          F    45        25        50   XL
21          E    10         6        16    M
26          G    16        16        24    L

方法二:pd.IntervalIndex

如果没有重叠的范围,那么如果我们将 Dataframe df_types中的size_min 16更改为15,我们可以使用此方法,这不会导致行爆炸。

idx = pd.IntervalIndex.from_arrays(df_types['size_min'], df_types['size_max'], closed='both')
event = df_types.loc[idx.get_indexer(df_products['size']), 'type'].to_numpy()

df_products['type'] = event

  id_product  size type
0          A     6    M
1          B    25   XL
2          C     7    M
3          D     2    S
4          F    45   XL
5          E    10    M
6          G    16    L
4dc9hkyq

4dc9hkyq2#

这比Erfan的解决方案要长得多;我之所以提供这种方法,是因为我相信它可以帮助避免合并导致的行数增加。
这是在SQL查询中查找与where子句匹配的cond1和cond2。下一步压缩这两个列表并查找元素的索引(True,True)...获得的索引与df_types的索引等效。根据索引连接从df_types提取的所有 Dataframe ,并再次连接到df_products。
应该有比这更好的办法;然而,我确实相信SQL在这方面做得更好。

cond1 = df_products['size'].apply(lambda x: [x>=i for i in [*df_types.size_min.array]])

cond2 = df_products['size'].apply(lambda x: [x<i for i in [*df_types.size_max.array]])

t = [list(zip(i,j)).index((True,True))
     for i,j in zip(cond1.array,cond2.array)]

result = (pd.concat([df_types.iloc[[i]]
                     for i in t])
          .filter(['type'])
          .reset_index(drop=True))

outcome = (pd.concat([df_products,result],
           axis=1,
           ignore_index=True,
           join='outer'))

outcome.columns = ['id_product', 'size', 'type']

    id_product  size    type
0   A   6   M
1   B   25  XL
2   C   7   M
3   D   2   S
4   F   45  XL
5   E   10  M
6   G   16  L

更新

随着时间的推移,希望我们能做得更好,我又试了一次,将事务转移到vanilla Python中,然后将最终结果返回给Pandas:

from itertools import product
test = [(id_product,first,last)
        for (id_product,first), (second, third,last)
        in product(zip(df_products.id_product,df_products['size']),
                   df_types.to_numpy()
                  )
        if second <= first <= third
       ]

test

[('A', 6, 'M'),
 ('B', 25, 'XL'),
 ('C', 7, 'M'),
 ('D', 2, 'S'),
 ('F', 45, 'XL'),
 ('E', 10, 'M'),
 ('G', 16, 'M'),
 ('G', 16, 'L')]

获取Pandas Dataframe :

pd.DataFrame(test,columns=['id_product', 'size', 'type'])
    id_product  size    type
0      A         6       M
1      B        25       XL
2      C        7        M
3      D        2        S
4      F        45       XL
5      E        10       M
6      G        16       M
7      G        16       L

注意,最后一项'G'返回两行,因为它基于条件匹配这两行。

2023年更新

将来自pyjanitor的conditional_join用于非对等连接:

# pip install pyjanitor
import pandas as pd

(df_products
.conditional_join(
    df_types, 
    # column from left, column from right, comparator
    ('size', 'size_min', '>='), 
    ('size', 'size_max', '<='), 
    # depending on the data size, 
    # you could get better performance
    # by using numba, if it is installed
    use_numba=False,
    right_columns='type')
) 
  id_product  size type
0          A     6    M
1          B    25   XL
2          C     7    M
3          D     2    S
4          F    45   XL
5          E    10    M
6          G    16    M
7          G    16    L

相关问题