方法#1
这是一个基于降维的内存效率和np.searchsorted 用于追溯并在两个用户数据之间寻找匹配的数据 -
# Extract array data for efficiency, as we will work NumPy tools
a = df.to_numpy(copy=False) #Pandas >= 0.24, use df.values otherwise
i = a[:,:3].astype(int)
j = a[:,3:].astype(bool)
# Test out without astype(int),astype(bool) conversions and see how they perform
# Get grouped scalars for Day and place headers combined
# This assumes that Day and Place data are positive integers
g = i[:,2]*(i[:,1].max()+1) + i[:,1]
# Get groups for user1,2 for original and grouped-scalar items
m1 = i[:,0]==1
uj1,uj2 = j[m1],j[~m1]
ui1 = i[m1]
u1,u2 = g[m1],g[~m1]
# Use searchsorted to look for matching ones between user-1,2 grouped scalars
su1 = u1.argsort()
ssu1_idx = np.searchsorted(u1,u2,sorter=su1)
ssu1_idx[ssu1_idx==len(u1)] = 0
ssu1_idxc = su1[ssu1_idx]
match_mask = u1[ssu1_idxc]==u2
match_idx = ssu1_idxc[match_mask]
# Select matching items off original table
p1,p2 = uj1[match_idx],uj2[match_mask]
# Setup output arrays
day_place = ui1[match_idx,1:]
user1_bools = p1
user2_bools = p2
方法 #1-扩展:通用 Day 和 Place dtype 数据
当Day 和Place 数据可能不一定是正整数时,我们可以扩展到通用情况。在这种情况下,我们可以利用 dtype-combined 基于视图的方法来执行数据缩减。因此,唯一需要改变的就是以不同的方式获得g,这将是一个基于视图的数组类型,并且会像这样获得 -
# https://stackoverflow.com/a/44999009/ @Divakar
def view1D(a): # a is array
a = np.ascontiguousarray(a)
void_dt = np.dtype((np.void, a.dtype.itemsize * a.shape[1]))
return a.view(void_dt).ravel()
# Get grouped scalars for Day and place headers combined with dtype combined view
g = view1D(i[:,1:])
方法 #2
我们将使用lex-sorting 对数据进行分组,以便在连续行中查找相同的元素会告诉我们两个用户之间是否存在匹配的元素。我们将从Approach#1 重新使用a,i,j。实施将是 -
# Lexsort the i table
sidx = np.lexsort(i.T)
# OR sidx = i.dot(np.r_[1,i[:,:-1].max(0)+1].cumprod()).argsort()
b = i[sidx]
# Get matching conditions on consecutive rows
m = (np.diff(b,axis=0)==[1,0,0]).all(1)
# Or m = (b[:-1,1] == b[1:,1]) & (b[:-1,2] == b[1:,2]) & (np.diff(b[:,0])==1)
# Trace back to original order by using sidx
match1_idx,match2_idx = sidx[:-1][m],sidx[1:][m]
# Index into relevant table and get desired array outputs
day_place,user1_bools,user2_bools = i[match1_idx,1:],j[match1_idx],j[match2_idx]
或者,我们可以使用m 的扩展掩码来索引sidx 并生成match1_idx,match2_idx。其余代码保持不变。因此,我们可以做 -
from scipy.ndimage import binary_dilation
# Binary extend the mask to have the same length as the input.
# Index into sidx with it. Use one-off offset and stepsize of 2 to get
# user1,2 matching indices
m_ext = binary_dilation(np.r_[m,False],np.ones(2,dtype=bool),origin=-1)
match_idxs = sidx[m_ext]
match1_idx,match2_idx = match_idxs[::2],match_idxs[1::2]
方法#3
这是另一个基于Approach #2 并移植到numba 的内存和性能。效率,我们将从approach #1重用a,i,j -
from numba import njit
@njit
def find_groups_numba(i_s,j_s,user_data,bools):
n = len(i_s)
found_iterID = 0
for iterID in range(n-1):
if i_s[iterID,1] == i_s[iterID+1,1] and i_s[iterID,2] == i_s[iterID+1,2]:
bools[found_iterID,0] = j_s[iterID,0]
bools[found_iterID,1] = j_s[iterID,1]
bools[found_iterID,2] = j_s[iterID+1,0]
bools[found_iterID,3] = j_s[iterID+1,1]
user_data[found_iterID,0] = i_s[iterID,1]
user_data[found_iterID,1] = i_s[iterID,2]
found_iterID += 1
return found_iterID
# Lexsort the i table
sidx = np.lexsort(i.T)
# OR sidx = i.dot(np.r_[1,i[:,:-1].max(0)+1].cumprod()).argsort()
i_s = i[sidx]
j_s = j[sidx]
n = len(i_s)
user_data = np.empty((n//2,2),dtype=i.dtype)
bools = np.empty((n//2,4),dtype=j.dtype)
found_iterID = find_groups_numba(i_s,j_s,user_data,bools)
out_bools = bools[:found_iterID] # Output bool
out_userd = user_data[:found_iterID] # Output user-Day, Place data
如果输出必须有自己的内存空间,则在最后两步附加 .copy()。
或者,我们可以将索引操作卸载到 NumPy 端以获得更简洁的解决方案 -
@njit
def find_consec_matching_group_indices(i_s,idx):
n = len(i_s)
found_iterID = 0
for iterID in range(n-1):
if i_s[iterID,1] == i_s[iterID+1,1] and i_s[iterID,2] == i_s[iterID+1,2]:
idx[found_iterID] = iterID
found_iterID += 1
return found_iterID
# Lexsort the i table
sidx = np.lexsort(i.T)
# OR sidx = i.dot(np.r_[1,i[:,:-1].max(0)+1].cumprod()).argsort()
i_s = i[sidx]
j_s = j[sidx]
idx = np.empty(len(i_s)//2,dtype=np.uint64)
found_iterID = find_consec_matching_group_indices(i_s,idx)
fidx = idx[:found_iterID]
day_place,user1_bools,user2_bools = i_s[fidx,1:],j_s[fidx],j_s[fidx+1]