2011-12-24 11 views
6

że dwie tablice 1D NumPy równej długości, id i data, gdzie id jest sekwencją powtarzając uporządkowane liczby całkowite określające podokien na data. Na przykład,Grupa obciąż lub minut w numpy tablicy

id data 
1  2 
1  7 
1  3 
2  8 
2  9 
2 10 
3  1 
3 -10 

Chciałbym agregować data grupując na id i biorąc albo max lub min. W SQL byłoby to typowe zapytanie agregacyjne, takie jak SELECT MAX(data) FROM tablename GROUP BY id ORDER BY id. Czy istnieje sposób, w jaki mogę uniknąć pętli w języku Python i zrobić to w wektoryzacji, czy też muszę upuścić do C?

Odpowiedz

8

W ciągu ostatnich kilku dni widziałem bardzo podobne pytania dotyczące przepełnienia stosu. Poniższy kod jest bardzo podobny do implementacji numpy.unique i ponieważ wykorzystuje bazową maszynę numpy, najprawdopodobniej będzie szybszy niż cokolwiek co możesz zrobić w pętli Pythona.

import numpy as np 
def group_min(groups, data): 
    # sort with major key groups, minor key data 
    order = np.lexsort((data, groups)) 
    groups = groups[order] # this is only needed if groups is unsorted 
    data = data[order] 
    # construct an index which marks borders between groups 
    index = np.empty(len(groups), 'bool') 
    index[0] = True 
    index[1:] = groups[1:] != groups[:-1] 
    return data[index] 

#max is very similar 
def group_max(groups, data): 
    order = np.lexsort((data, groups)) 
    groups = groups[order] #this is only needed if groups is unsorted 
    data = data[order] 
    index = np.empty(len(groups), 'bool') 
    index[-1] = True 
    index[:-1] = groups[1:] != groups[:-1] 
    return data[index] 
+0

Dzięki @Bago, to zapewnia świetną wydajność. Inną rzeczą, która może mi się przydać, jest to, że wygląda na to, że lexsort zawsze umieszcza wartości NaN na końcu okien bocznych. Tak więc, jeśli chcę znaleźć, powiedzmy, maksimum każdego okna z wyłączeniem NaN, mogę odwrócić znak danych, zastosować formułę min, a następnie odwrócić znak ponownie w drodze, z tylko niewielką karą wykonania. Z drugiej strony, jeśli rzeczywiście chcę zwrócić wartość NaN, jeśli w podokiendzie znajduje się NaN, to pozostawiam ją w niezmienionej postaci. – Abiel

+0

Abiel, patrz np.nanmax - max ignoring NaNs – denis

+0

Ładne rozwiązanie. Irytująco jest to czas O (n log n) i pamięć O (n), kiedy wiemy, że można go rozwiązać w pamięci O (n) i pamięci O (k) dla k bin. Być może numpy powinien obsługiwać 'binmax' oraz' bincount'. – joeln

0

myślę, że to realizuje to, czego szukasz:

[max([val for idx,val in enumerate(data) if id[idx] == k]) for k in sorted(set(id))] 

dla zewnętrznego listowego, od prawej do lewej, set(id) grup id s, sorted() nich rodzajów, for k ... iteracje nad nimi, a max przyjmuje maksimum, w tym przypadku, innego rozumienia listy. Tak więc przejście do tego rozumienia wewnętrznej listy: enumerate(data) zwraca zarówno indeks, jak i wartość z data, if id[val] == k wybiera elementy data odpowiadające idk.

Powtarza całą listę data dla każdego listy dla każdego id. Po wstępnym przetworzeniu na podlisty może być możliwe przyspieszenie, ale nie będzie to wówczas jedna liniówka.

6

W czystym Pythonie:

from itertools import groupby, imap, izip 
from operator import itemgetter as ig 

print [max(imap(ig(1), g)) for k, g in groupby(izip(id, data), key=ig(0))] 
# -> [7, 10, 1] 

Odmiana:

print [data[id==i].max() for i, _ in groupby(id)] 
# -> [7, 10, 1] 

podstawie @Bago's answer:

import numpy as np 

# sort by `id` then by `data` 
ndx = np.lexsort(keys=(data, id)) 
id, data = id[ndx], data[ndx] 

# get max() 
print data[np.r_[np.diff(id), True].astype(np.bool)] 
# -> [ 7 10 1] 

Jeśli pandas jest zainstalowany:

from pandas import DataFrame 

df = DataFrame(dict(id=id, data=data)) 
print df.groupby('id')['data'].max() 
# id 
# 1 7 
# 2 10 
# 3 1 
+0

