我正在尝试创建一个程序,该程序查找满足某些条件的连续行。例如,如果有一个看起来像这样的数据框:
df = pd.DataFrame([1,1,2,-13,-4,-5,6,17,8,9,-10,-11,-12,-13,14,15],
index=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],
columns=['value'])
>>> df
value
0 1
1 1
2 2
3 -13
4 -4
5 -5
6 6
7 17
8 8
9 9
10 -10
11 -11
12 -12
13 -13
14 -14
15 15
我希望它返回一个数据框,该数据框显示满足以下条件的行:
1)顺序必须为(positive rows)
和(negative rows)
,而不是相反。
2)每组正向或负向行必须至少具有3行
3)正负基团必须彼此相邻
posIdx, negIdx, posLength, negLength
0 2 3 3 3 # (1,1,2) (-13,-4,-5)
1 9 10 4 5 # (6,17,8,9) (-10,-11,-12,-13,-14)
是否有使用python或pandas命令执行此操作的简单方法?
答案 0 :(得分:4)
我创建帮助程序列以方便验证解决方案:
#column for negative and positive
df['sign'] = np.where(df['value'] < 0, 'neg','pos')
#consecutive groups
df['g'] = df['sign'].ne(df['sign'].shift()).cumsum()
#removed groups with length more like 2
df = df[df['g'].map(df['g'].value_counts()).gt(2)]
#tested if order `pos-neg` of groups, if not removed groups
m1 = df['sign'].eq('pos') & df['sign'].shift(-1).eq('neg')
m2 = df['sign'].eq('neg') & df['sign'].shift().eq('pos')
groups = df.loc[m1 | m2, 'g']
df = df[df['g'].isin(groups)].copy()
df['pairs'] = (df['sign'].ne(df['sign'].shift()) & df['sign'].eq('pos')).cumsum()
print (df)
value sign g pairs
0 1 pos 1 1
1 1 pos 1 1
2 2 pos 1 1
3 -13 neg 2 1
4 -4 neg 2 1
5 -5 neg 2 1
6 6 pos 3 2
7 17 pos 3 2
8 8 pos 3 2
9 9 pos 3 2
10 -10 neg 4 2
11 -11 neg 4 2
12 -12 neg 4 2
13 -13 neg 4 2
最后汇总所有组的GroupBy.first
,并按GroupBy.size
进行计数,并命名聚合(pandas 0.25+),对列进行排序并展平MultiIndex,最后正确的Idx_pos
减去1
:
df1 = (df.reset_index()
.groupby(['pairs','g', 'sign'])
.agg(Idx=('index','first'), Length=('sign','size'))
.reset_index(level=1, drop=True)
.unstack()
.sort_index(axis=1, level=[0,1], ascending=[True, False])
)
df1.columns = df1.columns.map(lambda x: f'{x[0]}_{x[1]}')
df1['Idx_pos'] = df1['Idx_neg'] - 1
print (df1)
Idx_pos Idx_neg Length_pos Length_neg
pairs
1 2 3 3 3
2 9 10 4 4
答案 1 :(得分:0)
这只是一种替代方法,我没有对此速度进行基准测试
首先,创建一个“符号”列,指示数字是正数还是负数。
其次,还要创建一个“检查”列,以指示从正到负或从负到正的变化发生在哪一行。如果为-1,则表示从+ ve变为-ve;反之则意味着+1。
下一步,获取索引,其中check为-1(neg_ids)和+1(pos_ids)
我使用more-itertools中的函数来插入neg_ids和pos_ids。目的是获得完全为正或为负的那些行块。
下一阶段将运行一个for循环,该循环对结果变量中创建的每个元组使用iloc函数,并找出“值”列中的所有值是正还是负。根据符号,我们将结果分配给“ K”字典中的键。请注意,posIdx将是该块中的最后一行(对于整个正值),而对于negIdx,它将是负块中的第一行。 iloc执行一个开始:end-1,因此posIdx将是一个end-1,而对于negIdx,开始不需要任何加法或减法。
最后一个阶段是将数据读入数据框
df = pd.DataFrame([1,1,2,-13,-4,-5,6,17,8,9,-10,-11,-12,-13,-14,15],
index=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],
columns=['value'])
df['sign'] = np.where(df.value.lt(0),0,1)
df['check'] = df.sign.sub(df.sign.shift().fillna(0))
neg_ids = df.loc[df.check==-1].index.tolist()
pos_ids = df.loc[df.check==1].index.tolist()
from more_itertools import interleave_longest, windowed
outcome = list(interleave_longest(pos_ids,neg_ids))
outcome = list(windowed(outcome,2))
print(outcome)
[(0, 3), (3, 6), (6, 10), (10, 15)]
from collections import defaultdict
K = defaultdict(list)
for start, end in outcome:
checker = df.iloc[start:end,0]
if checker.ge(0).all() and checker.shape[0]>2:
K['posIdx'].append(end-1)
K['posLength'].append(checker.shape[0])
elif checker.lt(0).all() and checker.shape[0]>2:
K['negIdx'].append(start)
K['negLength'].append(checker.shape[0])
pd.DataFrame(K)
posIdx posLength negIdx negLength
0 2 3 3 3
1 9 4 10 5