如何对具有偏移量的向量应用操作

How to apply an operation on a vector with offsets

考虑以下 pd.DataFrame

import numpy as np
import pandas as pd

start_end = pd.DataFrame([[(0, 3), (4, 5), (6, 12)], [(7, 10), (11, 90), (91, 99)]])
values = np.random.rand(1, 99)

start_endpd.DataFrame 形状 (X, Y),其中每个值都是 values 向量中 (start_location, end_location) 的元组。另一种说法是特定单元格中的值是不同长度的向量。

问题

如果我想找到 pd.DataFrame 中每个单元格的矢量值的平均值(例如),我该如何以经济高效的方式做到这一点?

我设法通过 .apply 函数实现了这一点,但速度很慢。

我想我需要找到一些方法将其呈现在 numpy 数组中,然后将其映射回二维数据框,但我不知道如何做。

备注

泛化问题

更一般地说,我这是一个反复出现的问题,即如何制作 3d 数组,其中一个维度通过某些转换函数(均值、最小值等)与 2d 矩阵的长度不相等

前瞻性方法

查看您的示例数据:

In [64]: start_end
Out[64]: 
         0         1         2
0   (1, 6)    (4, 5)   (6, 12)
1  (7, 10)  (11, 12)  (13, 19)

确实每行不重叠,但不是整个数据集。

现在,我们有 np.ufunc.reduceat 可以减少每个切片的 ufunc :

ufunc(ar[indices[i]: indices[i + 1]])

只要indices[i] < indices[i+1].

所以,ufunc(ar, indices),我们会得到:

[ufunc(ar[indices[0]: indices[1]]), ufunc(ar[indices[1]: indices[2]]), ..]

在我们的例子中,对于每个元组 (x,y),我们知道 x<y。对于堆叠版本,我们有:

[(x1,y1), (x2,y2), (x3,y3), ...]

如果我们展平,那就是:

[x1,y1,x2,y2,x3,y3, ...]

所以,我们可能没有 y1<x2,但这没关系,因为我们不需要为那个和类似的对:y2,x3 减少 ufunc。但这没关系,因为可以通过对最终输出进行步长切片来跳过它们。

因此,我们将有:

# Inputs : a (1D array), start_end (2D array of shape (N,2))
lens = start_end[:,1]-start_end[:,0]
out = np.add.reduceat(a, start_end.ravel())[::2]/lens

np.add.reduceat() 部分为我们提供了切片求和。我们需要除以 lens 来进行平均计算。

样本运行-

In [47]: a
Out[47]: 
array([0.49264042, 0.00506412, 0.61419663, 0.77596769, 0.50721381,
       0.76943416, 0.83570173, 0.2085408 , 0.38992344, 0.64348176,
       0.3168665 , 0.78276451, 0.03779647, 0.33456905, 0.93971763,
       0.49663649, 0.4060438 , 0.8711461 , 0.27630025, 0.17129342])

In [48]: start_end
Out[48]: 
array([[ 1,  3],
       [ 4,  5],
       [ 6, 12],
       [ 7, 10],
       [11, 12],
       [13, 19]])

In [49]: [np.mean(a[i:j]) for (i,j) in start_end]
Out[49]: 
[0.30963037472653104,
 0.5072138121177008,
 0.5295464559328862,
 0.41398199978967815,
 0.7827645134019902,
 0.5540688880441684]

In [50]: lens = start_end[:,1]-start_end[:,0]
    ...: out = np.add.reduceat(a, start_end.ravel())[::2]/lens

In [51]: out
Out[51]: 
array([0.30963037, 0.50721381, 0.52954646, 0.413982  , 0.78276451,
       0.55406889])

为了完整性,参考给定的样本,转换步骤是:

# Given start_end as df and values as a 2D array
start_end = np.vstack(np.concatenate(start_end.values)) 
a = values.ravel()  

对于其他具有 reduceat 方法的 ufunc,我们将只替换 np.add.reduceat

对于您的情况的计算均值,您永远不会像首先使用 numpy.cumsum 预先计算累积和那样快。查看以下代码:

import numpy as np
import pandas as pd
import time

R = 1_000
C = 10_000
M = 100

# Generation of test case
start = np.random.randint(0, M-1, (R*C,1))
end = np.random.randint(0, M-1, (R*C,1))
start = np.where(np.logical_and(start>=end, end>1), end-1, start)
end = np.where(np.logical_and(start>=end, start<M-1), start+1, end)
start_end = np.hstack((start, end))

values = np.random.rand(M)

t_start = time.time()
# Basic mean dataframe
lens = start_end[:,1]-start_end[:,0]
mean = np.add.reduceat(values, start_end.ravel())[::2]/lens
print('Timre 1:', time.time()-t_start, 's')

t_start = time.time()
#Cumulative sum
cum_values = np.zeros((values.size+1,))
cum_values[1:] = np.cumsum(values)
# Compute mean dataframe
mean_2 = (cum_values[start_end[:,1]]-cum_values[start_end[:,0]])/(start_end[:,1]-start_end[:,0])
print('Timre 2:', time.time()-t_start, 's')

print('Results are equal!' if np.allclose(mean, mean_2) else 'Results differ!')
print('Norm of the difference:', np.linalg.norm(mean - mean_2))

输出:

% python3 script.py
Timre 1: 0.48940515518188477 s
Timre 2: 0.16983389854431152 s
Results are equal!
Norm of the difference: 2.545241707481022e-12

M 增加时,性能差异会变得更糟。 M=5000 你得到:

% python3 script.py
Timre 1: 4.5356669425964355 s
Timre 2: 0.1772768497467041 s
Results are equal!
Norm of the difference: 1.0660592585125616e-10