Python中类似于R的match()用于索引的等效方式

6

我希望在Python中使用Pandas数据框实现R中match()函数的相当功能,而不使用for循环。

在R中,match()返回第一个参数在第二个参数中(第一次)匹配位置的向量。

假设我有两个数据框A和B,它们都包含列C。其中

A$C = c('a','b')
B$C = c('c','c','b','b','c','b','a','a')

在R中,我们将获得以下结果:
match(A$C,B$C) = c(7,3)

在Python中,有没有一种与pandas数据框的列相当的方法,而不需要循环遍历值。

5个回答

6

这里有一个一行代码

B.reset_index().groupby('C')['index'].first()[A.C].values

这个解决方案以与输入A相同的顺序返回结果,就像R中的match一样。


完整示例:

import pandas as pd

A = pd.DataFrame({'C':['a','b']})
B = pd.DataFrame({'C':['c','c','b','b','c','b','a','a']})

B.reset_index().groupby('C')['index'].first()[A.C].values

Output array([6, 2])

编辑(2023-04-12):在较新版本的pandas中,.loc匹配所有符合条件的行。因此,之前的解决方案(B.reset_index().set_index('c').loc[A.c, 'index'].values)将返回所有匹配项,而不仅仅是第一个。


遗憾的是,截至2023年,对于大向量(例如1e7),这比R的match()函数慢得多,几乎可以视为犯罪。Python似乎没有真正等效于R的match()函数的实现,后者已经在C中进行了优化。 - user2961927
1
如果您对性能感兴趣,Python 有很多选择:https://dev59.com/TGsz5IYBdhLWcg3w0rZC。我已更新答案,因为自我最初发布以来 pandas 已经发展了。 - toto_tico
谢谢!编写了4种方法。最快的仍然比R的match()函数慢1.5倍:https://dev59.com/U5vga4cB1Zd3GeqP0lqc#76030311 - user2961927

4
您可以先使用drop_duplicates,然后使用布尔索引isin合并。Python从0开始计数,所以输出结果时需要加上1
A = pd.DataFrame({'c':['a','b']})
B = pd.DataFrame({'c':['c','c','b','b','c','b','a','a']})


B = B.drop_duplicates('c')
print (B)
   c
0  c
2  b
6  a

print (B[B.c.isin(A.c)])
   c
2  b
6  a

print (B[B.c.isin(A.c)].index)
Int64Index([2, 6], dtype='int64')

print (pd.merge(B.reset_index(), A))
   index  c
0      2  b
1      6  a

print (pd.merge(B.reset_index(), A)['index'])
0    2
1    6
Name: index, dtype: int64

2
请注意,这里的结果以不同的顺序返回。在 R 中,match 的输出顺序对应于输入:('a', 'b') 对应于 c(7,3),但是这里的答案首先返回了 b 的结果,然后是 a。请参阅我的答案,其中提供了一种保持顺序的单行选项。 - toto_tico

2
这将返回所有匹配的索引(使用Python的从0开始的索引):
import pandas as pd

df1 = pd.DataFrame({'C': ['a','b']})
print df1

   C
0  a
1  b

df2 = pd.DataFrame({'C': ['c','c','b','b','c','b','a','a']})
print df2   

   C
0  c
1  c
2  b
3  b
4  c
5  b
6  a
7  a

match = df2['C'].isin(df1['C'])
print [i for i in range(match.shape[0]) if match[i]]

#[2, 3, 5, 6, 7]

1

这里有多种实现方法。在不使用 C 或 C++ 的情况下,最快的方法似乎是使用 datatable

