如何对具有偏移量的向量应用操作
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_end
是 pd.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
考虑以下 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_end
是 pd.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