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

Это мой DataFrame:

import pandas as pd
import numpy as np
df = pd.DataFrame(
    {
        'x': [1, np.nan, 3, np.nan, 5],
        'y': [np.nan, 7, 8, 9, np.nan],
        'x_a': [1, 2, 3, 4, 5],
        'y_a': [6, 7, 8, 9, 10]

    }
)

Ожидаемый результат: fill_na столбцы x и y:

     x     y  x_a  y_a
0  1.0   6.0    1    6
1  2.0   7.0    2    7
2  3.0   8.0    3    8
3  4.0   9.0    4    9
4  5.0  10.0    5   10

По сути, я хочу заполнить xx_a и yy_a. Другими словами, каждый столбец должен быть связан с другим столбцом, имеющим суффикс _a и имя столбца.

Я могу получить этот вывод, используя этот код:

for col in ['x', 'y']:
    df[col] = df[col].fillna(df[f'{col}_a'])

Но мне интересно, является ли это лучшим/наиболее эффективным способом? Предположим, у меня есть сотни таких столбцов.

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

Ben.T 13.06.2024 12:36

@Ben.T спасибо за ответ. Очевидно, что если решение связано с проверкой, оно является более общим. Но это нормально в любом случае.

AmirX 13.06.2024 12:59

Будет ли у вас список cols со всеми столбцами? Для большого набора данных.

U13-Forward 13.06.2024 13:29

@ U13-Вперед, спасибо за ответ. Я могу получить список всех столбцов с помощью df.columns. Можно подробнее, если что-то не понятно?

AmirX 13.06.2024 14:28

@AmirX Я понял, все в порядке, ты можешь проверить ответы и узнать, помогут ли они.

U13-Forward 13.06.2024 14:30
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
11
5
663
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Ответ принят как подходящий

А как насчет использования индекса для выбора всех столбцов одновременно и set_axis для выравнивания DataFrame:

cols = pd.Index(['x', 'y'])
df[cols] = df[cols].fillna(df[cols+'_a'].set_axis(cols, axis=1))

Примечание. это предполагает, что все столбцы в cols и все столбцы «_a» существуют. Если вы не уверены, что можете быть в безопасности и можете использовать пересечение и переиндексацию:

cols = pd.Index(['x', 'y']).intersection(df.columns)
df[cols] = df[cols].fillna(df.reindex(columns=cols+'_a').set_axis(cols, axis=1))

Или для подхода, который полностью независим от явной передачи входных столбцов и просто полагается на суффикс (_a):

suffix = '_a'

# find columns "xyz" that have a "xyz_a" counterpart
c1 = df.columns.intersection(df.columns+suffix)
c2 = c1.str.removesuffix(suffix)
# select, fillna, update
df[c2] = df[c2].fillna(df[c1].set_axis(c2, axis=1))

Выход:

     x     y  x_a  y_a
0  1.0   6.0    1    6
1  2.0   7.0    2    7
2  3.0   8.0    3    8
3  4.0   9.0    4    9
4  5.0  10.0    5   10

Пример, для которого потребуется второй подход:

df = pd.DataFrame(
    {
        'x': [1, np.nan, 3, np.nan, 5],
        'z': [np.nan, 7, 8, 9, np.nan],
        'p_a': [1, 2, 3, 4, 5],
        'y_a': [6, 7, 8, 9, 10]

    }
)

Вы можете использовать фильтр , чтобы получить все столбцы с _a, создать список столбцов без _a. затем fillna с диктом.

cols_a = df.filter(like='_a').columns
cols = [_c.strip('_a') for _c in cols_a]

df = df.fillna({_c:df[_c_a] for _c, _c_a in zip(cols, cols_a)})
print(df)
#      x     y  x_a  y_a
# 0  1.0   6.0    1    6
# 1  2.0   7.0    2    7
# 2  3.0   8.0    3    8
# 3  4.0   9.0    4    9
# 4  5.0  10.0    5   10

Вы можете использовать df.combine_first:

cols = ['x', 'y']

df[cols] = df[cols].combine_first(
    df.filter(regex='_a$').rename(lambda x: x.rstrip('_a'), axis=1)
    )

Выход:

     x     y  x_a  y_a
0  1.0   6.0    1    6
1  2.0   7.0    2    7
2  3.0   8.0    3    8
3  4.0   9.0    4    9
4  5.0  10.0    5   10

Если вы ожидаете дополнительных суффикс-столбцов (например, z_a), добавьте [cols] на всякий случай:

df[cols] = df[cols].combine_first(
    df.filter(regex='_a$').rename(lambda x: x.rstrip('_a'), axis=1)
    )[cols]

Подход также должен работать, если один из ваших столбцов не соответствует варианту суффикса. То есть:

df = pd.DataFrame(
    {
        'x': [1, np.nan, 3, np.nan, 5],
        'y': [np.nan, 7, 8, 9, np.nan],
        'x_a': [1, 2, 3, 4, 5],
        # 'y_a': [6, 7, 8, 9, 10],
        'z_a': [11, 12, 13, 14, 15]
    }
)

Выход:

     x    y  x_a  z_a
0  1.0  NaN    1   11
1  2.0  7.0    2   12
2  3.0  8.0    3   13
3  4.0  9.0    4   14
4  5.0  NaN    5   15

