У меня есть 3 столбца, которые нужно взвешивать и суммировать. Однако иногда в этих колонках есть значения Nan, и это влияет на окончательный набор столбцов, которые взвешиваются и суммируются. Далее пример df:
import numpy as np
import pandas as pd
f = { 'A': [1, np.nan, 2, np.nan, 5, 6, np.nan],
'B': [np.nan, np.nan, 1, 1, 1, np.nan, 7],
'C': [np.nan, 2, 3, 6, np.nan, 5, np.nan]}
fd = pd.DataFrame(data = f)
fd.head(10)
A B C
0 1.0 NaN NaN
1 NaN NaN 2.0
2 2.0 1.0 3.0
3 NaN 1.0 6.0
4 5.0 1.0 NaN
5 6.0 NaN 5.0
6 NaN 7.0 NaN
Этот пример демонстрирует все возможные комбинации Nan в столбцах. Затем я хотел бы создать столбец F, который представляет собой взвешенную сумму столбцов A, B и C, когда они не являются Nan. Вот мой код:
def scaler(df):
"Scaling and summing"
if (pd.notnull(df['A']) == True & pd.notnull(df['B']) == True & pd.notnull(df['C']) == True):
return df['A']*0.5+df['B']*0.25+df['C']*0.25
elif (pd.notnull(df['A']) == True & pd.notnull(df['B']) == False & pd.notnull(df['C']) == False):
return df['A']*1
elif (pd.notnull(df['A']) == True & pd.notnull(df['B']) == True & pd.notnull(df['C']) == False):
return df['A']*0.75+df['B']*0.25
elif (pd.notnull(df['A']) == True & pd.notnull(df['B']) == False & pd.notnull(df['C']) == True):
return df['A']*0.75+df['C']*0.25
elif (pd.notnull(df['A']) == False & pd.notnull(df['B']) == True & pd.notnull(df['C']) == True):
return df['B']*0.5+df['C']*0.5
elif (pd.notnull(df['A']) == False & pd.notnull(df['B']) == True & pd.notnull(df['C']) == False):
return df['B']*1
else:
return df['C']*1
fd['F'] =fd.apply(scaler, axis = 'columns')
fd.head(10)
A B C F
0 1.0 NaN NaN NaN
1 NaN NaN 2.0 NaN
2 2.0 1.0 3.0 2.0
3 NaN 1.0 6.0 6.0
4 5.0 1.0 NaN NaN
5 6.0 NaN 5.0 5.0
6 NaN 7.0 NaN 7.0
Итак, я получаю df, где правильно взвешен и суммируется только столбцы со всеми тремя значениями, отличными от Nan. Если в одном из столбцов есть хотя бы один Nan, я получаю либо Nan, либо неверное результирующее значение в столбце F.
Чтобы преодолеть эту проблему, я заменил все значения Nan в исходном df некоторым плавающим, который выходит за пределы диапазона для всех столбцов, а затем представленная выше логика кода отлично работает. Мои вопросы:
1) почему это происходит (все значения Nan просматривают результаты, хотя столбцы, содержащие эти значения, не участвуют непосредственно в перестраиваемых формулах)?
2) Как я преодолел проблему, я нахожу немного отвратительной. Возможно ли более элегантное решение?
Вы не понимаете, как работает pd.DataFrame.apply
. Вдоль axis=1
каждая строка передается функции, а не весь кадр данных. Это полезно назвать соответствующим аргументом функции.
Вы работаете с скаляров не серии в пределах вашей функции, и должны использовать регулярные and
вместо &
. Также обратите внимание, что pd.isnull
существует, а также pd.notnull
. Таким образом, вы можете переписать следующим образом:
def scaler(row):
"Scaling and summing"
if pd.notnull(row['A']) and pd.notnull(row['B']) and pd.notnull(row['C']):
return row['A']*0.5 + row['B']*0.25 + row['C']*0.25
elif pd.notnull(row['A']) and pd.isnull(row['B']) and pd.isnull(row['C']):
return row['A']
elif pd.notnull(row['A']) and pd.notnull(row['B']) and pd.isnull(row['C']):
return row['A']*0.75 + row['B']*0.25
elif pd.notnull(row['A']) and pd.isnull(row['B']) and pd.notnull(row['C']):
return row['A']*0.75 + row['C']*0.25
elif pd.isnull(row['A']) and pd.notnull(row['B']) and pd.notnull(row['C']):
return row['B']*0.5 + row['C']*0.5
elif pd.isnull(row['A']) and pd.notnull(row['B']) and pd.isnull(row['C']):
return row['B']
else:
return row['C']
df['F'] = df.apply(scaler, axis=1)
Но это неэффективно для большого количества строк. Более эффективным и удобочитаемым является решение, использующее np.select
. Они используют только векторизованные операции. Обратите внимание, что мы вычисляем только один раз, проверяя, являются ли значения нулями в каждой серии.
a_null = df['A'].isnull()
b_null = df['B'].isnull()
c_null = df['C'].isnull()
conds = [~a_null & b_null & c_null,
a_null & ~b_null & c_null,
a_null & b_null & ~c_null,
~a_null & ~b_null & c_null,
~a_null & b_null & ~c_null,
a_null & ~b_null & ~c_null,
~a_null & ~b_null & ~c_null]
choices = [df['A'], df['B'], df['C'],
0.75 * df['A'] + 0.25 * df['B'],
0.75 * df['A'] + 0.25 * df['C'],
0.5 * df['B'] + 0.5 * df['C'],
0.5 * df['A'] + 0.25 * df['B'] + 0.25 * df['C']]
df['F'] = np.select(conds, choices)
Результат:
A B C F
0 1.0 NaN NaN 1.00
1 NaN NaN 2.0 2.00
2 2.0 1.0 3.0 2.00
3 NaN 1.0 6.0 3.50
4 5.0 1.0 NaN 4.00
5 6.0 NaN 5.0 5.75
6 NaN 7.0 NaN 7.00
df["F"] = df.mean(axis=1)
. Тогда нет необходимости быть оригинальным в именованной переменной. Кадр данных обычно называетсяdf
.df.mean
нет условия для средневзвешенного значения, чего хочет ОП