我有一只大熊猫。正数系列。我需要找到“异常值”的索引,其值离开前一个“范数”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
请注意,10
之后的14
值不会返回,因为它们“恢复正常”值。
编辑:
abs()
。数字是积极的。答案 0 :(得分:7)
这是我原来的“矢量化”解决方案:
您可以使用shift和numpy的where获取last_valid
:
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实现(参见cython section of pandas docs),它比原生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 (enhancing performance) section。
答案 1 :(得分:2)
我尝试解决问题的方法是将其表示为last_valid的循环表达式,然后按照Andy Hayden的说法进行操作。 last_valid的表达式为:
lv[i] = lv[i-1] if diff >= 3 else v[i]
,其中
diff = abs(v[i] - lv[i-1]))
其中i是迭代次数。要在DataFrame中实现这一点,我使用的是来自ctype的指针。 通过在指向列lv(last_valid)的指针移动列上取消引用,可以在下一个迭代步骤中访问计算得到的值。
from ctypes import *
import pandas as pd
df = pd.DataFrame({'s_num': [7,8,9,10,14,10,10,14,100,14,10]})
df['s'] = df['s_num'].apply(c_long)
df['lv'] = [c_long(0) for x in range(len(df))]
df.ix[0, 'lv'] = df.ix[0, 's']
df['p_lv'] = df['lv'].apply(pointer)
df['p_lv_m1'] = df['p_lv'].shift()
def ff(x):
if not pd.isnull(x['p_lv_m1']):
diff = abs(x['s'].value - x['p_lv_m1'].contents.value)
x['p_lv'][0] = x['s'] if diff < 3 else x['p_lv_m1'][0]
return None
df.apply(ff, 1)
df['lv_num'] = df['lv'].apply(lambda x: x.value)
df = df[['s_num', 'lv_num']]
print df.ix[(df['s_num'] - df['lv_num']).abs() >= 3, 's_num']