Как игнорировать минимальное и максимальное значение в группе при вычислении средневзвешенного значения по группе в Pandas

#python #pandas #pandas-groupby

#python #pandas #pandas-groupby

Вопрос:

У меня есть фрейм данных, который выглядит следующим образом

 pd.DataFrame({'A': ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9', 'C10'],
  ...:                    'B': ['A', 'A', 'A', 'B', 'B', 'B', 'B', 'C', 'C', 'C'],
  ...:                    'W': [0.5, 0.2, 0.3, 0.2, 0.1, 0.4, 0.3, 0.4, 0.5, 0.1],
  ...:                    'V': [9, 1, 7, 4, 3, 5, 2, 6, 8, 10]})
Out[9]: 
     A  B    W   V
0   C1  A  0.5   9
1   C2  A  0.2   1
2   C3  A  0.3   7
3   C4  B  0.2   4
4   C5  B  0.1   3
5   C6  B  0.4   5
6   C7  B  0.3   2
7   C8  C  0.4   6
8   C9  C  0.5   8
9  C10  C  0.1  10
  

Я хочу вычислить средневзвешенное значение по группе в столбце ‘B’, игнорируя минимальное и максимальное значения (столбец ‘V’), где

столбец W = вес

столбец V = значение

Чтобы вычислить простое среднее значение для каждой группы с учетом всех значений, я могу сделать это:

 df['mean'] = df.groupby('B').apply(lambda x: (x.V * (x.W / x.W.sum())).sum()).reindex(df.B).values
print(df)
     A  B    W   V  mean
0   C1  A  0.5   9   6.8
1   C2  A  0.2   1   6.8
2   C3  A  0.3   7   6.8
3   C4  B  0.2   4   3.7
4   C5  B  0.1   3   3.7
5   C6  B  0.4   5   3.7
6   C7  B  0.3   2   3.7
7   C8  C  0.4   6   7.4
8   C9  C  0.5   8   7.4
9  C10  C  0.1  10   7.4
  

Однако я хочу игнорировать максимальное и минимальное значения в каждой группе, чтобы вычислить среднее значение по группе. результат должен выглядеть следующим образом

      A  B    W   V  meanNoMinMax
0   C1  A  0.5   9   7.0
1   C2  A  0.2   1   7.0
2   C3  A  0.3   7   7.0
3   C4  B  0.2   4   3.666667
4   C5  B  0.1   3   3.666667
5   C6  B  0.4   5   3.666667
6   C7  B  0.3   2   3.666667
7   C8  C  0.4   6   8.0
8   C9  C  0.5   8   8.0
9  C10  C  0.1  10   8.0
  

Как я могу достичь этого с помощью 1 строки (или очень небольшого количества строк) кода?

Логические

минимальное и максимальное значение в V, игнорируемое для каждой группы, даст следующую таблицу для вычисления среднего значения, игнорирующего минимальное и максимальное значение для группы

      A  B    W   V
1   C3  A  0.3   7
3   C4  B  0.2   4
4   C5  B  0.1   3
8   C9  C  0.5   8
  

Комментарии:

1. почему V == 1 сохраняется там?

2. спасибо, что указали. Я отредактировал свой вопрос.

3. если у вас есть две строки с минимальным (или максимальным) значением, не могли бы вы рассмотреть их оба или только одно из вхождений?

Ответ №1:

Добавляем условия и исправляем ваш код

 df['mean'] = df.groupby('B').apply(lambda x: (x.V * (x.W[(x.V!=x.V.max()) amp; (x.V!=x.V.min())] / x.W[(x.V!=x.V.max()) amp; (x.V!=x.V.min())].sum())).sum()).reindex(df.B).values
df
Out[293]: 
     A  B    W   V      mean
0   C1  A  0.5   9  7.000000
1   C2  A  0.2   1  7.000000
2   C3  A  0.3   7  7.000000
3   C4  B  0.2   4  3.666667
4   C5  B  0.1   3  3.666667
5   C6  B  0.4   5  3.666667
6   C7  B  0.3   2  3.666667
7   C8  C  0.4   6  8.000000
8   C9  C  0.5   8  8.000000
9  C10  C  0.1  10  8.000000
  

Ответ №2:

Создайте mask количество строк для исключения, затем выполните умножения и groupby transform суммы. Если вам нужно исключить только один экстремум в случае нескольких строк, привязанных к экстремальным значениям, вам нужно только изменить m .

 # Exclude all rows that are max or min within group
m = (df['V'].eq(df.groupby('B')['V'].transform('max'))
     | df['V'].eq(df.groupby('B')['V'].transform('min')))

# For ties, if only need to exclude the single largest/smallest:
#df = df.sort_values('V')
#m = ~df.duplicated('B') | ~df.duplicated('B', keep='last')

df['avg'] = (df['W'].mul(df['V']).mask(m).groupby(df['B']).transform('sum')
                .div(df['W'].mask(m).groupby(df['B']).transform('sum')))
  

      A  B    W   V       avg
0   C1  A  0.5   9  7.000000
1   C2  A  0.2   1  7.000000
2   C3  A  0.3   7  7.000000
3   C4  B  0.2   4  3.666667
4   C5  B  0.1   3  3.666667
5   C6  B  0.4   5  3.666667
6   C7  B  0.3   2  3.666667
7   C8  C  0.4   6  8.000000
8   C9  C  0.5   8  8.000000
9  C10  C  0.1  10  8.000000
  

Ответ №3:

В качестве альтернативы, с помощью еще нескольких строк кода, я думаю, что это также может сработать 🙂

 max_per_group = df.index.isin(df.groupby("B")['V'].idxmax().values.tolist())
min_per_group = df.index.isin(df.groupby("B")['V'].idxmin().values.tolist())
df["mean"] = df.loc[(~df.index.isin(max_per_group)) amp; (~df.index.isin(min_per_group))].groupby('B').apply(lambda x: (x.V * (x.W / x.W.sum())).sum()).reindex(df.B).values