首页 > 解决方案 > 在每行包含一个时间序列的数据框中查找相对和绝对波动的计数

问题描述

我有一个包含金融时间序列表的数据框,每一行都有以下列:

我们想要计算每行/ID 的时间序列的相对和绝对波动计数。然后我们要找出哪一行/ID的波动/'spikes'最多,如下

代码:

import pandas as pd 

# Create sample dataframe
raw_data = {'ID': ['A1', 'B1', 'C1', 'D1'], 
  'Domain': ['Finance', 'IT', 'IT', 'Finance'], 
  'Target': [1, 2, 3, 0.9%], 
  'Criteria':['<=', '<=', '>=', '>='],
  "1/01":[0.9, 1.1, 2.1, 1],
  "1/02":[0.4, 0.3, 0.5, 0.9], 
  "1/03":[1, 1, 4, 1.1], 
  "1/04":[0.7, 0.7, 0.1, 0.7],
  "1/05":[0.7, 0.7, 0.1, 1], 
  "1/06":[0.9, 1.1, 2.1, 0.6],}

df = pd.DataFrame(raw_data, columns = ['ID', 'Domain', 'Target','Criteria', '1/01', 
  '1/02','1/03', '1/04','1/05', '1/06'])

   ID   Domain  Target Criteria  1/01  1/02  1/03  1/04  1/05  1/06  
0  A1  Finance       1       <=   0.9   0.4   1.0   0.7   0.7   0.9  
1  B1       IT       2       <=   1.1   0.3   1.0   0.7   0.7   1.1  
2  C1       IT       3       >=   2.1   0.5   4.0   0.1   0.1   2.1  
3  D1  Finance     0.9%      >=   1.0   0.9   1.1   0.7   1.0   0.6

这是带有波动计数 ( FCount) 列的预期输出。然后我们可以得到具有最大 FCount 的 ID。

   ID   Domain  Target Criteria  1/01  1/02  1/03  1/04  1/05  1/06  FCount
0  A1  Finance       1       <=   0.9   0.4   1.0   0.7   0.7   0.9    -
1  B1       IT       2       <=   1.1   0.3   1.0   0.7   0.7   1.1    - 
2  C1       IT       3       >=   2.1   0.5   4.0   0.1   0.1   2.1    - 
3  D1  Finance     0.9%      >=   1.0   0.9   1.1   0.7   1.0   0.6    -

标签: pythonpandastime-series

解决方案


鉴于,

# importing pandas as pd 
import pandas as pd
import numpy as np

# Create sample dataframe
raw_data = {'ID': ['A1', 'B1', 'C1', 'D1'], 
'Domain': ['Finance', 'IT', 'IT', 'Finance'], 
'Target': [1, 2, 3, '0.9%'],
'Criteria':['<=', '<=', '>=', '>='],
"1/01":[0.9, 1.1, 2.1, 1],
"1/02":[0.4, 0.3, 0.5, 0.9], 
"1/03":[1, 1, 4, 1.1], 
"1/04":[0.7, 0.7, 0.1, 0.7],
"1/05":[0.7, 0.7, 0.1, 1], 
"1/06":[0.9, 1.1, 2.1, 0.6],}



df = pd.DataFrame(raw_data, columns = ['ID', 'Domain', 'Target','Criteria', '1/01', 
'1/02','1/03', '1/04','1/05', '1/06'])

通过将其分为两部分(绝对阈值和相对阈值)并在底层 numpy 数组上逐步解决此问题,更容易解决此问题。


编辑:前面的解释很长,跳到最后只是最后的功能

首先,创建日期列列表以仅访问每行中的相关列。

date_columns = ['1/01', '1/02','1/03', '1/04','1/05', '1/06']
df[date_columns].values
#Output:
array([[0.9, 0.4, 1. , 0.7, 0.7, 0.9],
       [1.1, 0.3, 1. , 0.7, 0.7, 1.1],
       [2.1, 0.5, 4. , 0.1, 0.1, 2.1],
       [1. , 0.9, 1.1, 0.7, 1. , 0.6]])

然后我们可以使用np.diff轻松获取底层数组上日期之间的差异。我们也将采用绝对值,因为这是我们感兴趣的。

np.abs(np.diff(df[date_columns].values))
#Output:
array([[0.5, 0.6, 0.3, 0. , 0.2],
       [0.8, 0.7, 0.3, 0. , 0.4],
       [1.6, 3.5, 3.9, 0. , 2. ],
       [0.1, 0.2, 0.4, 0.3, 0.4]])

