2016-07-19 4 views
7

У меня есть панды DataFrame:Найти первое ненулевое значение в каждой строке панд DataFrame

import pandas as pd 

df = pd.DataFrame([[0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], 
        [1.0, 0.0, 1.0, 3.0, 1.0, 1.0, 7.0, 0.0], 
        [0.0, 0.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0] 
        ] 
        , columns=['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']) 

    A B  C  D  E  F  G  H 
0 0.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 
1 1.0 0.0 1.0 3.0 1.0 1.0 7.0 0.0 
2 0.0 0.0 13.0 14.0 15.0 16.0 17.0 18.0 

И я хотел бы вернуть серию (не список) первого ненулевого значение в каждой строке. Это в настоящее время работает, но lookup возвращает список вместо серии (я знаю, что могу преобразовать список в серии), но я предполагаю, что есть лучший способ:

first_nonzero_colnames = (df > 0).idxmax(axis=1, skipna=True) 
df.lookup(first_nonzero_colnames.index, first_nonzero_colnames.values) 

[ 2. 1. 13.] 

я могу использовать .apply, но я хочу, чтобы избежать Это.

ответ

6

попробовать это:

res = df[df != 0.0].bfill(axis=1)['A'] 

все, что я делаю это замена всех не-нулей с nan с, а затем заполняя их с правой стороны, что заставляет все полученные значения в первом столбце быть первым ненулевым значением в строке.

или, более быстрый способ, как это было предложено @piRSquared:

df.replace(0, np.nan).bfill(1).iloc[:, 0] 
+1

Такого хорошего ответа. Я все еще работаю над собой. но я бы 'df.replace (0, np.nan) .bfill (1) .iloc [:, 0]' – piRSquared

+0

@piRSquared: Да, у меня было что-то подобное, 'df.mask (df == 0). bfill (1) .iloc [:, 0] ', но был просто избит! – root

+0

просто побежал '%% timeit' мое предложение занимает 3-е место, но это та же концепция. Я не против, если вы включите его в свой ответ. – piRSquared

2

Я не уверен, что я бы назвал это «лучше». Но он возвращает серию в один лайнер.

df.apply(lambda x: x[np.where(x > 0)[0][0]], axis=1) 
>>> 
0  2.0 
1  1.0 
2 13.0 
dtype: float64 
+0

Да, я стараюсь избегать использования 'apply' – slaw

3

@ ответ acushner является лучше. Просто поместите это там.

использование idxmax и apply

m = (df != 0).idxmax(1) 
df.T.apply(lambda x: x[m[x.name]]) 

0  2.0 
1  1.0 
2 13.0 
dtype: float64 

Это также работает:

m = (df != 0).idxmax(1) 
t = zip(m.index, m.values) 

df.stack().loc[t].reset_index(1, drop=True) 
+0

Не у них что-то в пандах для фантазии индексирования, так что-то вроде 'df [np.arange (3), m]'? Или это не имеет смысла, потому что это не сохранит формат '2D' для данных? – Divakar

+0

@ Divakar. Ближайшим является тот, который использовал OP [df.lookup] (http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.lookup.html). – ayhan

5

Это похоже на работу:

df[df!=0].cumsum(axis=1).min(axis=1) 
Out[74]: 
0  2.0 
1  1.0 
2 13.0 
dtype: float64 
+0

Также хорошо, но не так быстро. +1 – piRSquared

+0

Да, это использует индексирование - я ожидаю, что это будет быстрее. – ayhan

0

Вот очень быстрый способ использования .Не и .nonzero()

df2.apply(lambda x: x.iloc[x.nonzero()[0][0]], axis=1) 
>>> 
0  2.0 
1  1.0 
2 13.0 
dtype: float64 

производительность:

%%timeit 
df2.apply(lambda x: x.iloc[x.nonzero()[0][0]], axis=1) 
>>> 
190 µs ± 8.18 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 
Смежные вопросы