Более быстрые коллекции. Работа в стиле «как у панд» - PullRequest
2 голосов
/ 13 апреля 2019

Привет! В настоящее время я делаю следующее, чтобы найти все уникальные предметы в нескольких сериях панд:

In [44]: data = [Series([1,2,7,4]), Series([2,5,3,1]), Series([3, 2, 4])]
In [45]: counts = Counter(chain.from_iterable(data))
In [46]: unique_occurrences = [item for item, count in counts.items() if count == 1]
In [47]: unique_occurrences
Out[47]: [7, 5]

Есть ли способ ускорить это, поскольку реальные данные велики.

Спасибо.

Отзывы об ответах

Код:

def uniq_0(data):       # Original
    counts = Counter(chain.from_iterable(data))
    return [item for item, count in counts.items() if count == 1]

def uniq_1(data):       # Divakar #1
    a = np.concatenate(data)
    unq,c = np.unique(a, return_counts=1)
    return unq[c==1]

def uniq_2(data):       # Divakar #2
    a = np.concatenate(data)
    return np.flatnonzero(np.bincount(a)==1)

def uniq_3(data):       # Divakar #3
    counts = Counter(chain.from_iterable(data))
    k = np.array(list(counts.keys()))
    v = np.array(list(counts.values()))
    return k[v==1]

def uniq_4(data):       # Divakar #4
    L = max([i.max() for i in data])+1
    return np.flatnonzero(np.sum([np.bincount(i,minlength=L) 
                                  for i in data],axis=0)==1)

def uniq_5(data):       # Divakar #5
    L = max([i.max() for i in data])+1
    sums = np.zeros(L,dtype=int)
    for i in data:
        sums += np.bincount(i,minlength=L)
    return np.flatnonzero(sums==1)

def uniq_6(data):       # Erfan 
    v = pd.concat(data).value_counts()
    return v.index[v == 1]

if __name__ == '__main__':
    data = [Series([1,2,7,4]), Series([2,5,3,1]), Series([3, 2, 4])]
    funcs = [uniq_0, uniq_1, uniq_2, uniq_3, uniq_4, uniq_5, uniq_6]
    answers = [f(data) for f in funcs]
    golden = set(answers[0])
    check = [set(a) == golden for a in answers]
    for n, a in enumerate(answers):
        if set(a) != golden:
            print(f'  Error with uniq_{n}(data)')
        else:
            print(f'  Confirmed uniq_{n}(data) == golden')

Сессия Spyder:

  Confirmed uniq_0(data) == golden
  Confirmed uniq_1(data) == golden
  Confirmed uniq_2(data) == golden
  Confirmed uniq_3(data) == golden
  Confirmed uniq_4(data) == golden
  Confirmed uniq_5(data) == golden
  Confirmed uniq_6(data) == golden

In [73]: # 1000 Series. Averaging 10000.0 ints/Series. 405 ints unique.

In [74]: for f in funcs:
    ...:     print(f.__name__, end=': ')
    ...:     %timeit -r 3 f(data2)
uniq_0: 2.21 s ± 18.5 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)
uniq_1: 465 ms ± 2.5 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)
uniq_2: 126 ms ± 215 µs per loop (mean ± std. dev. of 3 runs, 10 loops each)
uniq_3: 2.22 s ± 48.6 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)
uniq_4: 1.12 s ± 10.8 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)
uniq_5: 374 ms ± 1.28 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)
uniq_6: 831 ms ± 20.6 ms per loop (mean ± std. dev. of 3 runs, 1 loop each)

In [75]: 

Комментарий

Большое спасибо тебе.Мои фактические данные больше, но они не помещаются на этом ноутбуке, но я чувствую, что теперь у меня есть достаточно вариантов, чтобы по-настоящему это зафиксировать. Еще раз спасибо !

Ответы [ 2 ]

3 голосов
/ 13 апреля 2019

Подход № 1

Вот один массив на основе NumPy -

a = np.concatenate(data)
unq,c = np.unique(a, return_counts=1)
out = unq[c==1]

Подход № 2 (для положительных целых данных)

Для положительных целых данных мы можем использовать np.bincount, чтобы получить out непосредственно из a -

out = np.flatnonzero(np.bincount(a)==1) # a from app#1

Подход № 3

Если мы хотим использовать counts, который мы могли бы предпочесть при работе с очень большим числом серий, так как в этом сценарии конкатенация может быть медленнее -

k = np.array(list(counts.keys()))
v = np.array(list(counts.values()))
out = k[v==1]

Подход № 4 (для положительных целых данных)

С большим числом рядов, содержащих положительные целые числа, мы можем использовать bincount для каждого и, таким образом, избежать конкатенации -

L = max([i.max() for i in data])+1
out = np.flatnonzero(np.sum([np.bincount(i,minlength=L) for i in data],axis=0)==1)

Подход № 5 (для целых положительных данных)

Это может быть еще больше улучшено с точки зрения эффективности использования памяти, вот так -

L = max([i.max() for i in data])+1
sums = np.zeros(L,dtype=int)
for i in data:
    sums += np.bincount(i,minlength=L)
out = np.flatnonzero(sums==1)
2 голосов
/ 13 апреля 2019

Мы можем использовать pd.concat в сочетании с value_counts и применять логическое индексирование:

v = pd.concat(data).value_counts()
v.index[v == 1].to_numpy()

, что дает

array([7, 5], dtype=int64)

Примечание
Если ваша версия для панд <0.24.0, используйте вместо: </p>

v.index[v == 1].values

Подробнее здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...