现在,只需担心绝对阈值,就像检查差异中的值是否大于限制一样简单。

abs_threshold = 0.5
np.abs(np.diff(df[date_columns].values)) > abs_threshold
#Output:
array([[False,  True, False, False, False],
       [ True,  True, False, False, False],
       [ True,  True,  True, False,  True],
       [False, False, False, False, False]])

我们可以看到,该数组每行的总和将为我们提供所需的结果(布尔数组的总和使用底层 True=1 和 False=0。因此,您实际上是在计算存在多少 True)。对于百分比阈值,我们只需要做一个额外的步骤,在比较之前将所有差异与原始值相除。把它们放在一起。

详细说明:

我们可以看到每一行的总和如何为我们提供跨越绝对阈值的值的计数,如下所示。

abs_fluctuations = np.abs(np.diff(df[date_columns].values)) > abs_threshold
print(abs_fluctuations.sum(-1))
#Output:
[1 2 4 0]

从相对阈值开始,我们可以像以前一样创建差异数组。

dates = df[date_columns].values #same as before, but just assigned
differences = np.abs(np.diff(dates)) #same as before, just assigned
pct_threshold=0.5 #aka 50%
print(differences.shape) #(4, 5) aka 4 rows, 5 columns if you want to think traditional tabular 2D shapes only
print(dates.shape) #(4, 6) 4 rows, 6 columns

现在,请注意,差异数组的列数将减少 1,这也是有道理的。因为对于 6 个日期,将有 5 个“差异”,每个间隔一个。

现在,只关注 1 行,我们看到计算百分比变化很简单。

print(dates[0][:2]) #for first row[0], take the first two dates[:2]
#Output:
array([0.9, 0.4])
print(differences[0][0]) #for first row[0], take the first difference[0]
#Output:
0.5

变化0.9 to 0.40.5绝对意义上的变化。但就百分比而言,它是0.5/0.9(差异/原始)* 100 的变化(我省略了乘以 100 以使事情更简单)又名55.555%0.5555..

在这一步要意识到的主要事情是,我们需要针对所有差异的“原始”值进行这种划分,以获得百分比变化。但是,日期数组的一个“列”太多了。所以,我们做一个简单的切片。

dates[:,:-1] #For all rows(:,), take all columns except the last one(:-1).
#Output:
array([[0.9, 0.4, 1. , 0.7, 0.7],
       [1.1, 0.3, 1. , 0.7, 0.7],
       [2.1, 0.5, 4. , 0.1, 0.1],
       [1. , 0.9, 1.1, 0.7, 1. ]])

现在,我可以通过元素除法计算相对或百分比变化

relative_differences = differences / dates[:,:-1]

然后,和以前一样。选择一个门槛,看看它是否被跨越

rel_fluctuations = relative_differences > pct_threshold
#Output:
array([[ True,  True, False, False, False],
       [ True,  True, False, False,  True],
       [ True,  True,  True, False,  True],
       [False, False, False, False, False]])

现在,如果我们想考虑是否超过了绝对阈值或相对阈值之一我们只需要按位或|(它甚至在句子中!)然后沿行求和。

把所有这些放在一起,我们可以创建一个可以使用的函数。请注意,函数并没有什么特别之处,它只是一种将代码行组合在一起以便于使用的方式。使用函数就像调用它一样简单,您一直在使用函数/方法而没有意识到它。


date_columns = ['1/01', '1/02','1/03', '1/04','1/05', '1/06'] #if hardcoded.
date_columns = df.columns[5:] #if you wish to assign dynamically, and all dates start from 5th column.

def get_FCount(df, date_columns, abs_threshold=0.5, pct_threshold=0.5):
    '''Expects a list of date columns with atleast two values.
        returns a 1D array, with FCounts for every row.
        pct_threshold: percentage, where 1 means 100%
    '''
    dates = df[date_columns].values
    differences = np.abs(np.diff(dates))
    abs_fluctuations = differences > abs_threshold
    rel_fluctuations = differences / dates[:,:-1] > pct_threshold
    return (abs_fluctuations | rel_fluctuations).sum(-1) #we took a bitwise OR. since we are concerned with values that cross even one of the thresholds.

df['FCount'] = get_FCount(df, date_columns) #call our function, and assign the result array to a new column
print(df['FCount'])
#Output:
0    2
1    3
2    4
3    0
Name: FCount, dtype: int32

推荐阅读