繁体   English   中英

使用阈值对点是否在指定区域内进行分类 - python

[英]Classify if point is within specified area using threshold - python

我有一个包含 xy 点的 df。 如果这些点仅位于这些帧的多边形内,我想删除它们。 这显示为以下area 这些点会从这个区域来来去去,所以我只想在它们被明确地放置在那里时删除。 否则将它们保留在 df 中。

核心困境是我不想在这里通过严格的规则。 因为这些点是流动的,我希望结合灵活性。 例如,某些点可能会暂时穿过该区域,不应删除。 而其他点位于该区域内足够长的时间,它们应该被移除。

显而易见的方法是在这里通过一些阈值方法。 使用下面的df1A位于3帧区域内,而B位于7帧区域内。 如果我通过了大于 5 帧的阈值,则应为该区域内的帧删除B ,而A不应受到影响。

问题是,它必须是连续的帧。 点会来来去去,所以我只想在连续 5 帧后删除。

import pandas as pd 
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
import random

df = pd.DataFrame({
    'X' : [-5,10,-5,-5,-5,-5,-5,-5,-5,30,20,10,0,-5,-5,-5,-5,-5,-5,-5,5],  
    'Y' : [50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50],                  
    'Label' : ['A','A','A','A','A','A','A','A','A','A','B','B','B','B','B','B','B','B','B','B','B'], 
    'Time' : [501,502,503,504,505,506,507,508,509,510,501,502,503,504,505,506,507,508,509,510,511],                         
    })

# designated area
x = ([1.5,-0.5,-1.25,-0.5,1.5,-11,-11,1.5]) 
y = ([75,62.5,50,37.5,25,25,75,75])

area = mpltPath.Path([[x, y] for x, y in zip(x, y)])
df1['is_inside'] = area.contains_points(df1[['X','Y']])

出去:

     X   Y Label  Time  is_inside
0   20  50     A   501       True # inside but only 1 frame. Keep
1   10  50     A   502      False # keep
2    0  50     A   503       True # inside total 7 frames (remove)
3   -5  50     A   504       True # inside total 7 frames (remove)
4   -5  50     A   505       True # inside total 7 frames (remove)
5   -5  50     A   506       True # inside total 7 frames (remove)
6    0  50     A   507       True # inside total 7 frames (remove)
7   10  50     A   508       True # inside total 7 frames (remove)
8   20  50     A   509       True # inside total 7 frames (remove)
9   30  50     A   510      False # keep
10  20  50     B   501      False # keep
11  10  50     B   502      False # keep
12   0  50     B   503      False # keep
13  -5  50     B   504       True # inside total 7 frames (remove)
14  -5  50     B   505       True # inside total 7 frames (remove)
15  -5  50     B   506       True # inside total 7 frames (remove)
16  -5  50     B   507       True # inside total 7 frames (remove)
17  -5  50     B   508       True # inside total 7 frames (remove)
18  -5  50     B   509       True # inside total 7 frames (remove)
19  -5  50     B   510       True # inside total 7 frames (remove)
20   5  50     B   511      False # keep

预期输出:

     X   Y Label  Time 
0   -5  50     A   501     
1   10  50     A   502         
9   30  50     A   510     
10  20  50     B   501     
11  10  50     B   502     
12   0  50     B   503     
20   5  50     B   511     

我首先复制您的数据:

import pandas as pd 
import matplotlib as mpl

x = [1.5, -0.5, -1.25, -0.5, 1.5, -11, -11, 1.5]
y = [75, 62.5, 50, 37.5, 25, 25, 75, 75]
vertices = list(zip(x, y))
polygon = mpl.path.Path(vertices, closed=True)

df = pd.DataFrame({
    'X' : [-5, 10, -5, -5, -5, -5, -5, -5, -5, 30, 
           20, 10, 0, -5, -5, -5, -5, -5, -5, -5, 5],  
    'Y' : [50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 
           50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50],                  
    'Label' : list('A'*10 + 'B'*11), 
    'Time' : 2*list(range(501, 511)) + [511]
    })
