Средние повторяющиеся значения из двух парных списков в Python с использованием NumPy

В прошлом я сталкивался с тем, что имел дело с усреднением двух парных lists, и я успешно использовал предоставленные там ответы.

Однако с большими (более 20 000) элементами процедура несколько медленная, и мне было интересно, ускорит ли ее использование NumPy.

Я начинаю с двух списков, одного из чисел с плавающей запятой и одного из строк:

names = ["a", "b", "b", "c", "d", "e", "e"]
values = [1.2, 4.5, 4.3, 2.0, 5.67, 8.08, 9.01]

Я пытаюсь вычислить среднее значение одинаковых значений, чтобы после его применения я получил:

result_names = ["a", "b", "c", "d", "e"]
result_values = [1.2, 4.4, 2.0, 5.67, 8.54]

В качестве примера я поместил два списка, но было бы достаточно и списка из (name, value) кортежей:

result = [("a", 1.2), ("b", 4.4), ("d", 5.67), ("e", 8.54)]

Как лучше всего это сделать с помощью NumPy?


person Einar    schedule 17.10.2011    source источник


Ответы (4)


С numpy вы можете написать что-то самостоятельно или использовать функциональность groupby (функция rec_groupby из matplotlib.mlab, но она намного медленнее. Для более мощной функциональности groupby, возможно, посмотрите pandas), и я сравнил его с ответом Майкла Данна со словарем:

import numpy as np
import random
from matplotlib.mlab import rec_groupby

listA = [random.choice("abcdef") for i in range(20000)]
listB = [20 * random.random() for i in range(20000)]

names = np.array(listA)
values = np.array(listB)

def f_dict(listA, listB):
    d = {}

    for a, b in zip(listA, listB):
        d.setdefault(a, []).append(b)

    avg = []
    for key in d:
        avg.append(sum(d[key])/len(d[key]))

    return d.keys(), avg

def f_numpy(names, values):
    result_names = np.unique(names)
    result_values = np.empty(result_names.shape)

    for i, name in enumerate(result_names):
        result_values[i] = np.mean(values[names == name])

    return result_names, result_values     

Это результат для трех:

In [2]: f_dict(listA, listB)
Out[2]: 
(['a', 'c', 'b', 'e', 'd', 'f'],
 [9.9003182717213765,
  10.077784850173568,
  9.8623915728699636,
  9.9790599744319319,
  9.8811096512807097,
  10.118695410115953])

In [3]: f_numpy(names, values)
Out[3]: 
(array(['a', 'b', 'c', 'd', 'e', 'f'], 
      dtype='|S1'),
 array([  9.90031827,   9.86239157,  10.07778485,   9.88110965,
         9.97905997,  10.11869541]))

In [7]: rec_groupby(struct_array, ('names',), (('values', np.mean, 'resvalues'),))
Out[7]: 
rec.array([('a', 9.900318271721376), ('b', 9.862391572869964),
       ('c', 10.077784850173568), ('d', 9.88110965128071),
       ('e', 9.979059974431932), ('f', 10.118695410115953)], 
      dtype=[('names', '|S1'), ('resvalues', '<f8')])

И кажется, что numpy немного быстрее для этого теста (и предопределенная функция groupby намного медленнее):

In [32]: %timeit f_dict(listA, listB)
10 loops, best of 3: 23 ms per loop

In [33]: %timeit f_numpy(names, values)
100 loops, best of 3: 9.78 ms per loop

In [8]: %timeit rec_groupby(struct_array, ('names',), (('values', np.mean, 'values'),))
1 loops, best of 3: 203 ms per loop
person joris    schedule 17.10.2011
comment
Так что похоже, что numpy того стоит: если ваш скрипт сделает это 150 раз, решение dict вызовет задержку ~ 2 секунды. - person Michael Dunn; 17.10.2011
comment
Но замечание, в таймингах я не учел преобразование списка в массив numpy. И это может компенсировать небольшой выигрыш по времени с numpy (я тестировал его в случае выше, и тогда у f_numpy почти такая же скорость: 19,3 мс). Так что, возможно, это зависит от того, нужно ли вам каждый раз преобразовывать список в массив numpy. - person joris; 17.10.2011
comment
Что касается моих тестов, я не вижу такого большого влияния на массив списка конверсий ->, но, по общему признанию, я не проводил всесторонних сравнений между двумя версиями. - person Einar; 17.10.2011
comment
Способ Numpy на самом деле намного медленнее, на порядки, если вы увеличиваете количество уникальных ключей. В вашем примере всего 6 уникальных ключей. Когда их больше, f_dict бьет из воды. - person johnzilla; 25.04.2017

Возможно, решение numpy более сложное, чем вам нужно. Не делая ничего необычного, я обнаружил, что следующее «быстро как вспышка» (например, не было заметного ожидания с 20000 элементов в списке):

import random

listA = [random.choice("abcdef") for i in range(20000)]
listB = [20 * random.random() for i in range(20000)]

d = {}

for a, b in zip(listA, listB):
    d.setdefault(a, []).append(b)

for key in d:
    print key, sum(d[key])/len(d[key])

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

person Michael Dunn    schedule 17.10.2011
comment
Я должен был упомянуть об этом, вы правы: я делаю это около 150 раз, а средняя длина составляет около 20 КБ. - person Einar; 17.10.2011

Простое решение через numpy, предполагая, что vA0 и vB0 являются numpy.arrays, которые индексируются vA0.

import numpy as np

def avg_group(vA0, vB0):
    vA, ind, counts = np.unique(vA0, return_index=True, return_counts=True) # get unique values in vA0
    vB = vB0[ind]
    for dup in vB[counts>1]: # store the average (one may change as wished) of original elements in vA0 reference by the unique elements in vB
        vB[np.where(vA==dup)] = np.average(vB0[np.where(vA0==dup)])
    return vA, vB
person Gabriel S. Gusmão    schedule 15.09.2015
comment
Это можно немного почистить, но здесь самым простым кажется метод unique. - person nedlrichards; 11.03.2020

Несколько поздно для вечеринки, но, поскольку в numpy по-прежнему отсутствует эта функция, вот моя лучшая попытка создать чистое решение numpy для группировки по ключу. Это должно быть намного быстрее, чем другие предлагаемые решения для наборов задач значительного размера. Суть здесь в изящной функциональности reduceat.

import numpy as np

def group(key, value):
    """
    group the values by key
    returns the unique keys, their corresponding per-key sum, and the keycounts
    """
    #upcast to numpy arrays
    key = np.asarray(key)
    value = np.asarray(value)
    #first, sort by key
    I = np.argsort(key)
    key = key[I]
    value = value[I]
    #the slicing points of the bins to sum over
    slices = np.concatenate(([0], np.where(key[:-1]!=key[1:])[0]+1))
    #first entry of each bin is a unique key
    unique_keys = key[slices]
    #sum over the slices specified by index
    per_key_sum = np.add.reduceat(value, slices)
    #number of counts per key is the difference of our slice points. cap off with number of keys for last bin
    key_count = np.diff(np.append(slices, len(key)))
    return unique_keys, per_key_sum, key_count


names = ["a", "b", "b", "c", "d", "e", "e"]
values = [1.2, 4.5, 4.3, 2.0, 5.67, 8.08, 9.01]

unique_keys, per_key_sum, key_count = group(names, values)
print per_key_sum / key_count
person Eelco Hoogendoorn    schedule 05.12.2013