def match(x, y, method = "dt"):
  '''
  x and y are two numpy 1d arrays containing only finite values.  
  
  method = 'dt': use datatable
  method = 'pandas': use pandas
  method = 'numpy': use numpy
  method = 'dict': use hashing.
  '''
  if method == 'dt': # Use datatable
    xdf = datatable.Frame({'val': x})
    ydf = datatable.Frame({'val': y, 'ind': np.arange(y.shape[0]) })[
      :, datatable.min(datatable.f.ind), datatable.by(datatable.f.val)]
    ydf.key = 'val'
    rst = xdf[:, :, datatable.join(ydf)]['ind'].to_numpy()
    return rst.filled(-1 - y.shape[0]).ravel()
  
  
  if method == 'pandas': # Use pandas dataframe.
    xdf = pd.DataFrame({'val': x})
    ydf = pd.DataFrame({'val': y, 'ind': np.arange(y.shape[0]) }).groupby(
      ['val']).min()
    joined = xdf.join(ydf, on = 'val', lsuffix = '_x', rsuffix = '_y')
    rst = joined['ind'].to_numpy()
    rst[np.isnan(rst)] = -1 - y.shape[0]
    return rst.astype(int)
  
  
  rst = np.zeros(x.shape[0], dtype = np.int32) - (y.shape[0] + 1)
  if method == 'numpy':
    yorder = y.argsort()
    ysorted = y[yorder]
    ind = np.searchsorted(ysorted, x)
    outofBound = ind >= y.shape[0]
    ind[outofBound] = 0
    eq = ysorted[ind] == x
    eq[outofBound] = False
    rst[eq] = yorder[ind][eq]

  else: # Hashing.

    D = dict(zip(y[::-1], np.arange(y.shape[0] - 1, -1, -1)))
    for i, u in enumerate(x):
      val = D.get(u)
      if val is not None: rst[i] = val
  return rst

测试代码:

  import datatable
  import pandas
  import time
  import numpy as np
  
  
  N = int(1e9)
  k = int(1e7)
  x = np.random.choice(N, k)
  y = np.random.choice(N, k)
  timeCosts = {}
  
  
  st = time.time()
  ind = match(x, y, "dt")
  timeCosts['datatable'] = time.time() - st
  np.all(x[ind >= 0] == y[ind[ind >= 0]])
  
  
  st = time.time()
  ind = match(x, y, "pandas")
  timeCosts['pandas'] = time.time() - st
  np.all(x[ind >= 0] == y[ind[ind >= 0]])
  
  
  st = time.time()
  ind = match(x, y, "numpy")
  timeCosts['numpy'] = time.time() - st
  np.all(x[ind >= 0] == y[ind[ind >= 0]])
  
  
  st = time.time()
  ind = match(x, y, "hashing")
  timeCosts['hashing'] = time.time() - st
  np.all(x[ind >= 0] == y[ind[ind >= 0]])

时间花费(秒): {'datatable': 1.55, 'pandas': 8.01, 'numpy': 14.91, 'hashing': 6.04}

但最快的仍然比R的match慢:1.05s

R一定使用了类似于基数排序中的哈希技术..


0
我定义了一个Python匹配函数,它可以接受列表或1D np数组作为参数,并将其与Pandas数据框的列一起使用。
import numpy as np
import pandas as pd 

    
def match(needles,haystack): # parameters are python lists or 1 dimensional np arrays.
    # Copyleft 2023 David A. Kra  Creative Commons License: BY-SA Attribution-ShareAlike 
    # Derived from method3 in https://www.statology.org/numpy-find-index-of-value/ 
    # which is Copyright 2021 Zach Bobbitt# match(needles,haystack) returns indices to use
        #find index location of first occurrence of each value of interest
        # returns an np 1 dimensional array of indices. notfound values get None
        # tresult=np.array(needles.size*[2*1024*1024*1024], dtype='i4', copy=True,) # dtype 'intp' is for indexing, as an alternative to 'i4' or 'int32'
        sorter = np.argsort( nphaystack:=np.array(haystack) )
        tresult=sorter[np.searchsorted(nphaystack, (npneedles:=np.array(needles)), sorter=sorter)]
            # if a needle value is greater than any value in in the haystack, will throw an IndexError, such as 
            #   IndexError: index 6 is out of bounds for axis 0 with size 6
        # If a needle is missing from the haystack, the index returned is of the next higher needle that is in the haystack. 
        #    Instead, turn it into None.
        return [tresult[i]  if nphaystack[ tresult[i] ] == npneedles[i] else None for i in range(tresult.size) ]

# usage:

npHaystackID=pdHaystack['ID_BUC'].to_numpy()  
npNeedlesID=pdNeedles['ID_BUC'].to_numpy() 

rowsOfNeedlesInBUC=match(npNeedlesID,npHaystackID)

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