Первое ненулевое значение для каждой строки из списка столбцов Pandas

Если у меня есть DataFrame в pandas, который выглядит примерно так:

    A   B   C
0   1 NaN   2
1 NaN   3 NaN
2 NaN   4   5
3 NaN NaN NaN

Как я могу получить первое ненулевое значение из каждой строки? Например. для вышеизложенного я хотел бы получить: [1, 3, 4, None] (или эквивалентную серию).

Ответ 1

Это действительно бесполезный способ сделать это, сначала используйте first_valid_index, чтобы получить допустимые столбцы, преобразовать возвращенную строку в фреймворк данных, чтобы мы могли вызвать apply по-разному и использовать это для индексации обратно к исходному df

In [160]:
def func(x):
    if x.values[0] is None:
        return None
    else:
        return df.loc[x.name, x.values[0]]
pd.DataFrame(df.apply(lambda x: x.first_valid_index(), axis=1)).apply(func,axis=1)
​
Out[160]:
0     1
1     3
2     4
3   NaN
dtype: float64

ИЗМЕНИТЬ

Немного более чистый способ:

In [12]:
def func(x):
    if x.first_valid_index() is None:
        return None
    else:
        return x[x.first_valid_index()]
df.apply(func, axis=1)

Out[12]:
0     1
1     3
2     4
3   NaN
dtype: float64

Ответ 2

Вам не нужно возиться с first_valid_index:

df.bfill(axis=1).iloc[:, 0]

Ответ 3

Я собираюсь взвесить здесь, поскольку я думаю, что это намного быстрее, чем любой из предлагаемых методов. argmin дает индекс первого значения False в каждой строке результата np.isnan в векторном виде, что является трудной частью. Он все еще полагается на цикл Python для извлечения значений, но поиск очень быстрый:

def get_first_non_null(df):
    a = df.values
    col_index = np.isnan(a).argmin(axis=1)
    return [a[row, col] for row, col in enumerate(col_index)]

EDIT: Здесь полностью векторизованное решение, которое может быть намного быстрее, в зависимости от формы ввода. Обновленный бенчмаркинг ниже.

def get_first_non_null_vec(df):
    a = df.values
    n_rows, n_cols = a.shape
    col_index = np.isnan(a).argmin(axis=1)
    flat_index = n_cols * np.arange(n_rows) + col_index
    return a.ravel()[flat_index]

Если строка полностью равна нулю, то соответствующее значение также будет равно null. Здесь некоторый бенчмаркинг против решения unutbu:

df = pd.DataFrame(np.random.choice([1, np.nan], (10000, 1500), p=(0.01, 0.99)))
#%timeit df.stack().groupby(level=0).first().reindex(df.index)
%timeit get_first_non_null(df)
%timeit get_first_non_null_vec(df)
1 loops, best of 3: 220 ms per loop
100 loops, best of 3: 16.2 ms per loop
100 loops, best of 3: 12.6 ms per loop
In [109]:


df = pd.DataFrame(np.random.choice([1, np.nan], (100000, 150), p=(0.01, 0.99)))
#%timeit df.stack().groupby(level=0).first().reindex(df.index)
%timeit get_first_non_null(df)
%timeit get_first_non_null_vec(df)
1 loops, best of 3: 246 ms per loop
10 loops, best of 3: 48.2 ms per loop
100 loops, best of 3: 15.7 ms per loop


df = pd.DataFrame(np.random.choice([1, np.nan], (1000000, 15), p=(0.01, 0.99)))
%timeit df.stack().groupby(level=0).first().reindex(df.index)
%timeit get_first_non_null(df)
%timeit get_first_non_null_vec(df)
1 loops, best of 3: 326 ms per loop
1 loops, best of 3: 326 ms per loop
10 loops, best of 3: 35.7 ms per loop

Ответ 4

Вот еще один способ сделать это:

In [183]: df.stack().groupby(level=0).first().reindex(df.index)
Out[183]: 
0     1
1     3
2     4
3   NaN
dtype: float64

Идея здесь заключается в использовании stack для перемещения столбцов в уровень индекса строки:

In [184]: df.stack()
Out[184]: 
0  A    1
   C    2
1  B    3
2  B    4
   C    5
dtype: float64

Теперь, если вы группируете по первому уровню строки, то есть исходному индексу, и принимаете первое значение из каждой группы, вы, по существу, получаете желаемый результат:

In [185]: df.stack().groupby(level=0).first()
Out[185]: 
0    1
1    3
2    4
dtype: float64

Все, что нам нужно сделать, это переиндексировать результат (используя исходный индекс), чтобы включают строки, которые полностью NaN:

df.stack().groupby(level=0).first().reindex(df.index)

Ответ 5

Это ничего нового, но это комбинация лучших бит @yangie подхода со списком и @EdChum df.apply, который, как мне кажется, проще всего понять.

Во-первых, в каких столбцах мы хотим выбрать наши значения из?

In [95]: pick_cols = df.apply(pd.Series.first_valid_index, axis=1)

In [96]: pick_cols
Out[96]: 
0       A
1       B
2       B
3    None
dtype: object

Теперь, как мы выбираем значения?

In [100]: [df.loc[k, v] if v is not None else None 
    ....:     for k, v in pick_cols.iteritems()]
Out[100]: [1.0, 3.0, 4.0, None]

Это нормально, но мы действительно хотим, чтобы индекс соответствовал индексу исходного DataFrame:

In [98]: pd.Series({k:df.loc[k, v] if v is not None else None
   ....:     for k, v in pick_cols.iteritems()})
Out[98]: 
0     1
1     3
2     4
3   NaN
dtype: float64

Ответ 6

Вот однострочное решение:

[row[row.first_valid_index()] if row.first_valid_index() else None for _, row in df.iterrows()]

Edit:

Это решение выполняет итерацию по строкам df. row.first_valid_index() возвращает метку для первого значения, отличного от NA/null, которое будет использоваться как индекс для получения первого ненулевого элемента в каждой строке.

Если в строке нет ненулевого значения, row.first_valid_index() будет None, поэтому его нельзя использовать как индекс, поэтому мне нужен оператор if-else.

Я собрал все в список для краткости.

Ответ 7

Ответ JoeCondron (EDIT: до его последнего редактирования!) классно, но есть запас для значительного улучшения, избегая не-векторизации перечисления:

def get_first_non_null_vect(df):
    a = df.values
    col_index = np.isnan(a).argmin(axis=1)
    return a[np.arange(a.shape[0]), col_index]

Улучшение невелико, если DataFrame относительно плоский:

In [4]: df = pd.DataFrame(np.random.choice([1, np.nan], (10000, 1500), p=(0.01, 0.99)))

In [5]: %timeit get_first_non_null(df)
10 loops, best of 3: 34.9 ms per loop

In [6]: %timeit get_first_non_null_vect(df)
10 loops, best of 3: 31.6 ms per loop

... но может иметь значение для slim DataFrames:

In [7]: df = pd.DataFrame(np.random.choice([1, np.nan], (10000, 15), p=(0.1, 0.9)))

In [8]: %timeit get_first_non_null(df)
100 loops, best of 3: 3.75 ms per loop

In [9]: %timeit get_first_non_null_vect(df)
1000 loops, best of 3: 718 µs per loop

По сравнению с векторизованной версией JoeCondron время выполнения очень похоже (это немного быстрее для тонких DataFrames и немного медленнее для больших).