Объединить значения из 2 столбцов в один столбец в фрейме pandas

Я ищу метод, который ведет себя аналогично объединению в T-SQL. У меня есть 2 столбца (столбцы A и B), которые редко заполняются в фрейме pandas. Я хотел бы создать новый столбец, используя следующие правила:

  • Если значение в столбце A не равно null, используйте это значение для нового столбца C
  • Если значение в столбце A равно null, используйте значение в столбце B для нового столбца C

Как я уже упоминал, это можно выполнить в MS SQL Server с помощью функции coalesce. Я не нашел для этого хорошего питонического метода; существует ли?

Ответ 1

используйте comb_first():

In [16]: df = pd.DataFrame(np.random.randint(0, 10, size=(10, 2)), columns=list('ab'))

In [17]: df.loc[::2, 'a'] = np.nan

In [18]: df
Out[18]:
     a  b
0  NaN  0
1  5.0  5
2  NaN  8
3  2.0  8
4  NaN  3
5  9.0  4
6  NaN  7
7  2.0  0
8  NaN  6
9  2.0  5

In [19]: df['c'] = df.a.combine_first(df.b)

In [20]: df
Out[20]:
     a  b    c
0  NaN  0  0.0
1  5.0  5  5.0
2  NaN  8  8.0
3  2.0  8  2.0
4  NaN  3  3.0
5  9.0  4  9.0
6  NaN  7  7.0
7  2.0  0  2.0
8  NaN  6  6.0
9  2.0  5  2.0

Ответ 2

Попробуйте это также... проще запомнить:

df['c'] = np.where(df["a"].isnull(), df["b"], df["a"] )

Это немного быстрее: df['c'] = np.where(df["a"].isnull() == True, df["b"], df["a"] )

%timeit df['d'] = df.a.combine_first(df.b)
1000 loops, best of 3: 472 µs per loop


%timeit  df['c'] = np.where(df["a"].isnull(), df["b"], df["a"] )
1000 loops, best of 3: 291 µs per loop

Ответ 3

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

Случай № 1: не взаимоисключающие NaN

Не все строки имеют NaN, и они NaN не являются взаимоисключающими между столбцами.

df = pd.DataFrame({
    'a': [1.0, 2.0, 3.0, np.nan, 5.0, 7.0, np.nan],
    'b': [5.0, 3.0, np.nan, 4.0, np.nan, 6.0, 7.0]})      
df

     a    b
0  1.0  5.0
1  2.0  3.0
2  3.0  NaN
3  NaN  4.0
4  5.0  NaN
5  7.0  6.0
6  NaN  7.0

Пусть сначала объединятся на a.

Series.mask

df['a'].mask(pd.isnull, df['b'])
# df['a'].mask(df['a'].isnull(), df['b'])
0    1.0
1    2.0
2    3.0
3    4.0
4    5.0
5    7.0
6    7.0
Name: a, dtype: float64

Series.where

df['a'].where(pd.notnull, df['b'])

0    1.0
1    2.0
2    3.0
3    4.0
4    5.0
5    7.0
6    7.0
Name: a, dtype: float64

Вы можете использовать похожий синтаксис, используя np.where.

В качестве альтернативы, чтобы сначала объединить на b, измените условия.


Случай № 2: взаимно исключающие позиционированные NaN

Все строки имеют NaN, которые являются взаимоисключающими между столбцами.

df = pd.DataFrame({
    'a': [1.0, 2.0, 3.0, np.nan, 5.0, np.nan, np.nan],
    'b': [np.nan, np.nan, np.nan, 4.0, np.nan, 6.0, 7.0]})
df

     a    b
0  1.0  NaN
1  2.0  NaN
2  3.0  NaN
3  NaN  4.0
4  5.0  NaN
5  NaN  6.0
6  NaN  7.0

Series.update

Этот метод работает на месте, изменяя оригинальный DataFrame. Это эффективный вариант для этого варианта использования.

df['b'].update(df['a'])
# Or, to update "a" in-place,
# df['a'].update(df['b'])
df

     a    b
0  1.0  1.0
1  2.0  2.0
2  3.0  3.0
3  NaN  4.0
4  5.0  5.0
5  NaN  6.0
6  NaN  7.0

Series.add

df['a'].add(df['b'], fill_value=0)

0    1.0
1    2.0
2    3.0
3    4.0
4    5.0
5    6.0
6    7.0
dtype: float64

DataFrame.fillna + DataFrame.sum

df.fillna(0).sum(1)

0    1.0
1    2.0
2    3.0
3    4.0
4    5.0
5    6.0
6    7.0
dtype: float64

Ответ 4

Я столкнулся с этой проблемой, но хотел объединить несколько столбцов, выбирая первый ненулевой из нескольких столбцов. Я нашел следующее полезное:

Создание фиктивных данных

import pandas as pd
df = pd.DataFrame({'a1': [None, 2, 3, None],
                   'a2': [2, None, 4, None],
                   'a3': [4, 5, None, None],
                   'a4': [None, None, None, None],
                   'b1': [9, 9, 9, 999]})

df
    a1   a2   a3    a4   b1
0  NaN  2.0  4.0  None    9
1  2.0  NaN  5.0  None    9
2  3.0  4.0  NaN  None    9
3  NaN  NaN  NaN  None  999

объединить a1 a2, a3 в новый столбец A

def get_first_non_null(dfrow, columns_to_search):
    for c in columns_to_search:
        if pd.notnull(dfrow[c]):
            return dfrow[c]
    return None

# sample usage:
cols_to_search = ['a1', 'a2', 'a3']
df['A'] = df.apply(lambda x: get_first_non_null(x, cols_to_search), axis=1)

print(df)
    a1   a2   a3    a4   b1    A
0  NaN  2.0  4.0  None    9  2.0
1  2.0  NaN  5.0  None    9  2.0
2  3.0  4.0  NaN  None    9  3.0
3  NaN  NaN  NaN  None  999  NaN

Ответ 5

Я думаю, что такое решение,

def coalesce(s: pd.Series, *series: List[pd.Series]):
    """coalesce the column information like a SQL coalesce."""
    for other in series:
        s = s.mask(pd.isnull, other)        
    return s

потому что, имея DataFrame со столбцами с ['a', 'b', 'c'], вы можете использовать его как объединение SQL,

df['d'] = coalesce(df.a, df.b, df.c)