python熊猫从一系列布尔值中获取索引边界

python熊猫从一系列布尔值中获取索引边界,python,pandas,Python,Pandas,我正在尝试根据一些特征剪辑视频。 我目前的策略是为每一帧创建一系列布尔值,并按时间戳进行索引True保存它,而False转储它 当我计划剪切视频时,我需要从这个列表中提取边界,这样我就可以告诉fmpeg我想从主视频中提取的部分的开始和结束 总结如下: 我有一个熊猫系列,看起来像这样: acquisitionTs 0.577331 False 0.611298 False 0.645255 False 0.679218 False 0.716538 Fals

我正在尝试根据一些特征剪辑视频。 我目前的策略是为每一帧创建一系列布尔值,并按时间戳进行索引
True
保存它,而
False
转储它

当我计划剪切视频时,我需要从这个列表中提取边界,这样我就可以告诉fmpeg我想从主视频中提取的部分的开始和结束

总结如下:

我有一个熊猫系列,看起来像这样:

acquisitionTs
0.577331     False
0.611298     False
0.645255     False
0.679218     False
0.716538     False
0.784453      True
0.784453      True
0.818417      True
0.852379      True
0.886336      True
0.920301      True
0.954259     False
             ...  
83.393376    False
83.427345    False
dtype: bool
(由于显示原因而被截断,但时间戳通常从0开始)

我需要得到
True
序列的边界,所以在这个例子中,我应该得到
[[t_0,t_1],[t_2,t_3]n,[t_2n-1,t_2n]
,如果在我的系列中有
t_0=0.784453
t_1=0.920301
不同的
True

现在这个问题看起来很简单,事实上,您可以将序列移位1,然后在之间进行异或运算,得到一个布尔值列表,其中
True
表示边界

e = df.shift(periods=1, freq=None, axis=0)^df
print(e[e].index)
(带
df
为熊猫系列) 还有一些工作要做,比如计算第一个元素是上升沿还是下降沿,但这种方法是有效的

然而,这似乎不是很像蟒蛇。事实上,问题很简单,我相信在
pandas
numpy
甚至
python
中一定有一个预先构建的函数,它可以很好地适应单个函数调用,而不是像上面那样的hack。虽然
groupby
函数似乎很有前途,但我以前从未使用过它

这样做的最佳方式是什么?

您可以用它来识别
True
s的集群:

In [102]: ts
Out[102]: 
0.069347    False
0.131956    False
0.143948    False
0.224864    False
0.242640     True
0.372599    False
0.451989    False
0.462090    False
0.579956     True
0.588791     True
0.603638    False
0.625107    False
0.642565    False
0.708547    False
0.730239    False
0.741652    False
0.747126     True
0.783276     True
0.896705     True
0.942829     True
Name: keep, dtype: bool

In [103]: groups, nobs = ndimage.label(ts); groups
Out[103]: array([0, 0, 0, 0, 1, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3], dtype=int32)
拥有
数组后,可以使用以下方法查找相关时间:

比如说,

import numpy as np
import pandas as pd
import scipy.ndimage as ndimage
np.random.seed(2016)

def make_ts(N, ngroups):
    times = np.random.random(N)
    times = np.sort(times)
    idx = np.sort(np.random.randint(N, size=(ngroups,)))
    arr = np.zeros(N)
    arr[idx] = 1
    arr = arr.cumsum()
    arr = (arr % 2).astype(bool)
    ts = pd.Series(arr, index=times, name='keep')
    return ts

def find_groups(ts):
    groups, nobs = ndimage.label(ts)
    df = pd.DataFrame({'times': ts.index, 'group': groups})
    result = (df.loc[df['group'] != 0]
                .groupby('group')['times']
                .agg({'start':'first','end':'last'}))
    return result

ts = make_ts(20, 5)
result = find_groups(ts)
屈服

          start       end
group                    
1      0.242640  0.242640
2      0.579956  0.588791
3      0.747126  0.942829
要以列表的形式获取开始和结束时间,可以使用:

In [125]: result.values.tolist()
Out[125]: 
[[0.24264034406127022, 0.24264034406127022],
 [0.5799564094638113, 0.5887908182432907],
 [0.7471260123697537, 0.9428288694956402]]

使用
ndimage.label
很方便,但请注意,也可以在不使用
scipy的情况下计算:

def find_groups_without_scipy(ts):
    df = pd.DataFrame({'times': ts.index, 'group': (ts.diff() == True).cumsum()})
    result = (df.loc[df['group'] % 2 == 1]
                .groupby('group')['times']
                .agg({'start':'first','end':'last'}))
    return result
这里的主要思想是使用
(ts.diff()==True).cumsum()查找
True
s集群的标签
ts.diff()==True
给出的结果与
ts.shift()^ts
的结果相同,但要快一点。取累积和(即调用
cumsum
)将
True
视为等于1,将
False
视为等于0,因此每次遇到
True
时,累积和将增加1。因此,每个集群都会使用不同的编号进行标记:

In [111]: (ts.diff() == True).cumsum()
Out[111]: 
0.069347    0
0.131956    0
0.143948    0
0.224864    0
0.242640    1
0.372599    2
0.451989    2
0.462090    2
0.579956    3
0.588791    3
0.603638    4
0.625107    4
0.642565    4
0.708547    4
0.730239    4
0.741652    4
0.747126    5
0.783276    5
0.896705    5
0.942829    5
Name: keep, dtype: int64

我会使用一个数据帧而不是一个系列(它实际上也适用于一个系列)

我会:

df[df.Value.diff().fillna(False)]
    acquisitionTs  Value
5        0.784453   True
7        0.818417  False
8        0.852379   True
11       0.954259  False
因此,正如您知道的第一个值False一样,您知道0-4是False,然后它在每个索引(5,7,8,11)处切换


我认为,
groupby
函数对您没有帮助,因为它会破坏真/假值的顺序(在我的示例中,您将有2组,而不是5组)。

这些都是很好的解决方案,但我认为可能有一个更简单、更普遍适用的选项

在其核心,您要查找某个值是否与前一个值不同。如果您将其与自身进行比较,但移动了1,您将得到您想要的。您还可以获得快速比较操作的好处

将熊猫作为pd导入
#创建一个系列
系列1=pd.系列(['duck','duck','duck','duck','duck','goose','goose','duck']))
#创建系列的副本,移动1个空间
系列_2=系列_1.移位(1)
#比较原始系列和移位系列,以获得新的“它是边缘吗?”系列
is_edge=系列_1!=系列2
数据帧({'data':series_1,'edge':is_edge})

