【问题标题】:How to apply an operation on a vector with offsets如何对具有偏移的向量应用操作
【发布时间】:2020-07-08 00:01:25
【问题描述】:

考虑以下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 数组中,然后将其映射回 2d 数据帧,但我不知道如何。

备注

  • 起点与终点之间的距离可能不同,可能存在异常值。
  • 单元格开始/结束始终与其他单元格不重叠(看看这个先决条件是否会影响求解速度会很有趣)。

广义问题

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

【问题讨论】:

  • 你的数据有多大?
  • 通常它大约是 10,000 x 100,000(start_end),向量大约是 10,000,000 如果你认为 - 向量太小,这是因为 start_end 通常有负元组或 nan 值(即此单元格不包含 values 向量中的信息)
  • start_end 不应该是 (N,2) 形状的二维数组吗?
  • 对于start_end 中的每一行,单元格是否不重叠且按递增顺序排列?
  • @Divakar 我更新了描述。也许(N, 2) 将是一个转换步骤后的状态,只要它可以映射回原始大小的二维矩阵。

标签: pandas numpy offset


【解决方案1】:

前瞻性方法

查看您的示例数据:

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,但这没关系,因为我们不需要 ufunc 减少那个,同样的对:y2,x3。但这没关系,因为可以通过对最终输出进行步长切片来跳过它们。

因此,我们会:

# 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

【讨论】:

  • 能否请您显示从pd.DataFrame(N, 2) 数组的转换步骤?我与这部分斗争。
  • @Newskooler 在帖子中添加。
  • @Newskooler 你有values = np.random.rand(1, 99)。所以它不是一个向量,而是一个二维数组。我们需要一个一维数组进行处理,因此使用.ravel() 进行展平。如果需要矢量 (1D),则可以跳过 ravel。
  • @Newskooler 请注意,我将第一对更改为 [ 1, 3] 以增加多样性。
  • @Newskooler 唯一不跳过的方法是当一个切片的结尾与下一个切片的开头相同时。然后,我们可以直接使用np.add.reduceat(a, start_end[:,0])
【解决方案2】:

在你的情况下计算平均值,你永远不会像你首先使用 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

【讨论】:

  • 是的!在计算复杂度为 O(N) 的 cumsum 之后,可以用 O(1) 计算所有其他均值。它非常强大,尤其是当您必须计算滚动窗口分析时。你甚至可以使用这个技巧来计算O(1) 中的标准差或相关性。
  • 虽然数值可能不稳定。
  • 你的意思是如果你从 cumsum 得到溢出?
  • 即使在此之前,如果您的增量与运行总数相比很小,您可能会丢失大量位:示例,只是为了说明原理a=np.array([2.0**40,1.2345678])np.diff(a.cumsum()) yields array([1.23461914])` .
  • 您实际上可以通过删除每个元素 values---=np.mean(values) 的全局向量平均值,然后将此值添加到您计算的每个平均值来避免 cumsum 变得太高。这将确保 cumsum 以0 结尾而不是一个大值。当然,需要了解有关数据集的更多信息才能知道是否会发生此问题。但就计算时间而言,它是飞涨的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多