python pandas从一系列布尔值获得索引边界

时间:2016-08-12 11:38:40

标签: python pandas

我正在尝试根据某些特征剪切视频。 我目前的策略导致每个帧的pandas系列布尔值,按时间戳索引。 True保留它,False将其转储。

当我计划剪切视频时,我需要从此列表中提取边界,以便我可以告诉fmpeg我想从主视频中提取的部分的开头和结尾。

涂总结:

我有一个pandas系列,如下所示:

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

(由于出现原因而被截断,但TimeStamp通常从0开始)

我需要获得True序列的边界,因此在此示例中,如果我[[t_0,t_1],[t_2,t_3]n, ... [t_2n-1,t_2n]],我应该t_0 = 0.784453t_1 = 0.920301n我的熊猫系列中True的不同序列。

现在这个问题看起来非常简单,实际上你可以把序列换成一个,然后在两者之间做一个xor来得到一个布尔列表,其中True用于边界

e = df.shift(periods=1, freq=None, axis=0)^df
print(e[e].index)

df是熊猫系列) 还有一些工作要做,比如判断第一个元素是上升边还是下降边,但是这个黑客行之有效。

然而,这似乎不是非常pythonic。事实上,问题是如此简单我相信在pandasnumpy或甚至python中必须有一个预先构建的函数,它可以很好地适用于单个函数调用而不是像上面的黑客。虽然groupby函数似乎很有希望,但我之前从未使用它。

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

3 个答案:

答案 0 :(得分:1)

您可以使用scipy.ndimage.label来识别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)

获得groups数组后,您可以使用groupby/agg找到相关联的时间:

    result = (df.loc[df['group'] != 0]
                .groupby('group')['times']
                .agg({'start':'first','end':'last'}))

例如,

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

此处的主要想法是使用True查找(ts.diff() == True).cumsum() s群集的标签。 ts.diff() == True给出与ts.shift() ^ ts相同的结果,但速度要快一些。取累积和(即调用cumsum)将True视为等于1,将False视为等于0,因此每次遇到True时累积和增加因此,每个群集都标有不同的数字:

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

答案 1 :(得分:1)

我会使用Dataframe而不是Series(它实际上也适用于Series)。

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

所以你知道这里的第一个值为False,你知道0-4是假的然后它会在每个索引处切换(5,7,8,11)

我认为groupby函数对你没有帮助,因为它会松开你的True / False值的顺序(在我的例子中你将有2组,而不是5组)。

答案 2 :(得分:1)

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

本质上,您正在寻找一个值是否与前一个值不同。如果您将它与自身进行比较但移动了 1,您将得到您想要的结果。您还可以从快速比较操作中受益。

import pandas as pd

# Create a series
series_1 = pd.Series(['duck', 'duck', 'duck', 'duck', 'goose', 'goose', 'duck'])

# Create a copy of the series shifted by 1 space
series_2 = series_1.shift(1)

# Compare the original and shifted series to get a new "Is it an edge?" series
is_edge = series_1 != series_2

pd.DataFrame({'data': series_1, 'edge': is_edge})

comparison_dataframe

或者如果包含比较列更清楚:

comparison_dataframe

并使用数字索引和布尔值使这个示例直接说明您的问题:

series_1 = pd.Series({.1: True, .2: True, .3: False, .4: False, .5: True, .6: True})
series_2 = series_1.shift(1)
is_edge = series_1 != series_2

pd.DataFrame({'original': series_1, 'shifted': series_2, 'edge': is_edge})

comparison_dataframe