2016-07-07 21 views
16

Po eksperymentach z synchronizacją różnych typów wyszukiwań w Pandas DataFrame pozostaje mi kilka pytań.Porównanie czasów wyszukiwania Pandy

Oto skonfigurować ...

import pandas as pd 
import numpy as np 
import itertools 

letters = [chr(x) for x in range(ord('a'), ord('z'))] 
letter_combinations = [''.join(x) for x in itertools.combinations(letters, 3)] 

df1 = pd.DataFrame({ 
     'value': np.random.normal(size=(1000000)), 
     'letter': np.random.choice(letter_combinations, 1000000) 
    }) 
df2 = df1.sort_values('letter') 
df3 = df1.set_index('letter') 
df4 = df3.sort_index() 

Więc DF1 wygląda mniej więcej tak ...

print(df1.head(5)) 


>>> 
    letter  value 
0 bdh 0.253778 
1 cem -1.915726 
2 mru -0.434007 
3 lnw -1.286693 
4 fjv 0.245523 

Oto kod, aby sprawdzić różnice w wydajności odnośników ...

print('~~~~~~~~~~~~~~~~~NON-INDEXED LOOKUPS/UNSORTED DATASET~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') 
%timeit df1[df1.letter == 'ben'] 
%timeit df1[df1.letter == 'amy'] 
%timeit df1[df1.letter == 'abe'] 

print('~~~~~~~~~~~~~~~~~NON-INDEXED LOOKUPS/SORTED DATASET~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') 
%timeit df2[df2.letter == 'ben'] 
%timeit df2[df2.letter == 'amy'] 
%timeit df2[df2.letter == 'abe'] 

print('~~~~~~~~~~~~~~~~~~~~~INDEXED LOOKUPS~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') 
%timeit df3.loc['ben'] 
%timeit df3.loc['amy'] 
%timeit df3.loc['abe'] 

print('~~~~~~~~~~~~~~~~~~~~~SORTED INDEXED LOOKUPS~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') 
%timeit df4.loc['ben'] 
%timeit df4.loc['amy'] 
%timeit df4.loc['abe'] 

A wyniki ...

~~~~~~~~~~~~~~~~~NON-INDEXED LOOKUPS/UNSORTED DATASET~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
10 loops, best of 3: 59.7 ms per loop 
10 loops, best of 3: 59.7 ms per loop 
10 loops, best of 3: 59.7 ms per loop 
~~~~~~~~~~~~~~~~~NON-INDEXED LOOKUPS/SORTED DATASET~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
10 loops, best of 3: 192 ms per loop 
10 loops, best of 3: 192 ms per loop 
10 loops, best of 3: 193 ms per loop 
~~~~~~~~~~~~~~~~~~~~~INDEXED LOOKUPS~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
The slowest run took 4.66 times longer than the fastest. This could mean that an intermediate result is being cached 
10 loops, best of 3: 40.9 ms per loop 
10 loops, best of 3: 41 ms per loop 
10 loops, best of 3: 40.9 ms per loop 
~~~~~~~~~~~~~~~~~~~~~SORTED INDEXED LOOKUPS~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
The slowest run took 1621.00 times longer than the fastest. This could mean that an intermediate result is being cached 
1 loops, best of 3: 259 µs per loop 
1000 loops, best of 3: 242 µs per loop 
1000 loops, best of 3: 243 µs per loop 

pytania ...

  1. To całkiem jasne, dlaczego wyszukiwanie na indeksie segregowanych jest tak dużo szybciej, wyszukiwanie binarne, aby uzyskać O (log (n)) vs wydajność O (n) dla pełnego skanowanie tablicy. Ale dlaczego wyszukiwanie na posortowanej nieindeksowanej kolumnie jest niepoparte indeksowaniem nieposortowanej nieindeksowanej kolumny df1?

  2. Co się stanie z The slowest run took x times longer than the fastest. This could mean that an intermediate result is being cached. Z pewnością wyniki nie są buforowane. Czy to dlatego, że utworzony indeks jest leniwy i czy nie jest ponownie indeksowany, dopóki nie jest potrzebny? To by wyjaśniało, dlaczego jest to tylko przy pierwszym połączeniu z .loc[].

  3. Dlaczego domyślnie nie jest posortowany indeks? Stały koszt tego rodzaju może być zbyt duży?

+0

'3. Dlaczego domyślnie nie jest sortowany indeks? '- wyobraź sobie, że ustawiłeś niestandardowy indeks i został posortowany, nie pytając cię ... – MaxU

+0

1. - ślad pamięci' df2' wynosi ok.50% większy w porównaniu do 'df1' – MaxU

+0

