matplotlib: 绘图时如何忽略异常值

45

我正在绘制来自各种测试的一些数据。有时在一个测试中,我会有一个异常值(比如0.1),而其他所有值都小三个数量级。

使用matplotlib,我将绘图范围设为[0,max_data_value]

如何仅缩放我的数据并不显示异常值,这将破坏我的绘图x轴?

我是否应该简单地取95个百分位数,并在x轴上有[0, 95_percentile] 的范围?


什么类型的图表?散点图?直方图? - David Robinson
我正在使用直方图绘图。 - Ricky Robinson
5个回答

78

不存在一种单一的“最佳”异常值测试方法。理想情况下,您应该结合先验信息(例如,“由于某些原因,此参数不应超过x...”)。

大多数异常值测试使用中位数绝对差(MAD)而不是95th百分位数或其他基于方差的测量。否则,计算出来的方差/标准差将会受到异常值的严重影响。

这里有一个实现其中一种常见异常值测试的函数。

def is_outlier(points, thresh=3.5):
    """
    Returns a boolean array with True if points are outliers and False 
    otherwise.

    Parameters:
    -----------
        points : An numobservations by numdimensions array of observations
        thresh : The modified z-score to use as a threshold. Observations with
            a modified z-score (based on the median absolute deviation) greater
            than this value will be classified as outliers.

    Returns:
    --------
        mask : A numobservations-length boolean array.

    References:
    ----------
        Boris Iglewicz and David Hoaglin (1993), "Volume 16: How to Detect and
        Handle Outliers", The ASQC Basic References in Quality Control:
        Statistical Techniques, Edward F. Mykytka, Ph.D., Editor. 
    """
    if len(points.shape) == 1:
        points = points[:,None]
    median = np.median(points, axis=0)
    diff = np.sum((points - median)**2, axis=-1)
    diff = np.sqrt(diff)
    med_abs_deviation = np.median(diff)

    modified_z_score = 0.6745 * diff / med_abs_deviation

    return modified_z_score > thresh

作为使用它的一个例子,您可以执行以下操作:

import numpy as np
import matplotlib.pyplot as plt

# The function above... In my case it's in a local utilities module
from sci_utilities import is_outlier

# Generate some data
x = np.random.random(100)

# Append a few "bad" points
x = np.r_[x, -3, -10, 100]

# Keep only the "good" points
# "~" operates as a logical not operator on boolean numpy arrays
filtered = x[~is_outlier(x)]

# Plot the results
fig, (ax1, ax2) = plt.subplots(nrows=2)

ax1.hist(x)
ax1.set_title('Original')

ax2.hist(filtered)
ax2.set_title('Without Outliers')

plt.show()

这里输入图像描述


这是一个很好的答案(+1),但我认为“”是按位非,而不是逻辑非 - 对于我不完全清楚的原因,这似乎在这里并不重要,但在其他地方可能会有所不同。 `False!= True,但not False == True`。 - Will Dean
1
好的观点!在numpy中,它被重载为在布尔数组上操作逻辑非(例如~np.array(False) == True),但对于其他任何东西都不是这种情况。我应该澄清一下。(顺便说一句,按照惯例,如果some_array有多个元素,则not some_array会引发值错误。因此需要在上面的示例中使用~。) - Joe Kington
3
当中位数偏差为零时,这种方法会失效。当我天真地加载了一个超过50%的零数据集时,就发生了这种情况。 - Wesley Tansey
@WesleyTansey你找到处理除以0错误的好方法了吗?我目前也在解决同样的问题。 - The2ndSon
我觉得我最终只是采取了该情况下的最小非零偏差。这对我的边缘情况效果很好。 - Wesley Tansey
显示剩余5条评论

14

如果您不关心拒绝异常值,正如Joe所提到的那样,而这只是出于美观原因,那么您可以仅设置图表的x轴限制:

plt.xlim(min_x_data_value,max_x_data_value)

在这里,values是您希望显示的限制。

plt.ylim(min,max)可以设置y轴上的限制。


5
对于直方图而言,发帖者需要重新计算分箱。Matplotlib 使用固定的分箱边缘。当你放大时它不会“重新分箱”。 - Joe Kington

12

我认为使用pandas的分位数函数很有用且更加灵活。

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

fig = plt.figure()
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)

pd_series = pd.Series(np.random.normal(size=300)) 
pd_series_adjusted = pd_series[pd_series.between(pd_series.quantile(.05), pd_series.quantile(.95))] 

ax1.boxplot(pd_series)
ax1.set_title('Original')

ax2.boxplot(pd_series_adjusted)
ax2.set_title('Adjusted')

plt.show()

输入图像描述


9
我通常通过函数np.clip传递数据。如果您有数据的最大和最小值的合理估计,只需使用它即可。如果您没有合理的估计,剪裁数据的直方图将显示尾巴的大小,如果离群值确实只是离群值,则尾巴应该很小。
我运行的类似于这样:
import numpy as np
import matplotlib.pyplot as plt

data = np.random.normal(3, size=100000)
plt.hist(np.clip(data, -15, 8), bins=333, density=True)

如果你更改剪切函数中的最小值和最大值,直到找到适合你的数据的正确值,就可以比较结果。

Example

在这个例子中,你可以立即看到最大值为8不好,因为你正在删除很多有意义的信息。最小值-15应该没问题,因为尾巴甚至都看不见。根据某些公差,你可能可以编写一些代码来找到一些好的边界,以最小化尾巴的大小。

3
在某些情况下(例如在像Joe Kington的答案中的直方图绘图中),重新缩放绘图可以显示异常值存在,但它们已经被缩放比例部分裁剪掉了。删除异常值不会产生与仅重新缩放相同的效果。自动查找适当的轴限制通常比检测和删除异常值更可取且更容易。
这里有一个使用百分位数和数据相关边距来实现良好视图的自动缩放想法。
import numpy as np
import matplotlib.pyplot as plt    

# xdata = some x data points ...
# ydata = some y data points ...

# Finding limits for y-axis     
ypbot = np.percentile(ydata, 1)
yptop = np.percentile(ydata, 99)
ypad = 0.2*(yptop - ypbot)
ymin = ypbot - ypad
ymax = yptop + ypad

使用示例:

fig = plt.figure(figsize=(6, 8))

ax1 = fig.add_subplot(211)
ax1.scatter(xdata, ydata, s=1, c='blue')
ax1.set_title('Original')
ax1.axhline(y=0, color='black')

ax2 = fig.add_subplot(212)
ax2.scatter(xdata, ydata, s=1, c='blue')
ax2.axhline(y=0, color='black')
ax2.set_title('Autscaled')
ax2.set_ylim([ymin, ymax])

plt.show()

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接