Выигрыш в столбце с NaN не меняет максимальное значение

avatar
hallque
15 декабря 2021 в 20:06
346
3
3

Обратите внимание, что аналогичный вопрос был задан некоторое время назад, но на него так и не ответили (см. Выигрыш не изменяет максимальное значение).

Я пытаюсь winsorize столбца в кадре данных, используя winsorize из scipy.stats.mstats. Если в столбце нет значений NaN, процесс работает правильно.

Однако значения NaN, похоже, не позволяют процессу работать в верхней (но не в нижней) части распределения. Независимо от того, какое значение я установил для nan_policy, значения NaN устанавливаются на максимальное значение в распределении. Я чувствую, что должен как-то неправильно установить параметр.

Ниже приведен пример, который можно использовать для воспроизведения как правильного винсоринга, когда нет значений NaN, так и проблемного поведения, с которым я сталкиваюсь, когда присутствуют значения 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())
Источник
ALollz
15 декабря 2021 в 20:27
0

похоже, что nan_policy на самом деле не работает, так как он все еще 'propagates' значения и, в конце концов, df2['Age'] все значения заполнены. Winsorization — это не что иное, как clipping, поэтому вы можете легко справиться с этим с помощью панд, которые, кажется, правильно игнорируют NaN

Ответы (3)

avatar
ALollz
15 декабря 2021 в 20:34
2

Похоже, nan_policy игнорируется. Но винсоризация — это просто отсечение, так что вы можете справиться с этим с пандами.

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
avatar
Velicious
23 декабря 2021 в 11:09
1

Я использовал следующий фрагмент кода в качестве основы для своей проблемы (принимая во внимание, что мне нужно было ежегодно выигрывать, поэтому я ввел две категории (A,B) в свои игрушечные данные)

У меня возникла та же проблема с заменой максимальных значений p99 из-за NaN.

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!

Надеюсь, эта идея поможет решить вашу проблему

avatar
Ben.T
15 декабря 2021 в 20:39
1

Можно рассмотреть возможность заполнения отсутствующих значений с помощью 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

или другой вариант, удалив nan перед winsorize.

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