2015-08-05 2 views
12

Если у меня есть DataFrame в панде, который выглядит примерно так:Первое непустое значения для каждой строки из списка столбцов пандов

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

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

ответ

7

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

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 

EDIT

немного уборщик путь:

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 
+0

Использование 'df' как глобальное здесь делает меня немного тошнотворным. На самом деле вам нужна комбинация ответа [@ yangjie] (http://stackoverflow.com/a/31828559/2071807) и этого. Я собираюсь опубликовать комбинацию, но не стесняйтесь просто украсть лучшие биты (если вы считаете, что они есть!) – LondonRob

+0

@LondonRob Я думаю, что на самом деле он должен просто перебирать df один раз – EdChum

+0

* Теперь * вы говорите , Теперь это действительно хорошо. +1 (почему бы просто не стереть вашу первую итерацию?) – LondonRob

2

Her е однострочный решение:

[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.

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

+1

Это замечательно. Немного о том, что происходит, сделало бы это действительно полезным ответом. Кроме того, вам стыдно проверять 'first_valid_index()' дважды. Возможно, если вы потеряете немного краткости, вы получите читаемость и эффективность. – LondonRob

+0

Да, я не доволен получением 'first_valid_index()' дважды. Спасибо за совет, возможно, я уточню позже. – yangjie

4

Это ничего нового, но это комбинация лучших бит @yangie's approach со списком, и @EdChum's df.apply approach, что я считаю самым легким для понимания.

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

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 
+0

@ AndyHayden Я как бы удивлен, нет никакого способа перейти от 'pick_cols' к окончательному результату. Как бы вы назвали эту операцию? – LondonRob

+0

Я обновил свой ответ, это повторяется только один раз, вам нужно проверить, является ли 'first_valid_index'' None' обрабатывать строку со всеми 'NaN' – EdChum

7

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

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) 
9

Я буду весить здесь, как я думаю, что это хорошее дело быстрее, чем любой из предложенных методов. 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 
+1

. Из всех этих ответов это самый быстрый из них из 5 или 10. – user1367204

+0

Небольшая коррекция нужна в return = [a [строка, col] для строки, col в перечислении (col_index)] – user1367204

+0

Исправлено. Спасибо, что указали, что вне – JoeCondron

2

JoeCondron's answer (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 

... но может иметь значение на стройных 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, и немного медленнее для больших).

13

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

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

Удивительное решение моей проблемы. Благодарю. Как получить имя столбца первого ненулевого значения? – RajeshM

+0

это умный! – MaxU

+0

awesome !! спасибо +1 –

Смежные вопросы