Средние повторяющиеся значения из двух парных списков в Python с использованием NumPy - PullRequest
4 голосов
/ 17 октября 2011

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

Однако с большими (более 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?

Ответы [ 4 ]

4 голосов
/ 17 октября 2011

С помощью numpy вы можете написать что-нибудь самостоятельно или использовать групповую функциональность (функция rec_groupby из matplotlib.mlab, но намного медленнее. Для более мощной групповой функциональности, возможно, посмотрите 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
3 голосов
/ 17 октября 2011

Может быть, решение для кучи более сложное, чем вам нужно.Не делая ничего сложного, я обнаружил, что следующее «быстро как мгновение» (как, например, не было заметного ожидания с 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 типичной длиной для ваших списков, и делаете ли вы это только пару раз в сценарии, или вы делаете это сотни / тысячи раз.

0 голосов
/ 15 сентября 2015

Простое решение с помощью 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
0 голосов
/ 05 декабря 2013

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

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
...