2016-07-19 5 views
7

ich eine Pandas Datenrahmen habe:Finden ersten von Null verschiedenen Wert in jeder Zeile von Pandas 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 

Und ich mag eine Serie (keine Liste) der ersten Nicht-Null zurück Wert in jeder Zeile. Dies funktioniert derzeit aber lookup gibt eine Liste anstelle einer Serie (ich weiß, dass ich die Liste in eine Serie umwandeln kann), aber ich gehe davon aus, es gibt einen besseren Weg:

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

[ 2. 1. 13.] 

ich .apply verwenden können, aber ich möchte vermeiden es.

Antwort

6

versuchen Sie dies:

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

alles, was ich tue, ist, alle Nicht-Nullen mit nan s ersetzt und dann von rechts zu füllen, die alle sich ergebenden Werte in der ersten Spalte zwingt, sei der erste Wert ungleich null in der Zeile.

oder eine schnellere Art und Weise, wie @piRSquared vorgeschlagen:

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

so eine gute Antwort. Ich arbeite immer noch an meinem. aber ich hätte 'df.replace (0, np.nan) .bfill (1) .iloc [:, 0]' – piRSquared

+0

@piRSquared: Ja, ich hatte etwas ähnliches, 'df.mask (df == 0). bfill (1) .iloc [:, 0] ', wurde aber gerade knapp geschlagen! – root

+0

gerade lief '%% timeit' mein Vorschlag nimmt 3. die Zeit, aber es ist das gleiche Konzept. Es macht mir nichts aus, wenn Sie es in Ihre Antwort aufnehmen. – piRSquared

2

Ich bin mir nicht sicher, dass ich das "besser" nennen würde. Aber es gibt eine Serie in einem einzigen Liner zurück.

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

Ja, ich versuche, mit zu vermeiden 'apply' – slaw

3

@ Antwort acushner das ist besser. Einfach das da draußen hinstellen.

Verwendung idxmax und 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 

Dies funktioniert auch:

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

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

Haben sie nicht etwas in Pandas für ausgefallene Indizierung, so etwas wie 'df [np.arange (3), m]'? Oder das macht keinen Sinn, weil das das '2D' Format für Datenrahmen nicht behält? – Divakar

+0

@Divakar Der nächste ist der, den OP verwendet hat [df.lookup] (http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.lookup.html). – ayhan

5

Dies scheint zu funktionieren:

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

Auch gut, aber nicht so schnell. +1 – piRSquared

+0

Ja, dass man Indexierung verwendet - ich würde erwarten, dass es schneller ist. – ayhan

0

Hier ist eine sehr schnelle Art und Weise mit .apply und .nonzero()

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

Leistung:

%%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) 
Verwandte Themen