@MaxU Powodem, dla którego pytam, jest fakt, że inne narzędzia (DBs i R's data.table) sortują według indeksu domyślnie –

Odpowiedz

9

Różnica w tych% timeit wyniki

In [273]: %timeit df1[df1['letter'] == 'ben'] 
10 loops, best of 3: 36.1 ms per loop 

In [274]: %timeit df2[df2['letter'] == 'ben'] 
10 loops, best of 3: 108 ms per loop 

również pokazuje się w czystych NumPy porównań równości:

In [275]: %timeit df1['letter'].values == 'ben' 
10 loops, best of 3: 24.1 ms per loop 

In [276]: %timeit df2['letter'].values == 'ben' 
10 loops, best of 3: 96.5 ms per loop 

Pod maską Pandy df1['letter'] == 'ben'calls a Cython function który pętle poprzez wartości podstawowej tablicy NumPy, df1['letter'].values. Zasadniczo robi to samo, co df1['letter'].values == 'ben', ale z inną obsługą NaN.

Ponadto należy zauważyć, że po prostu dostępu do elementów w df1['letter'] w porządku sekwencyjnym można zrobić szybciej niż robi to samo dla df2['letter']:

In [11]: %timeit [item for item in df1['letter']] 
10 loops, best of 3: 49.4 ms per loop 

In [12]: %timeit [item for item in df2['letter']] 
10 loops, best of 3: 124 ms per loop 

Różnica razy w ciągu każdego z tych trzech zestawów %timeit testów są w przybliżeniu takie same, jak . Myślę, że to dlatego, że wszyscy mają tę samą przyczynę.

Ponieważ kolumna letter posiada struny, macierze numpy df1['letter'].values i df2['letter'].values mieć dtype object i dlatego trzymają odnośniki do miejsca pamięci z dowolnych obiektów Pythona (w tym przypadku strun).

Należy wziąć pod uwagę lokalizację pamięci ciągów zapisanych w DataFrames, df1 i df2. W CPython id zwraca adres pamięci obiektu:

memloc = pd.DataFrame({'df1': list(map(id, df1['letter'])), 
         'df2': list(map(id, df2['letter'])), }) 

       df1    df2 
0 140226328244040 140226299303840 
1 140226328243088 140226308389048 
2 140226328243872 140226317328936 
3 140226328243760 140226230086600 
4 140226328243368 140226285885624 

komunikaty w df1 (po pierwszym kilkunastu) często pojawia się kolejno w pamięci, podczas sortowania powoduje, że sznurki df2 (pobrane w kolejności) będzie rozproszone w pamięci:

In [272]: diffs = memloc.diff(); diffs.head(30) 
Out[272]: 
     df1   df2 
0  NaN   NaN 
1  -952.0 9085208.0 
2  784.0 8939888.0 
3  -112.0 -87242336.0 
4  -392.0 55799024.0 
5  -392.0 5436736.0 
6  952.0 22687184.0 
7  56.0 -26436984.0 
8  -448.0 24264592.0 
9  -56.0 -4092072.0 
10 -168.0 -10421232.0 
11 -363584.0 5512088.0 
12  56.0 -17433416.0 
13  56.0 40042552.0 
14  56.0 -18859440.0 
15  56.0 -76535224.0 
16  56.0 94092360.0 
17  56.0 -4189368.0 
18  56.0  73840.0 
19  56.0 -5807616.0 
20  56.0 -9211680.0 
21  56.0 20571736.0 
22  56.0 -27142288.0 
23  56.0 5615112.0 
24  56.0 -5616568.0 
25  56.0 5743152.0 
26  56.0 -73057432.0 
27  56.0 -4988200.0 
28  56.0 85630584.0 
29  56.0 -4706136.0 

Większość ciągów w df1 56 bajty są od siebie:

In [14]: 
In [16]: diffs['df1'].value_counts() 
Out[16]: 
56.0   986109 
120.0   13671 
-524168.0   215 
-56.0    1 
-12664712.0   1 
41136.0    1 
-231731080.0   1 
Name: df1, dtype: int64 

In [20]: len(diffs['df1'].value_counts()) 
Out[20]: 7 

Natomiast struny w df2 są rozrzucone po całym miejscu:

In [17]: diffs['df2'].value_counts().head() 
Out[17]: 
-56.0  46 
56.0  44 
168.0 39 
-112.0 37 
-392.0 35 
Name: df2, dtype: int64 

In [19]: len(diffs['df2'].value_counts()) 
Out[19]: 837764 

Kiedy te obiekty (strings) znajdują się kolejno w pamięci ich wartości można szybciej odzyskać. Dlatego porównania równości wykonywane przez df1['letter'].values == 'ben' można wykonać szybciej niż te w df2['letter'].values == 'ben'. Czas wyszukiwania jest mniejszy.

Ten problem z dostępem do pamięci wyjaśnia również, dlaczego nie ma żadnych różnic w wynikach dla kolumny %timeit dla kolumny .

In [5]: %timeit df1[df1['value'] == 0] 
1000 loops, best of 3: 1.8 ms per loop 

In [6]: %timeit df2[df2['value'] == 0] 
1000 loops, best of 3: 1.78 ms per loop 

df1['value'] i df2['value'] są numpy tablice dtype float64. W przeciwieństwie do tablic obiektów , ich wartości są spięte w pamięci w sposób ciągły. Sortowanie df1 z df2 = df1.sort_values('letter') powoduje wartości w df2['value'] być kolejność, ale ponieważ wartości są skopiowany do nowej tablicy numpy wartości znajdują się kolejno w pamięci. Tak więc dostęp do wartości w df2['value'] można wykonać tak szybko, jak w df1['value'].

5

(1) pandas obecnie nie ma wiedzy na temat posortowalności kolumny.
Jeśli chcesz skorzystać z posortowanych danych, możesz użyć opcji df2.letter.searchsorted Zobacz odpowiedź @ unutbu, aby uzyskać wyjaśnienie, co tak naprawdę powoduje różnicę w czasie.

(2) Tabela mieszająca, która znajduje się pod indeksem, jest leniwie tworzona, a następnie buforowana.

+0

Nie bardzo rozumiem, jak typ indeksu jest odpowiedzialny za różnicę czasu tutaj. OP używa 0.17.1, który nie posiada 'RangeIndex' (oba indeksy będą typu całkowitego) i nowe indeksy muszą być tworzone w obu przypadkach. Indeksowanie Boole'a sprowadza się do 'take' z podstawowych tablic i typu indeksu nie ma tu znaczenia. –

+0

Masz rację - Myślałem, że czas utworzenia indeksu mógł być problemem, ale tak nie jest. – chrisb