【问题标题】:Winsorizing on column with NaN does not change the max value使用 NaN 对列进行 Winsorizing 不会更改最大值
【发布时间】:2021-03-14 13:05:42
【问题描述】:

请注意,不久前有人问过类似的问题,但从未得到回答(请参阅Winsorizing does not change the max value)。

我正在尝试使用来自scipy.stats.mstatswinsorizewinsorize 数据框中的一列。如果列中没有 NaN 值,则该过程正常运行。

但是,NaN 值似乎会阻止进程在分布的顶部(但不是底部)工作。无论我为nan_policy 设置什么值,NaN 值都设置为分布中的最大值。我觉得必须以某种方式错误地设置选项。

下面是一个示例,可用于在没有 NaN 值时重现正确的 Winsorizing 以及在存在 NaN 值时我遇到的问题行为。任何有关解决此问题的帮助将不胜感激。

#Import
import pandas as pd
import numpy as np
from scipy.stats.mstats import winsorize

# initialise data of lists.
data = {'Name':['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T'], 'Age':[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0]}
 
# Create 2 DataFrames
df = pd.DataFrame(data)
df2 = pd.DataFrame(data)

# Replace two values in 2nd DataFrame with np.nan
df2.loc[5,'Age'] = np.nan
df2.loc[8,'Age'] = np.nan

# Winsorize Age in both DataFrames
winsorize(df['Age'], limits=[0.1, 0.1], inplace = True, nan_policy='omit')
winsorize(df2['Age'], limits=[0.1, 0.1], inplace = True, nan_policy='omit')

# Check min and max values of Age in both DataFrames
print('Max/min value of Age from dataframe without NaN values')
print(df['Age'].max())
print(df['Age'].min())

print()

print('Max/min value of Age from dataframe with NaN values')
print(df2['Age'].max())
print(df2['Age'].min())

【问题讨论】:

  • 看起来nan_policy 没有实际工作,因为它仍然是'propagates' 的值,最后df2['Age'] 填充了所有值。 Winsorization 只不过是clipping,所以你可以用 pandas 轻松处理这个问题,这似乎正确地忽略了NaN

标签: python pandas dataframe numpy scipy


【解决方案1】:

我使用以下代码作为我问题的基础(而我需要每年进行一次winsorize,所以我在我的玩具数据中引入了两个类别(A,B))

由于 NaN 而没有替换最大 p99 值,我遇到了同样的问题。

import pandas as pd
import numpy as np

# Getting the toy data

# To see all columns and 100 rows
pd.options.display.max_columns = None
pd.set_option('display.max_rows', 100)

df = pd.DataFrame({"Zahl":np.arange(100),"Group":[i for i in "A"*50+"B"*50]})

# Getting NaN Values for first 4 rows

df.loc[0:3,"Zahl"] = np.NaN

# Defining a grouped list of 99/1% percentile values

p99 = df.groupby("Group")["Zahl"].quantile(.9).rename("99%-Quantile")
p1 =  df.groupby("Group")["Zahl"].quantile(.1).rename("1%-Quantile")

# Defining the winsorize function

def winsor(value,p99,p1):
    
    if (value < p99) & (value > p1):
        return value
    elif (value > p99) & (value > p1):
        return p99
    elif (value < p99) & (value < p1):
        return p1
    else:
        return value
    
df["New"] = df.apply(lambda row: winsor(row["Zahl"],p99[row["Group"]],p1[row["Group"]]),axis=1)


winsor 函数的好处是它自然会忽略 NaN 值!

希望这个想法对您的问题有所帮助

【讨论】:

    【解决方案2】:

    可以考虑在列中用mean填充缺失值,然后winsorize,只选择原来的非nan

    df2 = pd.DataFrame(data)
    
    # Replace two values in 2nd DataFrame with np.nan
    df2.loc[5,'Age'] = np.nan
    df2.loc[8,'Age'] = np.nan
    
    # mask of non nan
    _m = df2['Age'].notna()
    df2.loc[_m, 'Age'] = winsorize(df2['Age'].fillna(df2['Age'].mean()), limits=[0.1, 0.1])[_m]
    print(df2['Age'].max())
    print(df2['Age'].min())
    # 18.0
    # 3.0
    

    或通过删除winsorize之前的nan来选择其他选项。

    df2.loc[_m, 'Age'] = winsorize(df2['Age'].loc[_m], limits=[0.1, 0.1])
    print(df2['Age'].max())
    print(df2['Age'].min())
    # 19.0
    # 2.0
    

    【讨论】:

      【解决方案3】:

      看起来nan_policy 被忽略了。但是winsorization只是裁剪,所以你可以用pandas来处理。

      def winsorize_with_pandas(s, limits):
          """
          s : pd.Series
              Series to winsorize
          limits : tuple of float
              Tuple of the percentages to cut on each side of the array, 
              with respect to the number of unmasked data, as floats between 0. and 1
          """
          return s.clip(lower=s.quantile(limits[0], interpolation='lower'), 
                        upper=s.quantile(1-limits[1], interpolation='higher'))
      
      
      winsorize_with_pandas(df['Age'], limits=(0.1, 0.1))
      0      3.0
      1      3.0
      2      3.0
      3      4.0
      4      5.0
      5      6.0
      6      7.0
      7      8.0
      8      9.0
      9     10.0
      10    11.0
      11    12.0
      12    13.0
      13    14.0
      14    15.0
      15    16.0
      16    17.0
      17    18.0
      18    18.0
      19    18.0
      Name: Age, dtype: float64
      
      winsorize_with_pandas(df2['Age'], limits=(0.1, 0.1))
      0      2.0
      1      2.0
      2      3.0
      3      4.0
      4      5.0
      5      NaN
      6      7.0
      7      8.0
      8      NaN
      9     10.0
      10    11.0
      11    12.0
      12    13.0
      13    14.0
      14    15.0
      15    16.0
      16    17.0
      17    18.0
      18    19.0
      19    19.0
      Name: Age, dtype: float64
      

      【讨论】:

        猜你喜欢
        • 2018-11-09
        • 2019-10-07
        • 2017-03-06
        • 1970-01-01
        • 2021-10-11
        • 2020-12-31
        • 2012-10-09
        • 1970-01-01
        相关资源
        最近更新 更多