Создание столбца на основе столбцов, которые могут содержать значения Nan в пандах

1

У меня есть 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) Как я преодолел проблему, я нахожу немного отвратительной. Возможно ли более элегантное решение?

  • 1
    Привет, добро пожаловать на ТАК. Я бы посоветовал вам прочитать небольшое введение в панд, потому что то, что вы хотите получить, легко получить с помощью df["F"] = df.mean(axis=1) . Тогда нет необходимости быть оригинальным в именованной переменной. Кадр данных обычно называется df .
  • 1
    @ user32185 - в df.mean нет условия для средневзвешенного значения, чего хочет ОП
Показать ещё 3 комментария
Теги:
pandas
dataframe
numpy

1 ответ

1
Лучший ответ

Вы не понимаете, как работает 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
  • 1
    Спасибо! Это именно то, что я хотел бы видеть: элегантно и читабельно! Большое спасибо!

Ещё вопросы

Сообщество Overcoder
Наверх
Меню