在Pandas数据框中查找连续的片段

31

我有一个pandas.DataFrame,其中包含在连续时间点上进行的测量。除了每个测量值外,观测系统在每个时间点还具有不同的状态。因此,DataFrame还包含每个测量时系统的状态列。状态更改比测量间隔慢得多。因此,表示状态的列可能如下所示(索引:状态):

1:  3
2:  3
3:  3
4:  3
5:  4
6:  4
7:  4
8:  4
9:  1
10: 1
11: 1
12: 1
13: 1

有没有一种简单的方法来检索连续相等状态的每个段落的索引。这意味着我想要获得类似于以下内容:

[[1,2,3,4], [5,6,7,8], [9,10,11,12,13]]

结果可能不仅仅是普通列表。

到目前为止,我所能想到的唯一解决方案是手动迭代行,找到段落变化点,并从这些变化点重构索引,但我仍然希望有更简单的解决方案。

3个回答

50

一句话概括:

df.reset_index().groupby('A')['index'].apply(np.array)

示例代码:

In [1]: import numpy as np

In [2]: from pandas import *

In [3]: df = DataFrame([3]*4+[4]*4+[1]*4, columns=['A'])
In [4]: df
Out[4]:
    A
0   3
1   3
2   3
3   3
4   4
5   4
6   4
7   4
8   1
9   1
10  1
11  1

In [5]: df.reset_index().groupby('A')['index'].apply(np.array)
Out[5]:
A
1    [8, 9, 10, 11]
3      [0, 1, 2, 3]
4      [4, 5, 6, 7]

你也可以直接从groupby对象中访问信息:

In [1]: grp = df.groupby('A')

In [2]: grp.indices
Out[2]:
{1L: array([ 8,  9, 10, 11], dtype=int64),
 3L: array([0, 1, 2, 3], dtype=int64),
 4L: array([4, 5, 6, 7], dtype=int64)}

In [3]: grp.indices[3]
Out[3]: array([0, 1, 2, 3], dtype=int64)

为了解决DSM提到的情况,您可以尝试做以下事情:

In [1]: df['block'] = (df.A.shift(1) != df.A).astype(int).cumsum()

In [2]: df
Out[2]:
    A  block
0   3      1
1   3      1
2   3      1
3   3      1
4   4      2
5   4      2
6   4      2
7   4      2
8   1      3
9   1      3
10  1      3
11  1      3
12  3      4
13  3      4
14  3      4
15  3      4

现在按两列分组并应用lambda函数:

In [77]: df.reset_index().groupby(['A','block'])['index'].apply(np.array)
Out[77]:
A  block
1  3          [8, 9, 10, 11]
3  1            [0, 1, 2, 3]
   4        [12, 13, 14, 15]
4  2            [4, 5, 6, 7]

19
假设值不会在不连续的段中重复 - 例如,DataFrame([3]*4+[4]*4+[1]*4 + [3]*4, columns=['A'])将把两个3组放入同一组。你可以扫描这些来查找断点,但那只是原始问题的另一个版本。也许有一种方法可以让pandas的groupby在这里更像itertools.groupby。 - DSM
1
谢谢,你的第二个解决方案很有效。实际上,我遇到了DSM所描述的情况。 - languitar
如果您想按某个偏差(例如,组包含原始集合中相邻值的+-1内的所有值)进行分组,该如何实现? - ryanjdillon
@ryanjdillon,你可以尝试使用df['block'] = (df.A.diff(1).abs() > 1).cumsum()。但是这只检查相邻值之间的差异,所以如果你有一个DataFrame([3]*4+[4]*4+[5]*1+[6]*3, columns=['A']),它仍然只会产生一个单一的块。 - kekert
值得注意的是,实际上您不需要在那里使用 "astype(int)" -- pandas 完全可以对布尔值求和。 - MTKnife
本博客文章解释了其中的一个解决方案,链接为https://towardsdatascience.com/pandas-dataframe-group-by-consecutive-same-values-128913875dba - waithira

19
您可以使用np.diff()来测试段落的开始/结束位置并遍历这些结果。这是一个非常简单的解决方案,所以可能不是最高效的解决方案。
a = np.array([3,3,3,3,3,4,4,4,4,4,1,1,1,1,4,4,12,12,12])

prev = 0
splits = np.append(np.where(np.diff(a) != 0)[0],len(a)+1)+1

for split in splits:
    print np.arange(1,a.size+1,1)[prev:split]
    prev = split

结果为:

[1 2 3 4 5]
[ 6  7  8  9 10]
[11 12 13 14]
[15 16]
[17 18 19]

谢谢,实际上Zelazny7的解决方案更方便,因为我喜欢将段落存储在DataFrame中,它可以自动实现这一点。 - languitar
谢谢Rutger。np.diff()是一个很好的建议。 - ryanjdillon

0
你可以使用 `itertools.groupby`:
from itertools import groupby

s = pd.Series([3, 3, 3, 4, 4, 4, 4, 1, 1, 1, 1, 1])

[list(i) for _, i in groupby(s)]
# or list(map(lambda x: list(x[1]), groupby(s)))

输出:

[[3, 3, 3], [4, 4, 4, 4], [1, 1, 1, 1, 1]]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接