Панды фрейма данных пользовательских вперед оптимизация fillna


У меня есть функция, которая синхронизирует по некоторым спецификации столбцов dataframe.
Функции работает, тем не менее, мне было интересно, как:

  • Улучшения выступлений
  • Сделать это более подходящие для Python

Пожалуйста, не стесняйтесь оставить какие-либо предложения.


Технические характеристики функция

  1. Входы:

    • df : в dataframe с колоннами:
      • [a0,...aN] : a0 для aN имена могут быть любыми допустимыми string и содержит numeric значения
      • [agent,date] : исправленные имена, agent содержит numeric значения и date содержит datetime.
    • sync_with : столбцы для синхронизации с (а string или listof string содержащиеся в [a0,..., aN] или, по умолчанию empty list чтобы синхронизировать все [a0,...,aN].
  2. Синхронизация:

    • Сделать вперед fillna сгруппированные по агенту значения.
    • Брось строки, в которых все столбцы для синхронизации со значениями остаются пустыми
  3. Возвращает : синхронизированные dataframe

Вот моя функция:

import pandas as pd
import numpy as np

def synchronize(df,sync_with=[]):
    _df = df.copy()

    if not isinstance(sync_with,list):
        sync_with = [sync_with]

    _fixed_cols = ['date','agent']
    _fixed_cols.extend(sync_with)
    _colset = [c for c in _df.columns if c not in _fixed_cols]

    for ag in _df.agent.unique():
        _df.loc[_df.agent==ag,_colset] = _df.loc[_df.agent==ag,_colset].fillna(method='ffill')
        if _sync_with:
            _df.loc[_df.agent==ag,:] = _df.loc[_df.agent==ag,:].dropna(how='all', subset=_sync_with)
            _df.loc[_df.agent==ag,:] = _df.loc[_df.agent==ag,:].fillna(method='ffill')

    return _df.dropna(how='all', subset=_sync_with)

Образец

foo = pd.DataFrame(dict(date=pd.to_datetime(['2010', '2011', '2012', '2013', '2010', '2013', '2015', '2016']),
                        agent=[1,1,1,1,2,2,2,2],
                        _a=[1, np.nan, np.nan, 4, 5, np.nan, 7, 8],
                        _b=[11, 22, np.nan, np.nan, 55, np.nan, 77, np.nan],
                        _c=[111, np.nan, 333, np.nan, np.nan, 666, 777, np.nan]))

Результаты

# 1. default (13 ms per loop)
print(synchronize(foo))
    _a    _b     _c  agent       date
0  1.0  11.0  111.0      1 2010-01-01
1  1.0  22.0  111.0      1 2011-01-01
2  1.0  22.0  333.0      1 2012-01-01
3  4.0  22.0  333.0      1 2013-01-01
4  5.0  55.0    NaN      2 2010-01-01
5  5.0  55.0  666.0      2 2013-01-01
6  7.0  77.0  777.0      2 2015-01-01
7  8.0  77.0  777.0      2 2016-01-01

# 2. sync with one column (35 ms per loop)
print(synchronize(foo,'_c'))
    _a    _b     _c  agent       date
0  1.0  11.0  111.0      1 2010-01-01
2  1.0  22.0  333.0      1 2012-01-01
5  5.0  55.0  666.0      2 2013-01-01
6  7.0  77.0  777.0      2 2015-01-01

# 3. sync with two columns (35 ms per loop)
print(synchronize(foo,['_a','_b']))
    _a    _b     _c  agent       date
0  1.0  11.0  111.0      1 2010-01-01
1  1.0  22.0  111.0      1 2011-01-01
3  4.0  22.0  333.0      1 2013-01-01
4  5.0  55.0    NaN      2 2010-01-01
6  7.0  77.0  777.0      2 2015-01-01
7  8.0  77.0  777.0      2 2016-01-01


Комментарии
1 ответ

несколько способов сделать это более подходящие для Python

'частная' переменные

переменная внутри функции ограничены рамками этой функции, так что добавляя их с _ ненужно

списки

sync_with также может быть другой контейнер, как tuple или так. Только когда это str вам нужно преобразовать его.

также следует помнить о том, что может пойти неправильно с изменяемым по умолчанию аргументы. Они создаются в момент определения функции. В этом случае они не дописываются в функции или возвращаются, так что это не должно вызвать проблем, но я стараюсь их избегать в любом случае

fixed_cols затем может быть собран такой:

fixed_cols = ['agent', 'date'] + list(sync_with)

Тогда только если sync_with представляет собой генератор, что-то может пойти не так, так что я бы сделать что-то вроде этого:

def synchronize(df, sync_with=None):
if sync_with is None:
sync_with = []
elif isinstance(sync_with, str):
sync_with = [sync_with, ]
else:
sync_with = list(sync_with)

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


  • Изготовление копии изначально не является необходимым, если вы будете выполнять операции на месте на df

  • Вы можете использовать DataFrame.groupby.transform, а затем выберите строки, которые имеют какое-либо значение, отличное от null в исходной ДФ

Вы хотели сделать что-то вроде этого:

def synchronize3(df,sync_with=[]):
if isinstance(sync_with, str):
sync_with = [sync_with, ]
else:
sync_with = list(sync_with)
result = df.groupby('agent').transform(lambda x: x.fillna(method='ffill'))
if sync_with:
result = result.loc[pd.notnull(df[sync_with]).any(axis=1), :]
result = result.assign(agent=df['agent']).reindex(columns=df.columns)
return result

Результаты:

%timeit synchronize(foo)


9.48 ms ± 527 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit synchronize(foo,'_c')


29 ms ± 817 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit synchronize(foo,['_a','_b'])


27.8 ms ± 608 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

assert synchronize(foo).equals(synchronize3(foo))
%timeit synchronize3(foo)


5.71 ms ± 935 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

_a _b _c agent date
0 1.0 11.0 111.0 1 2010-01-01
1 1.0 22.0 111.0 1 2011-01-01
2 1.0 22.0 333.0 1 2012-01-01
3 4.0 22.0 333.0 1 2013-01-01
4 5.0 55.0 NaN 2 2010-01-01
5 5.0 55.0 666.0 2 2013-01-01
6 7.0 77.0 777.0 2 2015-01-01
7 8.0 77.0 777.0 2 2016-01-01


assert synchronize(foo,'_c').equals(synchronize3(foo,'_c'))
%timeit synchronize3(foo,'_c')


6.41 ms ± 131 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

_a _b _c agent date
0 1.0 11.0 111.0 1 2010-01-01
2 1.0 22.0 333.0 1 2012-01-01
5 5.0 55.0 666.0 2 2013-01-01
6 7.0 77.0 777.0 2 2015-01-01


assert synchronize(foo,['_a','_b']).equals(synchronize3(foo,['_a','_b']))
%timeit synchronize3(foo,['_a','_b'])


7.33 ms ± 1.16 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

_a _b _c agent date
0 1.0 11.0 111.0 1 2010-01-01
1 1.0 22.0 111.0 1 2011-01-01
3 4.0 22.0 333.0 1 2013-01-01
4 5.0 55.0 NaN 2 2010-01-01
6 7.0 77.0 777.0 2 2015-01-01
7 8.0 77.0 777.0 2 2016-01-01


багфиксы

версия вашего алгоритма я использовал это:

def synchronize(df,sync_with=[]):
_df = df.copy()

if not isinstance(sync_with,list):
sync_with = [sync_with]

_fixed_cols = ['date','agent']
_fixed_cols.extend(sync_with)
_colset = [c for c in _df.columns if c not in _fixed_cols]

for ag in _df.agent.unique():
_df.loc[_df.agent==ag,_colset] = _df.loc[_df.agent==ag,_colset].fillna(method='ffill')
if sync_with:
_df.loc[_df.agent==ag,:] = _df.loc[_df.agent==ag,:].dropna(how='all', subset=sync_with)
_df.loc[_df.agent==ag,:] = _df.loc[_df.agent==ag,:].fillna(method='ffill')
if sync_with:
_df = _df.dropna(how='all', subset=sync_with)
return _df.astype({'agent': 'int64'}) # else the `dtype` is different from the one in my method, throwing of the assertions

1
ответ дан 21 марта 2018 в 09:03 Источник Поделиться