df = df.sort_values(['Label', 'Time'])
df['is_inside'] = polygon.contains_points(df[['X','Y']])

这是原始 DataFrame 的样子:

In [91]: df
Out[91]: 
     X   Y Label  Time  is_inside
0   -5  50     A   501       True
1   10  50     A   502      False
2   -5  50     A   503       True
3   -5  50     A   504       True
4   -5  50     A   505       True
5   -5  50     A   506       True
6   -5  50     A   507       True
7   -5  50     A   508       True
8   -5  50     A   509       True
9   30  50     A   510      False
10  20  50     B   501      False
11  10  50     B   502      False
12   0  50     B   503      False
13  -5  50     B   504       True
14  -5  50     B   505       True
15  -5  50     B   506       True
16  -5  50     B   507       True
17  -5  50     B   508       True
18  -5  50     B   509       True
19  -5  50     B   510       True
20   5  50     B   511      False

您可以使用itertools.groupby删除不需要的点:

import numpy as np
from itertools import groupby

threshold = 5

indexer = []

for label in np.unique(df['Label']):
    for key, group in groupby(df.loc[df['Label'] == label]['is_inside']):
        runlength = len(list(group))
        remove = key and (runlength > threshold)
        indexer.extend([remove]*runlength)

df.drop(df[indexer].index, inplace=True)

输出:

In [92]: df
Out[92]: 
     X   Y Label  Time  is_inside
0   -5  50     A   501       True
1   10  50     A   502      False
9   30  50     A   510      False
10  20  50     B   501      False
11  10  50     B   502      False
12   0  50     B   503      False
20   5  50     B   511      False

最干净(和有效)的方法是使用pd.DataFrame.groupby 这还有一个额外的好处,即能够轻松地为更复杂的分类添加更多多边形/过滤器。

定义对象

import pandas as pd
import matplotlib.path as mpltPath

使用与原始问题相同的数据

# Define data
df = pd.DataFrame({
    'X': [20, 10, 0, -5, -5, -5, 0, 10, 20, 30, 20, 10, 0, -5, -5, -5, -5, -5, -5, -5],
    'Y': [50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50],
    'Label': ['A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B'],
    'Time': [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510],
})

直接从 matplotlib.path.Path 对象定义多边形(即无需先绘制)。

# Define Polygon
x, y = [1.5, -0.5, -1.25, -0.5, 1.5, -11, -11, 1.5], [75, 62.5, 50, 37.5, 25, 25, 75, 75]
path = mpltPath.Path([[x, y] for x, y in zip(x, y)])
df['is_inside'] = path.contains_points(df[['X','Y']])

滚动分组

排序,因为时间排序对滚动操作很重要

df = df.sort_values(by=['Label','Time'])
df = df.reset_index(drop=True)

以下函数检查每个标签是否在n个连续帧的多边形中

def get_label_to_remove(df, n):
    d = df.groupby(['Label'])['is_inside'].agg(lambda x: x.rolling(n).agg(all).any())
    return d.to_dict()

测试用例

观测值分布如下(1 表示多边形内的一个点):

        501 502 503 504 505 506 507 508 509 510
    A   0   0   0   1   1   1   0   0   0   0
    B   0   0   0   1   1   1   1   1   1   1

注意 A 连续出现 3 次,B 连续出现 7 次。 观察函数在[2,3,4][6,7,8]

In[0]:
for n in [2,3,4,6,7,8]:
    print(n, get_label_to_remove(df, n))

Out[0]:
2 {'A': True, 'B': True}
3 {'A': True, 'B': True}
4 {'A': False, 'B': True}
6 {'A': False, 'B': True}
7 {'A': False, 'B': True}
8 {'A': False, 'B': False}

这适用于任意数量的标签,无需任何更改。

只去除坏点

