Самый эффективный способ сортировки массива по бинам, указанным индексным массивом?

1
задан Paul Panzer 18 March 2019 в 18:48
поделиться

1 ответ

Вот несколько решений:

  1. В любом случае используйте np.argsort, ведь это быстро скомпилированный код.

  2. Используйте np.bincount, чтобы получить размеры бинов, и np.argpartition, что составляет O(n) для фиксированного количества бинов. Недостаток: в настоящее время нет стабильного алгоритма, поэтому мы должны отсортировать каждую ячейку.

  3. Использовать scipy.ndimage.measurements.labeled_comprehension. Это примерно соответствует тому, что требуется, но не знаю, как это реализовано.

  4. Использовать pandas. Я полный pandas нуб, так что то, что я здесь собрал, используя groupby, может быть неоптимальным.

  5. Использование scipy.sparse переключения между форматами сжатых разреженных строк и сжатых разреженных столбцов позволяет реализовать именно ту операцию, которую мы ищем.

  6. Используйте pythran (я уверен, что numba также работает) в цикличном коде в вопросе. Все, что требуется, это вставить в начало после импорта numpy

.

#pythran export sort_to_bins(int[:], float[:], int)

, а затем компилировать

# pythran stb_pthr.py

Тесты 100 бинов, переменное количество элементов:

enter image description here [1118]

[1137 ] Возьмите домой:

Если вы согласны с numba / pythran, то это путь, если нет scipy.sparse, то масштабируется довольно хорошо.

Код:

import numpy as np
from scipy import sparse
from scipy.ndimage.measurements import labeled_comprehension
from stb_pthr import sort_to_bins as sort_to_bins_pythran
import pandas as pd

def sort_to_bins_pandas(idx, data, mx=-1):
    df = pd.DataFrame.from_dict(data=data)
    out = np.empty_like(data)
    j = 0
    for grp in df.groupby(idx).groups.values():
        out[j:j+len(grp)] = data[np.sort(grp)]
        j += len(grp)
    return out

def sort_to_bins_ndimage(idx, data, mx=-1):
    if mx==-1:
        mx = idx.max() + 1
    out = np.empty_like(data)
    j = 0
    def collect(bin):
        nonlocal j
        out[j:j+len(bin)] = np.sort(bin)
        j += len(bin)
        return 0
    labeled_comprehension(data, idx, np.arange(mx), collect, data.dtype, None)
    return out

def sort_to_bins_partition(idx, data, mx=-1):
    if mx==-1:
        mx = idx.max() + 1
    return data[np.argpartition(idx, np.bincount(idx, None, mx)[:-1].cumsum())]

def sort_to_bins_partition_stable(idx, data, mx=-1):
    if mx==-1:
        mx = idx.max() + 1
    split = np.bincount(idx, None, mx)[:-1].cumsum()
    srt = np.argpartition(idx, split)
    for bin in np.split(srt, split):
        bin.sort()
    return data[srt]

def sort_to_bins_sparse(idx, data, mx=-1):
    if mx==-1:
        mx = idx.max() + 1    
    return sparse.csr_matrix((data, idx, np.arange(len(idx)+1)), (len(idx), mx)).tocsc().data

def sort_to_bins_argsort(idx, data, mx=-1):
    return data[idx.argsort(kind='stable')]

from timeit import timeit
exmpls = [np.random.randint(0, K, (N,)) for K, N in np.c_[np.full(16, 100), 1<<np.arange(5, 21)]]

timings = {}
for idx in exmpls:
    data = np.arange(len(idx), dtype=float)
    ref = None
    for x, f in (*globals().items(),):
        if x.startswith('sort_to_bins_'):
            timings.setdefault(x.replace('sort_to_bins_', '').replace('_', ' '), []).append(timeit('f(idx, data, -1)', globals={'f':f, 'idx':idx, 'data':data}, number=10)*100)
            if x=='sort_to_bins_partition':
                continue
            if ref is None:
                ref = f(idx, data, -1)
            else:
                assert np.all(f(idx, data, -1)==ref)

import pylab
for k, v in timings.items():
    pylab.loglog(1<<np.arange(5, 21), v, label=k)
pylab.xlabel('#items')
pylab.ylabel('time [ms]')
pylab.legend()
pylab.show()
0
ответ дан Paul Panzer 18 March 2019 в 18:48
поделиться
Другие вопросы по тегам:

Похожие вопросы: