Самый быстрый способ отбросить строки / получить подмножество с отличием от большого DataFrame в Pandas - PullRequest
0 голосов
/ 20 ноября 2018

Вопрос

Я ищу самый быстрый способ отбросить набор строк, индексы которых у меня есть, или получить подмножество разности этих индексов (что приводит к тому же набору данных) избольшой Pandas DataFrame.

Пока у меня есть два решения, которые кажутся мне относительно медленными:

  1. df.loc[df.difference(indices)]

    , что занимает ~ 115 сек.на моем наборе данных

  2. df.drop(indices)

    , что занимает ~ 215 с на моем наборе данных

Есть ли более быстрый способсделай это?Предпочтительно в Pandas.

Производительность предлагаемых решений

Ответы [ 3 ]

0 голосов
/ 20 ноября 2018

Используя iloc (или loc, см. Ниже) и Series.drop:

df = pd.DataFrame(np.arange(0, 1000000, 1))
indices = np.arange(0, 1000000, 3)

%timeit -n 100 df[~df.index.isin(indices)]
%timeit -n 100 df.iloc[df.index.drop(indices)]

41.3 ms ± 997 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
32.7 ms ± 1.06 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

Как указывает @jezrael, вы можете толькоиспользуйте iloc, если index является RangeIndex, в противном случае вам придется использовать loc.Но это все же быстрее, чем df[df.isin()] (см. Почему ниже).

Все три варианта на 10 миллионов строк:

df = pd.DataFrame(np.arange(0, 10000000, 1))
indices = np.arange(0, 10000000, 3)

%timeit -n 10 df[~df.index.isin(indices)]
%timeit -n 10 df.iloc[df.index.drop(indices)]
%timeit -n 10 df.loc[df.index.drop(indices)]

4.98 s ± 76.8 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
752 ms ± 51.3 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
2.65 s ± 69.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Почему супер медленный loc превзойти boolean_indexing?

Ну, краткий ответ, что это не так.df.index.drop(indices) намного быстрее, чем ~df.index.isin(indices) (приведенные выше данные с 10 миллионами строк):

%timeit -n 10 ~df.index.isin(indices)
%timeit -n 10 df.index.drop(indices)

4.55 s ± 129 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
388 ms ± 10.8 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Мы можем сравнить это с производительностью boolean_indexing против iloc против loc:

boolean_mask = ~df.index.isin(indices)
dropped_index = df.index.drop(indices)

%timeit -n 10 df[boolean_mask]
%timeit -n 10 df.iloc[dropped_index]
%timeit -n 10 df.loc[dropped_index]


489 ms ± 25.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
371 ms ± 10.6 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
2.38 s ± 153 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
0 голосов
/ 20 ноября 2018

Если порядок строк не имеет значения, вы можете расположить их на месте:

n=10**7
df=pd.DataFrame(arange(4*n).reshape(n,4))
indices=np.unique(randint(0,n,size=n//2))

from numba import njit
@njit
def _dropfew(values,indices):
    k=len(values)-1
    for ind in indices[::-1]:
            values[ind]=values[k]
            k-=1

def dropfew(df,indices):
    _dropfew(df.values,indices)
    return df.iloc[:len(df)-len(indices)]

Запуски:

In [39]: %time df.iloc[df.index.drop(indices)]
Wall time: 1.07 s

In [40]: %time dropfew(df,indices)
Wall time: 219 ms
0 голосов
/ 20 ноября 2018

Я полагаю, что вы можете создать логическую маску, инвертировать по ~ и фильтровать по boolean indexing:

df1 = df[~df.index.isin(indices)]

Как упомянуто @ user3471881 для избежания цепной индексации, если вы планируетепри манипулировании отфильтрованным df позже необходимо добавить copy:

df1 = df[~df.index.isin(indices)].copy()

Эта фильтрация зависит от количества совпадающих индексов, а также от длины DataFrame.

Итак, другоеВозможное решение - создать array/list индексов для хранения, а затем инвертировать не нужно:

df1 = df[df.index.isin(need_indices)]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...