【问题标题】:Detect and exclude outliers in a pandas DataFrame检测和排除 pandas DataFrame 中的异常值
【发布时间】:2022-01-07 10:50:54
【问题描述】:

我有一个包含几列的 pandas 数据框。

现在我知道某些行是基于某个列值的异常值。

例如

列 'Vol' 包含 12xx 附近的所有值,其中一个值为 4000(异常值)。

现在我想排除那些像这样具有Vol 列的行。

所以,基本上我需要在数据框上放置一个过滤器,以便我们选择某一列的值在平均值范围内的所有行,例如,与平均值相差 3 个标准差。

有什么优雅的方法可以实现这一目标?

【问题讨论】:

    标签: python pandas filtering dataframe outliers


    【解决方案1】:

    像在 numpy.array 中一样使用 boolean 索引

    df = pd.DataFrame({'Data':np.random.normal(size=200)})
    # example dataset of normally distributed data. 
    
    df[np.abs(df.Data-df.Data.mean()) <= (3*df.Data.std())]
    # keep only the ones that are within +3 to -3 standard deviations in the column 'Data'.
    
    df[~(np.abs(df.Data-df.Data.mean()) > (3*df.Data.std()))]
    # or if you prefer the other way around
    

    对于一个系列来说是类似的:

    S = pd.Series(np.random.normal(size=200))
    S[~((S-S.mean()).abs() > 3*S.std())]
    

    【讨论】:

    • 他们是DataFrame.abs() FYI,也是DataFrame.clip()
    • 对于clip(),Jeff,轮廓不会被删除:df.SOME_DATA.clip(-3std,+3std) 将轮廓分配给 +3std 或 -3std
    • 几乎一样,@AMM
    • 如果我们的 pandas 数据框有 100 列,我们如何做同样的事情?
    • 太棒了,感谢@CTZhu 的回答。 @DreamerP 您可以将其应用于整个 DataFrame:df_new = df[np.abs(df - df.mean()) &lt;= (3 * df.std())]但是与将其应用于系列或单列相比,这将用 np.nan 替换异常值并保持 DataFrame 的形状,因此可能需要插值来填充缺失值。
    【解决方案2】:

    如果您的数据框中有多个列,并且想要删除至少一列中存在异常值的所有行,则以下表达式将一次性完成。

    df = pd.DataFrame(np.random.randn(100, 3))
    
    from scipy import stats
    df[(np.abs(stats.zscore(df)) < 3).all(axis=1)]
    

    说明:

    • 对于每一列,它首先计算每个值的 Z 分数 列,相对于列均值和标准差。
    • 然后它采用绝对 Z 分数,因为方向不 仅当它低于阈值时才有意义。
    • all(axis=1) 确保对于每一行,所有列满足 约束。
    • 最后,这个条件的结果用于索引数据帧。

    根据单个列过滤其他列

    • 例如为zscoredf[0] 指定一列,然后删除.all(axis=1)
    df[(np.abs(stats.zscore(df[0])) < 3)]
    

    【讨论】:

    • 你能解释一下这段代码在做什么吗?也许提供一个想法,我如何删除在单个指定列中具有异常值的所有行?会有帮助的。谢谢。
    • 对于每一列,首先它计算列中每个值相对于列均值和标准差的 Z 分数。然后是 Z-score 的绝对值,因为方向无关紧要,只要它低于阈值。 .all(axis=1) 确保对于每一行,所有列都满足约束。最后,此条件的结果用于索引数据帧。
    • 当列中有 Nulls/Nans 时,您将如何处理这种情况。我们怎么能忽略它们?
    • 我们如何处理这个解决方案的 str 列?如果某些列是非数字列,并且我们想根据所有数字列删除异常值。
    • 得到错误:“TypeError:不支持的操作数类型/:'str'和'int'”
    【解决方案3】:

    此答案类似于@tanemaki 提供的答案,但使用lambda 表达式而不是scipy stats

    df = pd.DataFrame(np.random.randn(100, 3), columns=list('ABC'))
    
    standard_deviations = 3
    df[df.apply(lambda x: np.abs(x - x.mean()) / x.std() < standard_deviations)
       .all(axis=1)]
    

    过滤只有一列(例如“B”)在三个标准差内的数据框:

    df[((df['B'] - df['B'].mean()) / df['B'].std()).abs() < standard_deviations]
    

    请参阅此处了解如何滚动应用此 z 分数:Rolling Z-score applied to pandas dataframe

    【讨论】:

    【解决方案4】:

    scipy.stats 具有 trim1()trimboth() 方法,可根据排名和引入的移除值百分比将异常值剔除在一行中。

    【讨论】:

    • trimboth 对我来说是最简单的。
    【解决方案5】:

    对于您的每个数据框列,您可以获得分位数:

    q = df["col"].quantile(0.99)
    

    然后过滤:

    df[df["col"] < q]
    

    如果需要删除上下异常值,请将条件与 AND 语句结合起来:

    q_low = df["col"].quantile(0.01)
    q_hi  = df["col"].quantile(0.99)
    
    df_filtered = df[(df["col"] < q_hi) & (df["col"] > q_low)]
    

    【讨论】:

    • 这篇文章很好地概述了异常值去除技术machinelearningmastery.com/…
    • 这可能只从上限删除异常值..而不是下限?
    • @indolentdeveloper 你是对的,只需反转不等式以删除较低的异常值,或将它们与 OR 运算符结合。
    • 评论的想法是更新答案;)。因为有人可能会错过这一点。
    • @user6903745 AND 语句还是“OR”?
    【解决方案6】:

    另一种选择是转换您的数据,以减轻异常值的影响。您可以通过对数据进行微调来做到这一点。

    import pandas as pd
    from scipy.stats import mstats
    %matplotlib inline
    
    test_data = pd.Series(range(30))
    test_data.plot()
    

    # Truncate values to the 5th and 95th percentiles
    transformed_test_data = pd.Series(mstats.winsorize(test_data, limits=[0.05, 0.05])) 
    transformed_test_data.plot()
    

    【讨论】:

      【解决方案7】:
      #------------------------------------------------------------------------------
      # accept a dataframe, remove outliers, return cleaned data in a new dataframe
      # see http://www.itl.nist.gov/div898/handbook/prc/section1/prc16.htm
      #------------------------------------------------------------------------------
      def remove_outlier(df_in, col_name):
          q1 = df_in[col_name].quantile(0.25)
          q3 = df_in[col_name].quantile(0.75)
          iqr = q3-q1 #Interquartile range
          fence_low  = q1-1.5*iqr
          fence_high = q3+1.5*iqr
          df_out = df_in.loc[(df_in[col_name] > fence_low) & (df_in[col_name] < fence_high)]
          return df_out
      

      【讨论】:

      • 我在“df_out = df_in.loc[(df_in[col_name] > fence_low) & (df_in[col_name]
      【解决方案8】:

      如果您喜欢方法链接,您可以像这样为所有数字列获取布尔条件:

      df.sub(df.mean()).div(df.std()).abs().lt(3)
      

      每列的每个值都将转换为True/False,具体取决于其与平均值的距离是否小于三个标准差。

      【讨论】:

      • 这应该是le(3),因为它的删除异常值。这样你就可以得到异常值的True。除了+1,这个答案应该更高
      【解决方案9】:

      包含数据和 2 个组的完整示例如下:

      进口:

      from StringIO import StringIO
      import pandas as pd
      #pandas config
      pd.set_option('display.max_rows', 20)
      

      2组数据示例:G1:Group 1. G2: Group 2:

      TESTDATA = StringIO("""G1;G2;Value
      1;A;1.6
      1;A;5.1
      1;A;7.1
      1;A;8.1
      
      1;B;21.1
      1;B;22.1
      1;B;24.1
      1;B;30.6
      
      2;A;40.6
      2;A;51.1
      2;A;52.1
      2;A;60.6
      
      2;B;80.1
      2;B;70.6
      2;B;90.6
      2;B;85.1
      """)
      

      将文本数据读取到 pandas 数据框:

      df = pd.read_csv(TESTDATA, sep=";")
      

      使用标准差定义异常值

      stds = 1.0
      outliers = df[['G1', 'G2', 'Value']].groupby(['G1','G2']).transform(
                 lambda group: (group - group.mean()).abs().div(group.std())) > stds
      

      定义过滤后的数据值和异常值:

      dfv = df[outliers.Value == False]
      dfo = df[outliers.Value == True]
      

      打印结果:

      print '\n'*5, 'All values with decimal 1 are non-outliers. In the other hand, all values with 6 in the decimal are.'
      print '\nDef DATA:\n%s\n\nFiltred Values with %s stds:\n%s\n\nOutliers:\n%s' %(df, stds, dfv, dfo)
      

      【讨论】:

        【解决方案10】:

        我删除异常值的函数

        def drop_outliers(df, field_name):
            distance = 1.5 * (np.percentile(df[field_name], 75) - np.percentile(df[field_name], 25))
            df.drop(df[df[field_name] > distance + np.percentile(df[field_name], 75)].index, inplace=True)
            df.drop(df[df[field_name] < np.percentile(df[field_name], 25) - distance].index, inplace=True)
        

        【讨论】:

          【解决方案11】:

          我认为删除和丢弃异常值在统计上是错误的。 它使数据与原始数据不同。 也使数据形状不均,因此最好的方法是通过对数转换数据来减少或避免异常值的影响。 这对我有用:

          np.log(data.iloc[:, :])
          

          【讨论】:

          • 无法假设 OP 想要做某事的原因。
          【解决方案12】:

          我更喜欢剪辑而不是丢弃。以下将在第 2 和第 98 个百分位数处剪辑到位。

          df_list = list(df)
          minPercentile = 0.02
          maxPercentile = 0.98
          
          for _ in range(numCols):
              df[df_list[_]] = df[df_list[_]].clip((df[df_list[_]].quantile(minPercentile)),(df[df_list[_]].quantile(maxPercentile)))
          

          【讨论】:

            【解决方案13】:

            由于我处于数据科学之旅的早期阶段,我正在使用以下代码处理异常值。

            #Outlier Treatment
            
            def outlier_detect(df):
                for i in df.describe().columns:
                    Q1=df.describe().at['25%',i]
                    Q3=df.describe().at['75%',i]
                    IQR=Q3 - Q1
                    LTV=Q1 - 1.5 * IQR
                    UTV=Q3 + 1.5 * IQR
                    x=np.array(df[i])
                    p=[]
                    for j in x:
                        if j < LTV or j>UTV:
                            p.append(df[i].median())
                        else:
                            p.append(j)
                    df[i]=p
                return df
            

            【讨论】:

              【解决方案14】:

              对于数据框中的每个系列,您可以使用 betweenquantile 删除异常值。

              x = pd.Series(np.random.normal(size=200)) # with outliers
              x = x[x.between(x.quantile(.25), x.quantile(.75))] # without outliers
              

              【讨论】:

              • 这里您只选择四分位距 (IQR) 内的数据,但请记住,此范围之外的值可能不是异常值。
              • 选择例如我认为 0.1 和 0.9 会很安全。像这样使用 between 和 quantiles 是一种很好的语法。
              【解决方案15】:

              将第 98 和第 2 个百分位数作为异常值的界限

              upper_limit = np.percentile(X_train.logerror.values, 98) 
              lower_limit = np.percentile(X_train.logerror.values, 2) # Filter the outliers from the dataframe
              data[‘target’].loc[X_train[‘target’]>upper_limit] = upper_limit data[‘target’].loc[X_train[‘target’]<lower_limit] = lower_limit
              

              【讨论】:

                【解决方案16】:

                你可以使用布尔掩码:

                import pandas as pd
                
                def remove_outliers(df, q=0.05):
                    upper = df.quantile(1-q)
                    lower = df.quantile(q)
                    mask = (df < upper) & (df > lower)
                    return mask
                
                t = pd.DataFrame({'train': [1,1,2,3,4,5,6,7,8,9,9],
                                  'y': [1,0,0,1,1,0,0,1,1,1,0]})
                
                mask = remove_outliers(t['train'], 0.1)
                
                print(t[mask])
                

                输出:

                   train  y
                2      2  0
                3      3  1
                4      4  1
                5      5  0
                6      6  0
                7      7  1
                8      8  1
                

                【讨论】:

                  【解决方案17】:

                  由于我还没有看到处理 numericalnon-numerical 属性的答案,所以这里是一个补充答案。

                  您可能只想删除数值属性上的异常值(分类变量几乎不可能是异常值)。

                  函数定义

                  我已经扩展了@tanemaki 的建议,以在也存在非数字属性时处理数据:

                  from scipy import stats
                  
                  def drop_numerical_outliers(df, z_thresh=3):
                      # Constrains will contain `True` or `False` depending on if it is a value below the threshold.
                      constrains = df.select_dtypes(include=[np.number]) \
                          .apply(lambda x: np.abs(stats.zscore(x)) < z_thresh, reduce=False) \
                          .all(axis=1)
                      # Drop (inplace) values set to be rejected
                      df.drop(df.index[~constrains], inplace=True)
                  

                  用法

                  drop_numerical_outliers(df)
                  

                  示例

                  想象一个数据集df,其中包含一些关于房屋的值:胡同、土地轮廓、售价……例如:Data Documentation

                  首先,您希望在散点图上可视化数据(z-score Thresh=3):

                  # Plot data before dropping those greater than z-score 3. 
                  # The scatterAreaVsPrice function's definition has been removed for readability's sake.
                  scatterAreaVsPrice(df)
                  

                  # Drop the outliers on every attributes
                  drop_numerical_outliers(train_df)
                  
                  # Plot the result. All outliers were dropped. Note that the red points are not
                  # the same outliers from the first plot, but the new computed outliers based on the new data-frame.
                  scatterAreaVsPrice(train_df)
                  

                  【讨论】:

                  • 很好的解决方案!作为提醒,reduce=Falsepandas 版本 0.23.0 起已被弃用
                  • result_type='reduce' 替换reduce=False
                  • @KeyMaker00 我真的很想使用它,但我收到以下错误:ValueError: No axis named 1 for object type Series
                  【解决方案18】:

                  在回答实际问题之前,我们应该根据您的数据的性质询问另一个非常相关的问题:

                  什么是异常值?

                  想象一系列值[3, 2, 3, 4, 999](其中999 似乎不适合)并分析各种异常值检测方法

                  Z 分数

                  这里的问题是,有问题的值严重扭曲了我们的度量 meanstd,导致不显眼的 z 分数大约为 [-0.5, -0.5, -0.5, -0.5, 2.0],使每个值都保持在平均值的两个标准差内。因此,一个非常大的异常值可能会扭曲您对异常值的整体评估。我不鼓励这种方法。

                  分位数过滤器

                  一种更强大的方法是this answer,消除了底部和顶部 1% 的数据。但是,如果这些数据确实是异常值,这将消除与问题无关的固定分数。您可能会丢失大量有效数据,另一方面,如果您有超过 1 % 或 2 % 的数据作为异常值,您仍会保留一些异常值。

                  IQR 距离中位数

                  更强大的分位数原则版本:消除所有与数据中位数相差超过f 乘以interquartile range 的数据。例如,sklearnRobustScaler 就是这样做的。 IQR 和中位数对异常值具有鲁棒性,因此您比 z-score 方法的问题更聪明。

                  在正态分布中,我们大致有 iqr=1.35*s,因此您可以将 z-score 过滤器的 z=3 转换为 iqr 过滤器的 f=2.22。这将删除上面示例中的999

                  基本假设是您的数据至少“中间一半”是有效的并且与分布非常相似,而如果尾部与您的问题相关,您也会搞砸。

                  高级统计方法

                  当然,还有一些花哨的数学方法,例如 Peirce criterionGrubb's testDixon's Q-test,仅举几个也适用于非正态分布数据的方法。它们都不容易实现,因此没有进一步解决。

                  代码

                  在示例数据框中将所有数值列的所有异常值替换为 np.nan。该方法对 pandas 提供的all dtypes 具有鲁棒性,可以轻松应用于混合类型的数据帧:

                  import pandas as pd
                  import numpy as np                                     
                  
                  # sample data of all dtypes in pandas (column 'a' has an outlier)         # dtype:
                  df = pd.DataFrame({'a': list(np.random.rand(8)) + [123456, np.nan],       # float64
                                     'b': [0,1,2,3,np.nan,5,6,np.nan,8,9],                  # int64
                                     'c': [np.nan] + list("qwertzuio"),                     # object
                                     'd': [pd.to_datetime(_) for _ in range(10)],           # datetime64[ns]
                                     'e': [pd.Timedelta(_) for _ in range(10)],             # timedelta[ns]
                                     'f': [True] * 5 + [False] * 5,                         # bool
                                     'g': pd.Series(list("abcbabbcaa"), dtype="category")}) # category
                  cols = df.select_dtypes('number').columns  # limits to a (float), b (int) and e (timedelta)
                  df_sub = df.loc[:, cols]
                  
                  
                  # OPTION 1: z-score filter: z-score < 3
                  lim = np.abs((df_sub - df_sub.mean()) / df_sub.std(ddof=0)) < 3
                  
                  # OPTION 2: quantile filter: discard 1% upper / lower values
                  lim = np.logical_or(df_sub < df_sub.quantile(0.99, numeric_only=False),
                                      df_sub > df_sub.quantile(0.01, numeric_only=False))
                  
                  # OPTION 3: iqr filter: within 2.22 IQR (equiv. to z-score < 3)
                  iqr = df_sub.quantile(0.75, numeric_only=False) - df_sub.quantile(0.25, numeric_only=False)
                  lim = np.abs((df_sub - df_sub.median()) / iqr) < 2.22
                  
                  
                  # replace outliers with nan
                  df.loc[:, cols] = df_sub.where(lim, np.nan)
                  

                  删除包含至少一个 nan-value 的所有行:

                  df.dropna(subset=cols, inplace=True) # drop rows with NaN in numerical columns
                  # or
                  df.dropna(inplace=True)  # drop rows with NaN in any column
                  

                  使用 pandas 1.3 函数:

                  【讨论】:

                  • 为了避免在非数字列中删除带有 NaN 的行,请使用 df.dropna(how='any', subset=cols, inplace=True)
                  猜你喜欢
                  • 1970-01-01
                  • 2019-03-22
                  • 1970-01-01
                  • 2018-10-08
                  • 1970-01-01
                  • 2016-06-20
                  • 2017-06-12
                  • 1970-01-01
                  相关资源
                  最近更新 更多