我有一只大熊猫.正数系列.我需要找到"异常值"的索引,其值离开3
前一个"规范"或更多.
如何向量化此功能:
def baseline(s): values = [] indexes = [] last_valid = s.iloc[0] for idx, val in s.iteritems(): if abs(val - last_valid) >= 3: values.append(val) indexes.append(idx) else: last_valid = val return pd.Series(values, index=indexes)
例如,如果输入是:
import pandas as pd s = pd.Series([7,8,9,10,14,10,10,14,100,14,10]) print baseline(s)
所需的输出是:
4 14 7 14 8 100 9 14
请注意,s 10
之后的值14
不会返回,因为它们是"恢复正常"值.
编辑:
添加abs()
到代码中.数字是积极的.
这里的目的是加速代码.
一个不完全模仿代码的答案可能是可以接受的.
更改示例以包含另一个边缘大小写,其中值缓慢变化3.
Andy Hayden.. 7
这是我原来的"矢量化"解决方案:
你可以获得last_valid
使用shift和numpy的位置:
In [1]: s = pd.Series([10, 10, 10, 14, 10, 10, 10, 14, 100, 14, 10]) In [2]: last_valid = pd.Series(np.where((s - s.shift()).abs() < 3, s, np.nan)) last_valid.iloc[0] = s.iloc[0] # initialize with first value of s last_valid.ffill(inplace=True) In [3]: last_valid Out[3]: 0 7 1 8 2 9 3 10 4 10 5 10 6 10 7 10 8 10 9 10 10 10 dtype: float64
这使问题更容易.你可以将它与s
:
In [4]: s - last_valid # alternatively use (s - last_valid).abs() Out[4]: 0 0 1 0 2 0 3 0 4 4 5 0 6 0 7 4 8 90 9 4 10 0 dtype: float64
那些差异超过+3的元素:
In [5]: (s - last_valid).abs() >= 3 Out[5]: 0 False 1 False 2 False 3 False 4 True 5 False 6 False 7 True 8 True 9 True 10 False dtype: bool In [6]: s[(s - last_valid).abs() >= 3] Out[6]: 4 14 7 14 8 100 9 14 dtype: int64
如预期的.......或者看起来如此,@ alko的例子表明这不太正确.
正如@alko指出的那样,下面的矢量化方法并不完全正确,特别是对于这个例子s = pd.Series([10, 14, 11, 10, 10, 12, 14, 100, 100, 14, 10])
,我的"矢量化"方法包括第二个100作为"不是异常值",即使它在基线中.
这导致我(以及@alko)认为这不能被矢量化.作为替代方案,我已经包含了一个简单的cython实现(参见pandas docs的cython部分),它比原生python快得多:
%%cython cimport numpy as np import numpy as np cimport cython @cython.wraparound(False) @cython.boundscheck(False) cpdef _outliers(np.ndarray[double] s): cdef np.ndarray[Py_ssize_t] indexes cdef np.ndarray[double] vals cdef double last, val cdef Py_ssize_t count indexes = np.empty(len(s), dtype='int') vals = np.empty(len(s)) last = s[0] count = 0 for idx, val in enumerate(s): if abs(val - last) >= 3: indexes[count] = idx vals[count] = val count += 1 else: last = val return vals[:count], indexes[:count] def outliers(s): return pd.Series(*_outliers(s.values.astype('float')))
时间的一些指示:
In [11]: s = pd.Series([10,10,12,14,100,100,14,10]) In [12]: %timeit baseline(s) 10000 loops, best of 3: 132 µs per loop In [13]: %timeit outliers(s) 10000 loops, best of 3: 46.8 µs per loop In [21]: s = pd.Series(np.random.randint(0, 100, 100000)) In [22]: %timeit baseline(s) 10 loops, best of 3: 161 ms per loop In [23]: %timeit outliers(s) 100 loops, best of 3: 9.43 ms per loop
有关更多信息,请参阅pandas docs 的cython(增强性能)部分.