I znaleziono to pytanie jest bardzo interesująca i chociaż nie mógł uzyskać znaczną poprawę w porównaniu z innymi proponowanymi metodami, w których znalazłem czystą metodę numpy, która była nieco szybsza niż inne proponowane metody.
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])}
Uzyskane czasy były (od najszybszego do najwolniejszego):
>>> %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
i należy zauważyć, że w przeciwieństwie do by_pand2 wynika dict list, jak podano w przykładzie. Jeśli wolisz zwrócić defaultdict
możesz po prostu zmienić ostatni raz na return defaultdict(list, ((unique_vals[i], sorted_indices[run_endpoints[i]:run_endpoints[i + 1]].tolist()) for i in range(num_values)))
, co zwiększyło ogólny czas w moich testach do 4,4 ms.
Na koniec należy zauważyć, że te terminy są wrażliwe na dane. Gdy stosuje się tylko 10 różnych wartości mam:
- get_values_and_indicies: 4,34 ms w każdej pętli
- data_to_idxlists_unique: 4,42 ms na pętli
- by_pand2: 4,83 ms na pętli
- data_to_idxlists: 6,09 ms na pętli
- by_pand1: 9,39 ms na pętlę
- przez_dd: 22.4 ms w każdej pętli
, a jeśli stosuje się 10,000 różne wartości, że otrzymał:
- get_values_and_indicies: 7,00 ms w każdej pętli
- data_to_idxlists_unique: 14,8 ms na pętli
- by_dd: 29,8 ms na pętli
- by_pand2: 47,7 ms na pętli
- by_pand1: 67,3 ms na pętli
- data_to_idxlists: 869 ms na pętlę
Wydaje się mało prawdopodobne, aby indeksowanie było wąskim gardłem w tych liniach. Zarówno indeksowanie jak i dołączanie są operacjami czasu "O (1)". – erip
@ DSM Tak, 'dane' ma indeksy kanoniczne. – Mai
FWIW, rozumiem, że rozumienie jest znacznie szybsze niż "dla pętli", więc może to być coś do testowania. Nie jestem pewien, czy rezygnacja z 'defaultdict' jest na coś, na co cię stać. – erip