Tôi tìm thấy câu hỏi này được khá thú vị và trong khi tôi đã không thể có được một cải tiến lớn so với các phương pháp được đề xuất khác mà tôi đã tìm thấy một phương pháp thô lỗ tinh khiết nhanh hơn một chút so với các phương pháp được đề xuất khác.
import numpy as np
import pandas as pd
from collections import defaultdict
data = np.random.randint(0, 10**2, size=10**5)
series = pd.Series(data)
def get_values_and_indicies(input_data):
input_data = np.asarray(input_data)
sorted_indices = input_data.argsort() # Get the sorted indices
# Get the sorted data so we can see where the values change
sorted_data = input_data[sorted_indices]
# Find the locations where the values change and include the first and last values
run_endpoints = np.concatenate(([0], np.where(sorted_data[1:] != sorted_data[:-1])[0] + 1, [len(input_data)]))
# Get the unique values themselves
unique_vals = sorted_data[run_endpoints[:-1]]
# Return the unique values along with the indices associated with that value
return {unique_vals[i]: sorted_indices[run_endpoints[i]:run_endpoints[i + 1]].tolist() for i in range(num_values)}
def by_dd(input_data):
idx_lists = defaultdict(list)
for idx, ele in enumerate(input_data):
idx_lists[ele].append(idx)
return idx_lists
def by_pand1(input_data):
idx_lists = defaultdict(list)
return {k: v.tolist() for k,v in series.groupby(input_data).indices.items()}
def by_pand2(input_data):
return series.groupby(input_data).indices
def data_to_idxlists(input_data):
u, ixs = np.unique(input_data, return_inverse=True)
return {u: np.nonzero(ixs==i) for i, u in enumerate(u)}
def data_to_idxlists_unique(input_data):
sorting_ixs = np.argsort(input_data)
uniques, unique_indices = np.unique(input_data[sorting_ixs], return_index = True)
return {u: sorting_ixs[start:stop] for u, start, stop in zip(uniques, unique_indices, list(unique_indices[1:])+[None])}
Các timings kết quả là (từ nhanh nhất để chậm nhất):
>>> %timeit get_values_and_indicies(data)
100 loops, best of 3: 4.25 ms per loop
>>> %timeit by_pand2(series)
100 loops, best of 3: 5.22 ms per loop
>>> %timeit data_to_idxlists_unique(data)
100 loops, best of 3: 6.23 ms per loop
>>> %timeit by_pand1(series)
100 loops, best of 3: 10.2 ms per loop
>>> %timeit data_to_idxlists(data)
100 loops, best of 3: 15.5 ms per loop
>>> %timeit by_dd(data)
10 loops, best of 3: 21.4 ms per loop
và cần lưu ý rằng không giống như by_pand2 nó kết quả một dict danh sách như được đưa ra trong ví dụ. Nếu bạn muốn trả lại số defaultdict
, bạn có thể chỉ cần thay đổi thời gian qua thành return defaultdict(list, ((unique_vals[i], sorted_indices[run_endpoints[i]:run_endpoints[i + 1]].tolist()) for i in range(num_values)))
để tăng thời gian tổng thể trong các thử nghiệm của tôi lên 4,4 ms.
Cuối cùng, tôi nên lưu ý rằng thời gian này nhạy cảm với dữ liệu. Khi tôi sử dụng chỉ có 10 giá trị khác nhau tôi nhận:
- get_values_and_indicies: 4,34 ms mỗi vòng lặp
- data_to_idxlists_unique: 4,42 ms mỗi vòng lặp
- by_pand2: 4,83 ms mỗi vòng lặp
- data_to_idxlists: 6,09 ms mỗi vòng lặp
- by_pand1: 9,39 ms mỗi vòng lặp
- by_dd: 22.4 ms mỗi vòng lặp
trong khi nếu tôi sử dụng 10.000 giá trị khác nhau tôi nhận:
- get_values_and_indicies: 7,00 ms mỗi vòng lặp
- data_to_idxlists_unique: 14,8 ms mỗi vòng lặp
- by_dd: 29,8 ms mỗi vòng lặp
- by_pand2: 47,7 ms trên mỗi vòng
- by_pand1: 67,3 ms mỗi vòng
- data_to_idxlists: 869 ms trên mỗi vòng
Có vẻ như việc lập chỉ mục không phải là nút cổ chai của bạn trong các dòng này. Cả hai chỉ mục và phụ thêm là các hoạt động thời gian 'O (1)', trên thực tế. – erip
@DSM Có, 'dữ liệu' có chỉ số kinh điển. – Mai
FWIW, tôi hiểu sự hiểu biết là nhanh hơn đáng kể so với 'for loop's, vì vậy đây có thể là một cái gì đó để chuẩn. Bạn không chắc chắn nếu từ bỏ 'defaultdict' là thứ bạn có thể mua được. – erip