使用阈值对点是否在指定区域内进行分类 - python
Classify if point is within specified area using threshold - python
我有一个包含 xy 点的 df。如果这些点仅位于这些帧的多边形内,我想删除它们。这在下面显示为 area
。这些点会从这个区域来来去去,所以我只想在它们被明确地放置在那里时移除。否则将它们保留在 df.
主要的困境是我不想在这里通过严格的规则。因为要点是流动的,所以我希望融入灵活性。例如,某些点可能会暂时通过该区域,不应将其删除。而其他点位于区域内的时间足够长,应将其移除。
显而易见的方法是在这里传递一些阈值方法。使用下面的 df1
,A
位于 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
。这还有一个额外的好处,就是能够轻松地添加更多 polygons/filters 以进行更复杂的分类。
定义对象
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
作为掩码)
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 帧位置在多边形内,则移除”,或者您可以更平滑并使用一些,例如高斯加权曲线。
代码库
这件事你就照样做:
### 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
的数量(因为 True
= 1
,False
= 0
).
- 之后我们将检查数字是否高于设置的限制 5。
- 确保设置
min_periods = 1
,否则不会为第一个 7
条目创建滚动 window,因为 windows 的大小尚未达到 [=22] =].
“如果在 8 帧中的 5 帧内则删除”看起来像这样:
df['inside_score'] = df['is_inside'].rolling(window=8, min_periods=1).sum()
df['inside_score_critical'] = df['inside_score'] >= 5
方法 2
我将提出一个类似的标准,它使用高斯平滑函数而不是集合 window 大小。不幸的是,要创建所需的结果,您必须使用数字。
- 此处的
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
我有一个包含 xy 点的 df。如果这些点仅位于这些帧的多边形内,我想删除它们。这在下面显示为 area
。这些点会从这个区域来来去去,所以我只想在它们被明确地放置在那里时移除。否则将它们保留在 df.
主要的困境是我不想在这里通过严格的规则。因为要点是流动的,所以我希望融入灵活性。例如,某些点可能会暂时通过该区域,不应将其删除。而其他点位于区域内的时间足够长,应将其移除。
显而易见的方法是在这里传递一些阈值方法。使用下面的 df1
,A
位于 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
。这还有一个额外的好处,就是能够轻松地添加更多 polygons/filters 以进行更复杂的分类。
定义对象
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
作为掩码)
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 帧位置在多边形内,则移除”,或者您可以更平滑并使用一些,例如高斯加权曲线。
代码库
这件事你就照样做:
### 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
的数量(因为True
=1
,False
=0
). - 之后我们将检查数字是否高于设置的限制 5。
- 确保设置
min_periods = 1
,否则不会为第一个7
条目创建滚动 window,因为 windows 的大小尚未达到 [=22] =].
“如果在 8 帧中的 5 帧内则删除”看起来像这样:
df['inside_score'] = df['is_inside'].rolling(window=8, min_periods=1).sum()
df['inside_score_critical'] = df['inside_score'] >= 5
方法 2
我将提出一个类似的标准,它使用高斯平滑函数而不是集合 window 大小。不幸的是,要创建所需的结果,您必须使用数字。
- 此处的
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