Неявные объединения (что и называется вашим первым запросом) становятся намного более запутанными, трудными для чтения и сложными для поддержания, когда вам нужно начать добавлять в ваш запрос больше таблиц. Представьте, что вы делаете тот же запрос и тип соединения в четырех или пяти разных таблицах ... это кошмар.
Использование явного соединения (ваш второй пример) гораздо читабельнее и прост в обслуживании.
Ответ 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 и немного медленнее для больших).
Я собираюсь взвесить здесь, поскольку я думаю, что это намного быстрее, чем любой из предлагаемых методов. 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
groupby
в axis=1
Если мы передаем вызываемый, который возвращает то же значение, мы группируем все столбцы вместе. Это позволяет нам использовать groupby.agg
, который дает нам метод first
, который делает этот простой
df.groupby(lambda x: 'Z', 1).first()
Z
0 1.0
1 3.0
2 4.0
3 NaN
. Это возвращает фреймворк с именем столбца вещи, которую я возвращал в своем вызываемом
lookup
, notna
и idxmax
df.lookup(df.index, df.notna().idxmax(1))
array([ 1., 3., 4., nan])
argmin
и нарезка v = df.values
v[np.arange(len(df)), np.isnan(v).argmin(1)]
array([ 1., 3., 4., nan])
Вот еще один способ сделать это:
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)