Dzięki @JF dla wszystkich różnych podejść. Oczywiście rozwiązanie numpy jest szybsze niż czysty Python, ale byłem zaskoczony, jak szybkie było twoje pierwsze, czyste rozwiązanie Pythona. Ciekawi mnie względna wydajność rozwiązania pandy; niestety nie mogłem tego przetestować, ponieważ otrzymałem NameError, gdy próbuję zaimportować DataFrame przy użyciu najnowszej wersji. – Abiel

+0

@Abiel: 'pandy .__ wersja __ == '0.6.1''' – jfs

+2

+1 dla Pandy. Myślę, że najprostszy w swojej czytelności. –

0

Poniższe rozwiązanie wymaga jedynie sortowania danych (nie lexsortu) i nie wymaga wyszukiwania granic między grupami. Polega ona na tym, że jeśli o jest tablicą wskaźników do r następnie r[o] = x wypełni r z najnowszej wartości x dla każdej wartości o, tak że r[[0, 0]] = [1, 2] powróci r[0] = 2. Wymaga to, że grupy są liczbami całkowitymi od 0 do liczby grup - 1, jak dla numpy.bincount, i że nie jest to wartość dla każdej grupy:

def group_min(groups, data): 
    n_groups = np.max(groups) + 1 
    result = np.empty(n_groups) 
    order = np.argsort(data)[::-1] 
    result[groups.take(order)] = data.take(order) 
    return result 

def group_max(groups, data): 
    n_groups = np.max(groups) + 1 
    result = np.empty(n_groups) 
    order = np.argsort(data) 
    result[groups.take(order)] = data.take(order) 
    return result 
0

Nieco szybciej i bardziej ogólnie odpowiedź niż już zaakceptowane jedną; podobnie jak w przypadku rozwiązania joeln, unika droższego lexsortu i działa na dowolne ufunki.Co więcej, wymaga tylko, aby klucze były sortowalne, a nie ints w określonym zakresie. Przyjęta odpowiedź może być jeszcze szybsza, biorąc pod uwagę, że maksymalna/minimalna wartość nie jest obliczana jawnie. Zdolność ignorowania nans z przyjętego rozwiązania jest zgrabna; ale można też po prostu przypisać nanominy fałszywemu kluczowi.

import numpy as np 

def group(key, value, operator=np.add): 
    """ 
    group the values by key 
    any ufunc operator can be supplied to perform the reduction (np.maximum, np.minimum, np.substract, and so on) 
    returns the unique keys, their corresponding per-key reduction over the operator, 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] 
    #reduce over the slices specified by index 
    per_key_sum = operator.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, reduced_values, key_count = group(names, values) 
print 'per group mean' 
print reduced_values/key_count 
unique_keys, reduced_values, key_count = group(names, values, np.minimum) 
print 'per group min' 
print reduced_values 
unique_keys, reduced_values, key_count = group(names, values, np.maximum) 
print 'per group max' 
print reduced_values 
3

Jestem całkiem nowy, Python i Numpy ale wydaje się, że można zastosować metodę ufunc s zamiast reduceat.at:

import numpy as np 
data_id = np.array([0,0,0,1,1,1,1,2,2,2,3,3,3,4,5,5,5]) 
data_val = np.random.rand(len(data_id)) 
ans = np.empty(data_id[-1]+1) # might want to use max(data_id) and zeros instead 
np.maximum.at(ans,data_id,data_val) 

na przykład:

data_val = array([ 0.65753453, 0.84279716, 0.88189818, 0.18987882, 0.49800668, 
    0.29656994, 0.39542769, 0.43155428, 0.77982853, 0.44955868, 
    0.22080219, 0.4807312 , 0.9288989 , 0.10956681, 0.73215416, 
    0.33184318, 0.10936647]) 
ans = array([ 0.98969952, 0.84044947, 0.63460516, 0.92042078, 0.75738113, 
    0.37976055]) 

Oczywiście ma to sens tylko wtedy, gdy twoje wartości data_id są odpowiednie do użycia jako indeksy (tj. Nieujemne liczby całkowite i nie wielkie ... przypuszczalnie jeśli są duże/rzadkie, możesz zainicjować ans używając np.unique(data_id) lub czegoś podobnego).

Należy zauważyć, że data_id nie musi być w rzeczywistości sortowany.

1

Ive zapakował wersję mojej poprzedniej odpowiedzi w pakiecie numpy_indexed; miło mieć to wszystko zapakowane i przetestowane w zgrabnym interfejsie; plus to ma dużo większą funkcjonalność, a także:

import numpy_indexed as npi 
group_id, group_max_data = group_by(id).max(data) 

i tak dalej