Обновлено: сценарий без предустановленного списка столбцов (cols). В этом случае вы можете начать со столбцов суффиксов:

df = (df
      .filter(regex='_a$')
      .rename(lambda x: x.rstrip('_a'), axis=1)
      .combine_first(df)[df.columns]
      )

Это хорошо (+1), но это может потерпеть неудачу, если у вас есть дополнительные столбцы _a. Я бы посоветовал reindex(columns=cols) после combine_first, если вы хотите быть в безопасности ;)

mozway 13.06.2024 12:48

@mozway: хорошая мысль. Я полагаю, добавление [cols] должно помочь в этом сценарии, не так ли? Типа: df[cols].combine_first(other)[cols], или это будет как-то дорого?

ouroboros1 13.06.2024 12:54

Честно говоря, я не знаю, быстрее ли нарезка или reindex. Если нарезать, то первое можно опустить [cols] ;)

mozway 13.06.2024 13:11

@mozway: да, я это понял (что могу опустить первый [cols]), но задаюсь аналогичным вопросом: df.combine_first(other)[cols] против df[cols].combine_first(other)[cols] с точки зрения производительности;)? Посмотрим, смогу ли я найти время позже, чтобы проверить эти вещи. еще раз спасибо

ouroboros1 13.06.2024 13:14

Если у вас много дополнительных столбцов, df[cols].combine_first(other)[cols] будет использовать меньше промежуточной памяти.

mozway 13.06.2024 13:15

Вы уверены, что в ОП есть список со всеми столбцами?'

U13-Forward 13.06.2024 13:26

Более эффективный подход:

a_cols = pd.Series(cols).add('_a')
df[cols] = df[cols].fillna(df.reindex(a_cols, axis=1).set_axis(cols, axis=1).dropna(how='all', axis=1))

Короче, но менее эффективно:

df.apply(lambda x: x.fillna(value=df.get(x.name[0] + '_a', x)))

Выход:

     x     y  x_a  y_a
0  1.0   6.0    1    6
1  2.0   7.0    2    7
2  3.0   8.0    3    8
3  4.0   9.0    4    9
4  5.0  10.0    5   10

Тестирование с использованием dataframe с разными значениями:

df = pd.DataFrame(
    {
        'x': [1, np.nan, 3, np.nan, 5],
        'y': [np.nan, 7, 8, 9, np.nan],
        'x_a': [1, 2, 3, 4, 5],
        # 'y_a': [6, 7, 8, 9, 10],
        'z_a': [11, 12, 13, 14, 15]
    }
)

Выход:

     x    y  x_a  z_a
0  1.0  NaN    1   11
1  2.0  7.0    2   12
2  3.0  8.0    3   13
3  4.0  9.0    4   14
4  5.0  NaN    5   15

Это даже хуже оригинального подхода, не так ли? Вы не только перебираете все столбцы, но даже обрабатываете те, у которых нет аналога, заполняя их самими собой.

mozway 13.06.2024 13:30

@mozway Проверьте мои изменения, кстати, спасибо за ваш вклад!

U13-Forward 13.06.2024 13:44

@mozway снова отредактировано

U13-Forward 13.06.2024 13:55

Это, наверное, лучше, но почти идентично моему сейчас подходу;)

mozway 13.06.2024 14:00

@mozway Ах, я только что посмотрел, да, я не видел твоего подхода раньше, в любом случае, честная игра, чувак. +1 для тебя.

U13-Forward 13.06.2024 14:01

Другое возможное решение, использующее np.where:

df.loc[:, 'x':'y'] = np.where(
    df.loc[:, 'x':'y'].isna(), df.loc[:, 'x_a':'y_a'], df.loc[:, 'x':'y'])

Используя df.loc[:, 'x':'y'] и df.loc[:, 'x_a':'y_a'], мы также могли бы использовать fillna. Однако столбцы в каждом из двух блоков должны быть смежными.

Выход:

     x     y  x_a  y_a
0  1.0   6.0    1    6
1  2.0   7.0    2    7
2  3.0   8.0    3    8
3  4.0   9.0    4    9
4  5.0  10.0    5   10
import pandas as pd
import numpy as np


df = pd.DataFrame(
    {
        'x': [1, np.nan, 3, np.nan, 5],
        'y': [np.nan, 7, 8, 9, np.nan],
        'x_a': [1, 2, 3, 4, 5],
        'y_a': [6, 7, 8, 9, 10]
    }
)

# Identify the columns to fill and their corresponding '_a' columns
suffix = '_a'
cols = pd.Index([col for col in df.columns if not col.endswith(suffix)])
print(cols) 
#cols_a = cols + suffix
#print(cols_a)
cols_a =  np.array([col for col in df.columns if col.endswith(suffix)])
# Fill NaN values in the original columns with values from the '_a' columns
df[cols] = df[cols].combine_first(df[cols_a].set_axis(cols,axis=1))
print(df)
'''
   x     y  x_a  y_a
0  1.0   6.0    1    6
1  2.0   7.0    2    7
2  3.0   8.0    3    8
3  4.0   9.0    4    9
4  5.0  10.0    5   10
'''
df.drop(columns = cols_a, inplace = True)
print(df)
'''
    x     y
0  1.0   6.0
1  2.0   7.0
2  3.0   8.0
3  4.0   9.0
4  5.0  10.0
'''

Другие вопросы по теме