删除每列的离群值及其对应的行

3 投票
2 回答
3574 浏览
提问于 2025-04-18 18:11

我的Numpy数组有10列,大约有200万行。

现在我需要分别分析每一列,找出异常值,并删除整个对应的行。

所以我会先分析第0列;找到第10行、第20行和第100行的异常值;然后删除这些行。

接下来,我会在现在已经去掉一些行的数组中分析第1列,并用同样的方法处理。

当然,我可以想到一个普通的手动方法来完成这个过程(逐列遍历,找出异常值的索引,删除行,然后继续分析下一列),但我发现Numpy里有一些快速的小技巧可以用来完成这样的统计任务。

如果你能多说一点这个方法的运行时间成本,那就更好了。

我并不局限于使用NumPy,如果SciPy有什么有用的工具,我也可以使用。

谢谢!

2 个回答

0

最有效的解决方案取决于找出异常值的成本、删除一行的成本,以及异常值出现的频率。

如果你的异常值出现得不太频繁,我建议你可以这样做:

  • 创建一个布尔值表,标记哪些是异常值(原始表中的每个元素对应一个布尔值)
  • 对这个表进行求和,计算每一行的总和
  • 创建一个新表,只保留那些异常值总和为0的行

逐行删除数据会花费很多时间,如果找异常值的过程不太复杂,那么因为同一行可能有多个异常值而增加的工作量就不算太重要。

用代码表示的话,大概是这样的:

outliers = find_outliers(data)
data_without_outliers = data[outliers.sum(axis=1) == 0]

这里的 find_outliers 函数会创建一个布尔值表,标记每个元素是否是异常值(也就是说,如果原始数组 data 中的对应元素是异常值,就标记为 True)。

我猜性能的好坏还和你用的异常值检测算法有关。如果你能让它简单且高效,那么速度会很快。

4

这里有两种非常简单的方法,第二种稍微复杂一点:

arr = np.random.randn(2e6, 10)

def remove_outliers(arr, k):
    mu, sigma = np.mean(arr, axis=0), np.std(arr, axis=0, ddof=1)
    return arr[np.all(np.abs((arr - mu) / sigma) < k, axis=1)]

def remove_outliers_bis(arr, k):
    mask = np.ones((arr.shape[0],), dtype=np.bool)
    mu, sigma = np.mean(arr, axis=0), np.std(arr, axis=0, ddof=1)
    for j in range(arr.shape[1]):
        col = arr[:, j]
        mask[mask] &= np.abs((col[mask] - mu[j]) / sigma[j]) < k
    return arr[mask]

性能取决于你有多少个异常值:

In [38]: %timeit remove_outliers(arr, 1)
1 loops, best of 3: 1.13 s per loop

In [39]: %timeit remove_outliers_bis(arr, 1)
1 loops, best of 3: 983 ms per loop

In [40]: %timeit remove_outliers(arr, 2)
1 loops, best of 3: 1.21 s per loop

In [41]: %timeit remove_outliers_bis(arr, 2)
1 loops, best of 3: 1.51 s per loop

当然还有:

In [42]: np.allclose(remove_outliers(arr, 1), remove_outliers_bis(arr, 1))
Out[42]: True

In [43]: np.allclose(remove_outliers(arr, 2), remove_outliers_bis(arr, 2))
Out[43]: True

我觉得第二种方法的复杂性并不值得为了可能的速度提升去使用,但每个人的情况可能不同……

撰写回答