web-dev-qa-db-fra.com

Coalescent les valeurs de 2 colonnes dans une seule colonne dans un pandas dataframe

Je recherche une méthode qui se comporte de manière similaire à la fusion dans T-SQL. J'ai 2 colonnes (colonnes A et B) peu peuplées dans une trame de données pandas. Je voudrais créer une nouvelle colonne en utilisant les règles suivantes:

  1. Si la valeur de la colonne A n'est pas nulle , utilisez cette valeur pour la nouvelle colonne C
  2. Si la valeur de la colonne A ( est nulle , utilisez la valeur de la colonne B pour la nouvelle colonne C

Comme je l'ai mentionné, cela peut être accompli dans MS SQL Server via la fonction coalesce. Je n'ai pas trouvé de bonne méthode Pythonic pour cela; existe-t-il?

33
Sevyns

utilisez combine_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
65
MaxU

Essayez aussi ... plus facile à retenir:

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

C'est un peu plus rapide: 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
12
Merlin

combine_first est l'option la plus simple. Il y en a quelques autres que je décris ci-dessous. Je vais décrire quelques autres solutions, certaines applicables à différents cas.

Cas n ° 1: NaN non mutuellement exclusifs

Toutes les lignes n'ont pas de NaN, et ces NaN sont pas s'excluent mutuellement entre les colonnes.

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

Combinons d'abord sur 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

Vous pouvez utiliser une syntaxe similaire en utilisant np.where.

Alternativement, pour combiner d'abord sur b, changez les conditions.


Cas n ° 2: NaN positionnés mutuellement exclusifs

Toutes les lignes ont des NaN qui s'excluent mutuellement entre les colonnes.

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

Cette méthode fonctionne sur place, modifiant le DataFrame d'origine. Il s'agit d'une option efficace pour ce cas d'utilisation.

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
10
cs95

J'ai rencontré ce problème avec mais je voulais fusionner plusieurs colonnes, en choisissant le premier non nul de plusieurs colonnes. J'ai trouvé les informations suivantes utiles:

Construire des données factices

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

fusionner a1 a2, a3 dans une nouvelle colonne 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
0
David Smith

Je pense à une solution comme ça,

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

car étant donné un DataFrame avec des colonnes avec ['a', 'b', 'c'], vous pouvez l'utiliser comme une fusion SQL,

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