应 OP 的要求添加此内容。 以下识别to_remove而不仅仅是坏标签(使用to_remove作为掩码)

n = 5
df['to_remove'] = df.groupby(['Label'])['is_inside'].apply(lambda x: x.rolling(n).agg(all)).fillna(0).astype(bool)
df.loc[df['is_inside'] & ~df['to_remove'], ['to_remove']] = pd.NA
df['to_remove'] = df['to_remove'].fillna(method='bfill')

快跑

In[0]:
df

Out[0]:
0   20  50  A   501 False   False
1   10  50  A   502 False   False
2   0   50  A   503 False   False
3   -5  50  A   504 True    False
4   -5  50  A   505 True    False
5   -5  50  A   506 True    False
6   0   50  A   507 False   False
7   10  50  A   508 False   False
8   20  50  A   509 False   False
9   30  50  A   510 False   False
10  20  50  B   501 False   False
11  10  50  B   502 False   False
12  0   50  B   503 False   False
13  -5  50  B   504 True    True
14  -5  50  B   505 True    True
15  -5  50  B   506 True    True
16  -5  50  B   507 True    True
17  -5  50  B   508 True    True
18  -5  50  B   509 True    True
19  -5  50  B   510 True    True

免责声明:

这是上面发布的答案的扩展。 我认为上面的两个答案都非常好,只想添加我的 2 美分而不是重新发明轮子。 所以我要延长 Leonardus Chen 的回答,所有功劳都应该归功于他。

人们可能想要扩展以前的答案的原因

为了使您对外部层的检测更加稳健,您可以引入某种平滑 (最坏的情况:5 帧中有 4 帧位置在多边形内,但恰好是每五帧位置在单帧外)

平滑可以是一个简单的标准,例如“如果位置在多边形内至少有 8 个连续帧中有 5 个,则删除”,或者您可以更平滑并使用一些例如高斯加权曲线。

代码库

为此,您可以像 Leonardus Chen 那样做:

### Code copied from Leonardus Chen to create a fully working example
import pandas as pd
import matplotlib.path as mpltPath

# Define data
df = pd.DataFrame({
    'X': [20, 10, 0, -5, -5, -5, 0, 10, 20, 30, 20, 10, 0, -5, -5, -5, -5, -5, -5, -5],
    'Y': [50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50],
    'Label': ['A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B'],
    'Time': [501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510],
})

# Define Polygon
x, y = [1.5, -0.5, -1.25, -0.5, 1.5, -11, -11, 1.5], [75, 62.5, 50, 37.5, 25, 25, 75, 75]
path = mpltPath.Path([[x, y] for x, y in zip(x, y)])
df['is_inside'] = path.contains_points(df[['X','Y']])

代码扩展

方法一

现在你继续使用df['is_inside']

  • 首先,我们将通过添加booleans (如True = 1False = 0 )来计算我们拥有的True的数量。
  • 之后,我们将检查该数字是否高于设定的 5 限制。
  • 确保设置min_periods = 1否则不会为前7个条目创建滚动窗口,因为窗口的大小还不是8

“如果在 8 帧中的 5 帧内则删除”如下所示:

df['inside_score'] = df['is_inside'].rolling(window=8, min_periods=1).sum()
df['inside_score_critical'] = df['inside_score'] >= 5

方法二

我将提出一个类似的标准,它使用高斯平滑函数而不是设置的窗口大小。 不幸的是,要创建所需的结果,您必须使用数字。

  • 这里的windows参数无关紧要,但通常应该比std=4高很多,否则可能会注意到不那么平滑的截止。
  • 而是设置std=4来控制高斯曲线的宽度以实现您想要的跨度。 (我发现 std 为 4 的行为与上述方法相似,但希望更流畅一些)
df['inside_score_2'] = df['is_inside'].rolling(window=10, min_periods=1, win_type='gaussian').sum(std=4)
df['inside_score_2_critical'] = df['inside_score_2'] >= 5

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM