Обновление нескольких столбцов pandas с помощью одной пользовательской функции

#python #pandas

#python #pandas

Вопрос:

Мне нужно очистить довольно много столбцов в фрейме данных pandas; поэтому я определил и использовал несколько функций с помощью метода apply для столбцов фрейма данных.

Фиктивный пример:

 def fn_a(x):
    if x<50:
        return 'OK'
    else:
        return 'not OK'

def fn_b(x):
    if x<=40:
        return 'too small'
    elif x>40 and x<70:
        return 'just right'
    else:
        return 'too high'
    
df = pd.DataFrame(np.random.randint(0, 100, size=(5, 2)), columns=['a','b'])
df['a'] = df['a'].apply(fn_a)
df['b'] = df['b'].apply(fn_b)
 

Есть ли способ применить только одну функцию, т. Е. Определить Одну fn() функцию и передать ее в метод apply вместо перехода по столбцам по столбцам? Другими словами, что я должен добавить fn , чтобы

 def fn(x):
    ...

df = df.apply(fn)
 

достаточно?

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

1. Вам лучше использовать векторизацию для повышения производительности….

2. Но ваши две функции разные, как вы можете программно закодировать это в одну функцию? Или вы хотите объединить оба fn_a и fn_b в одну функцию с помощью оператора if?

3. Для функции в примере вы не должны использовать apply вообще. Достаточно ли близок пример к вашим реальным функциям?

4. @DYZ Они похожи по духу; в основном объединяют кучу разных значений под определенным количеством общих терминов. Должен ли я использовать np.where здесь? Почему бы и нет apply ?

5. @itaishz да, мой вопрос заключается в объединении нескольких операторов if-else (специфичных для конкретных столбцов) в рамках одной функции и применении ее ко всему фрейму данных.

Ответ №1:

Воспроизводимые данные:

 np.random.seed(0)
df = pd.DataFrame(np.random.randint(0, 100, size=(5, 2)), columns=['a','b'])
 
     a   b
0  44  47
1  64  67
2  67   9
3  83  21
4  36  87
 

Использование pandas применяется:

Если вы действительно хотите использовать apply для этого, вы можете установить axis=1 и получить имя столбца с x[colname] указанием, где x находится ваша текущая строка:

 def fn_a(x):
    x['a'] = 'OK' if x['a'] < 50 else 'not OK'
    if x['b'] <= 40:
        x['b'] = 'too small'
    elif x['b'] > 40 and x['b'] < 70:
        x['b'] = 'just right'
    else:
        x['b'] = 'too high'
    return x

df = df.apply(fn_a, axis=1)
print(df)
 

Вывод:

         a           b
0      OK  just right
1  not OK  just right
2  not OK   too small
3  not OK   too small
4      OK    too high
 

Использование векторизованного подхода:

Вы можете рассмотреть возможность использования np.where и np.select . Кроме того, взгляните на pd.cut . Вы можете установить функцию для обновления фрейма данных на месте с помощью:

 def fn(df):
    df['a'] = np.where(df.a.lt(50), 'OK', 'not OK')
    df['b'] = np.select(
        condlist=[df.b.le(40), df.b.gt(40) amp; df.b.lt(70)],
        choicelist=['too small', 'just right'],
        default='too high'
    )
    
fn(df)
print(df)
 

Выход:

         a           b
0      OK  just right
1  not OK  just right
2  not OK   too small
3  not OK   too small
4      OK    too high
 

Если вы не хотите изменять фрейм данных на месте, создайте копию внутри функции, измените скопированный фрейм данных, а затем верните его:

 def fn(df):
    df = df.copy()
    df['a'] = np.where(df.a.lt(50), 'OK', 'not OK')
    df['b'] = np.select(
        condlist=[df.b.le(40), df.b.gt(40) amp; df.b.lt(70)],
        choicelist=['too small', 'just right'],
        default='too high'
    )
    return df

df = fn(df)
print(df)
 

которая возвращает тот же результат.

Ответ №2:

Попробуйте:

 def fn(x1, x2):
    return [fn_a(x1), fn_b(x2)]

df[['c', 'd']] = df.apply(lambda row: fn(row.a, row.b), axis=1).values.tolist()