Есть ли более читаемый способ для каменноугольных колонн в pandas

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

def coalesce(values):
    not_none = (el for el in values if el is not None)
    return next(not_none, None)

df = pd.DataFrame([{'third':'B','first':'A','second':'C'},
                   {'third':'B','first':None,'second':'C'},
                   {'third':'B','first':None,'second':None},                   
                   {'third':None,'first':None,'second':None},
                   {'third':'B','first':'A','second':None}])

df['combo1'] = df.apply(coalesce, axis=1)
df['combo2'] = df[['second','third','first']].apply(coalesce, axis=1)
print df

Результаты

  first second third combo1 combo2
0     A      C     B      A      C
1  None      C     B      C      C
2  None   None     B      B      B
3  None   None  None   None   None
4     A   None     B      A      B

этот код работает (и результат - это то, что я хочу), но это не очень быстро.
Я хочу выбрать приоритеты, если мне нужно [['second', 'third', 'first']]

Совместите несколько подобно функции с тем же именем из tsql.
Я подозреваю, что я, возможно, пропустил простой способ добиться этого с хорошей производительностью на больших DataFrames (+400 000 строк)

Я знаю, что есть много способов заполнить недостающие данные, которые я часто использую на оси = 0 это то, что заставляет меня думать, что я, возможно, пропустил простой вариант для оси = 1

Можете ли вы предложить что-то приятнее/быстрее... или подтвердите, что это так хорошо, как оно есть.

Ответ 1

Вы можете использовать pd.isnull, чтобы найти нуль - в этом случае None - значения:

In [169]: pd.isnull(df)
Out[169]: 
   first second  third
0  False  False  False
1   True  False  False
2   True   True  False
3   True   True   True
4  False   True  False

а затем используйте np.argmin, чтобы найти индекс первого непустого значения. Если все значения равны нулю, np.argmin возвращает 0:

In [186]: np.argmin(pd.isnull(df).values, axis=1)
Out[186]: array([0, 1, 2, 0, 0])

Затем вы можете выбрать желаемые значения из df, используя NumPy integer-indexing:

In [193]: df.values[np.arange(len(df)), np.argmin(pd.isnull(df).values, axis=1)]
Out[193]: array(['A', 'C', 'B', None, 'A'], dtype=object)

Например,

import pandas as pd
df = pd.DataFrame([{'third':'B','first':'A','second':'C'},
                   {'third':'B','first':None,'second':'C'},
                   {'third':'B','first':None,'second':None},                   
                   {'third':None,'first':None,'second':None},
                   {'third':'B','first':'A','second':None}])

mask = pd.isnull(df).values
df['combo1'] = df.values[np.arange(len(df)), np.argmin(mask, axis=1)]
order = np.array([1,2,0])
mask = mask[:, order]
df['combo2'] = df.values[np.arange(len(df)), order[np.argmin(mask, axis=1)]]

дает

  first second third combo1 combo2
0     A      C     B      A      C
1  None      C     B      C      C
2  None   None     B      B      B
3  None   None  None   None   None
4     A   None     B      A      B

Использование argmin вместо df3.apply(coalesce, ...) значительно быстрее, если в DataFrame много строк:

df2 = pd.concat([df]*1000)

In [230]: %timeit mask = pd.isnull(df2).values; df2.values[np.arange(len(df2)), np.argmin(mask, axis=1)]
1000 loops, best of 3: 617 µs per loop

In [231]: %timeit df2.apply(coalesce, axis=1)
10 loops, best of 3: 84.1 ms per loop

Ответ 2

Pandas эквивалент COALESCE - это метод fillna():

result = column_a.fillna(column_b)

Результатом является столбец, в котором каждое значение берется из column_a, если этот столбец предоставляет ненулевое значение, в противном случае значение берется из column_b. Таким образом, ваш combo1 можно создать с помощью

df['first'].fillna(df['second']).fillna(df['third'])

даяние:

0       A
1       C
2       B
3    None
4       A

И ваш combo2 может быть создан с помощью:

(df['second']).fillna(df['third']).fillna(df['first'])

который возвращает новый столбец:

0       C
1       C
2       B
3    None
4       B

Если вам нужна эффективная операция под названием COALESCE, она может просто объединить столбцы с fillna() слева направо и затем вернуть результат:

def coalesce(df, column_names):
    i = iter(column_names)
    column_name = next(i)
    answer = df[column_name]
    for column_name in i:
        answer = answer.fillna(df[column_name])
    return answer

print coalesce(df, ['first', 'second', 'third'])
print coalesce(df, ['second', 'third', 'first'])

который дает:

0       A
1       C
2       B
3    None
4       A

0       C
1       C
2       B
3    None
4       B