或者,如果比较栏中包含的内容更清楚:

让这个例子用数字索引和布尔值直接说明你的问题:

series_1=pd.series({.1:True、.2:True、.3:False、.4:False、.5:True、.6:True})
系列_2=系列_1.移位(1)
is_edge=系列_1!=系列2
数据帧({'original':series_1,'shift':series_2,'edge':is_edge})

很好地利用了您可以支配的资源,而不是引入额外的依赖项。谢谢您的回答!然而,对于第一个元素,您的代码似乎并不不可知,第一个元素可能是真的,也可能是假的,因此您将以与您首先想要的相反的结果结束。一个简单的解决方法是在结果中插入第一行,如果它是真的(最后一行也会发生同样的情况),感谢您的帮助!编辑:实际上,我们可以只观察结果的第一个(和最后一个)元素的值,它告诉我们边缘是上升还是下降,所以一开始并没有什么问题。
df
    acquisitionTs  Value
0        0.577331  False
1        0.611298  False
2        0.645255  False
3        0.679218  False
4        0.716538  False
5        0.784453   True
6        0.784453   True
7        0.818417  False
8        0.852379   True
9        0.886336   True
10       0.920301   True
11       0.954259  False
df[df.Value.diff().fillna(False)]
    acquisitionTs  Value
5        0.784453   True
7        0.818417  False
8        0.852379   True
11       0.954259  False