2011-12-24 43 views
6

Tôi có hai bằng độ dài mảng 1D NumPy, iddata, nơi id là một chuỗi lặp lại, ra lệnh cho các số nguyên xác định tiểu cửa sổ trên data. Ví dụ,Đoàn tự do max hoặc min trong một mảng NumPy

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

Tôi muốn kết hợp lại data bằng cách nhóm trên id và không lấy được tối đa hoặc tối thiểu. Trong SQL, đây sẽ là một truy vấn tổng hợp điển hình như SELECT MAX(data) FROM tablename GROUP BY id ORDER BY id. Có cách nào tôi có thể tránh các vòng Python và làm điều này một cách vectorized, hoặc tôi phải thả xuống C?

Trả lời

8

Tôi đã nhìn thấy một số câu hỏi rất giống nhau về chồng tràn trong vài ngày qua. Đoạn mã sau rất giống với việc thực hiện numpy.unique và bởi vì nó tận dụng lợi thế của máy móc thiết yếu, nó có khả năng sẽ nhanh hơn bất cứ thứ gì bạn có thể làm trong một vòng python.

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

Cảm ơn @Bago, điều này mang lại hiệu suất tuyệt vời. Một điều tôi thấy hữu ích ở đây là có vẻ như lexsort sẽ luôn đặt giá trị NaN ở cuối các cửa sổ con. Vì vậy, nếu tôi muốn tìm, nói rằng, tối đa của mỗi cửa sổ không bao gồm NaN, tôi có thể lật dấu hiệu của dữ liệu, áp dụng công thức min, và sau đó lật lại dấu hiệu trên đường ra, chỉ với một hình phạt hiệu suất nhỏ. Mặt khác, nếu tôi thực sự muốn có một giá trị NaN được trả về nếu có một NaN ở bất kỳ đâu trong cửa sổ con, thì tôi để nguyên nó. – Abiel

+0

Abiel, xem np.nanmax - max bỏ qua NaNs – denis

+0

Giải pháp tốt. Hiếm khi đó là thời gian O (n log n) và bộ nhớ O (n), khi chúng ta biết nó có thể được giải quyết trong thời gian O (n) và O (k) đối với thùng k. Có lẽ numpy nên hỗ trợ 'binmax' cũng như' bincount'. – joeln

0

Tôi nghĩ rằng đây hoàn thành những gì bạn đang tìm kiếm:

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

Để xem danh sách sự hiểu biết bên ngoài, từ phải sang trái, set(id) nhóm các id s, sorted() loại họ, for k ... lặp trên họ, và max mất tối đa, trong trường hợp này, một danh sách hiểu khác. Vì vậy, di chuyển đến danh sách nội bộ đó: enumerate(data) trả về cả chỉ mục và giá trị từ data, if id[val] == k chọn ra các thành viên data tương ứng với idk.

Điều này lặp qua danh sách đầy đủ data cho mỗi id. Với một số tiền xử lý vào danh sách con, nó có thể có khả năng tăng tốc nó, nhưng nó sẽ không phải là một lớp lót sau đó.

6

Trong Python tinh khiết:

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] 

Một biến thể:

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

Dựa trên @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] 

Nếu pandas được cài đặt:

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

Cảm ơn @JF cho tất cả các phương pháp khác nhau. Tất nhiên, giải pháp gọn gàng nhanh hơn Python thuần túy nhưng tôi đã ngạc nhiên về tốc độ của giải pháp Python thuần túy đầu tiên của bạn. Tôi tò mò về hiệu suất tương đối của giải pháp gấu trúc; tiếc là tôi không thể kiểm tra nó vì tôi nhận được một NameError khi tôi cố gắng nhập DataFrame bằng cách sử dụng bản dựng mới nhất. – Abiel

+0

@Abiel: 'pandas .__ phiên bản __ == '0.6.1'' – jfs

+2

+1 cho gấu trúc. Tôi nghĩ đơn giản nhất trong khả năng đọc của nó. –

0

Giải pháp sau chỉ yêu cầu sắp xếp dữ liệu (không phải lexsort) và không yêu cầu tìm ranh giới giữa các nhóm. Nó dựa trên thực tế là nếu o là một mảng của các chỉ số thành r sau đó r[o] = x sẽ điền r với giá trị mới nhất x cho mỗi giá trị của o, như vậy sẽ trở lại r[[0, 0]] = [1, 2]r[0] = 2. Nó đòi hỏi rằng nhóm của bạn là các số nguyên từ 0 đến số nhóm - 1 khi cho numpy.bincount, và rằng có một giá trị cho mỗi nhóm:

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

Một câu trả lời hơi nhanh hơn và tổng quát hơn so với đã chấp nhận một; giống như câu trả lời của joeln nó tránh được lexsort đắt tiền hơn, và nó hoạt động cho ufuncs tùy ý.Hơn nữa, nó chỉ đòi hỏi rằng các phím có thể sắp xếp, thay vì là int trong một phạm vi cụ thể. Câu trả lời được chấp nhận vẫn có thể nhanh hơn, mặc dù max/min không được tính toán một cách rõ ràng. Khả năng bỏ qua nans của giải pháp được chấp nhận là gọn gàng; nhưng người ta cũng có thể chỉ đơn giản chỉ định giá trị nan một chìa khóa giả.

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

Tôi khá mới để Python và NumPy nhưng, nó có vẻ như bạn có thể sử dụng phương pháp .at của ufunc s chứ không phải reduceat:

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) 

Ví dụ:

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]) 

Tất nhiên điều này chỉ có ý nghĩa nếu giá trị data_id của bạn phù hợp để sử dụng làm chỉ số (tức là số nguyên không âm và không lớn ... có lẽ là nếu chúng lớn/thưa thớt, bạn có thể khởi tạo ans sử dụng np.unique(data_id) hoặc thứ gì đó).

Tôi nên chỉ ra rằng data_id không thực sự cần phải được sắp xếp.

1

Ive đã đóng gói phiên bản câu trả lời trước của tôi trong gói numpy_indexed; tốt đẹp của nó để có tất cả điều này bao bọc và thử nghiệm trong một giao diện gọn gàng; cộng với nó có chức năng nhiều hơn nữa cũng như:

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

Và vân vân

Các vấn đề liên quan