Korzyści z wydajności posortowanych indeksów
Sortuj MultiIndex za pomocą sort_index(), mierz wydajność wycinków za pomocą timeit i używaj is_monotonic_increasing jako warunku ochronnego.
Korzyści z wydajności posortowanych indeksów to bezpłatna lekcja Pandas & NumPy Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Pandas & NumPy Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Pandas & NumPy Academy zawiera 4 lekcji w sumie.
Dlaczego sortowanie indeksu ma znaczenie dla wydajności
Posortowany indeks umożliwia Pandas użycie wyszukiwania binarnego (O(log n)) zamiast pełnego skanowania liniowego (O(n)) podczas wyszukiwania zakresów etykiet. W przypadku DataFrame zawierającego milion wierszy wyszukiwanie binarne znajduje docelowy zakres po około 20 porównaniach, podczas gdy skanowanie liniowe może wymagać nawet miliona porównań. Dzięki temu operacje wycinania na posortowanych MultiIndexach są o rzędy wielkości szybsze niż na indeksach nieposortowanych — a różnica ta ma kluczowe znaczenie w potokach produkcyjnych przetwarzających miliony wierszy.
import pandas as pd
import numpy as np
np.random.seed(42)
# Create a large DataFrame with a MultiIndex
countries = ['DE', 'UK', 'USA', 'FR', 'JP']
dates = pd.date_range('2020-01-01', periods=200)
mi = pd.MultiIndex.from_product([countries, dates], names=['country', 'date'])
df = pd.DataFrame({'value': np.random.randn(len(mi))}, index=mi)
print(f'DataFrame shape: {df.shape}')
print(f'Index is sorted: {df.index.is_monotonic_increasing}')Sprawdzanie, czy indeks jest posortowany
Należy użyć df.index.is_monotonic_increasing, aby sprawdzić, czy indeks jest posortowany rosnąco. Wyrażenie to zwraca wartość logiczną. W przypadku MultiIndex Pandas sprawdza sortowanie leksykograficznie na wszystkich poziomach. Przed wykonaniem operacji wycinania za pomocą .loc[start:end] na MultiIndex należy zawsze wykonać to sprawdzenie — nieposortowany indeks spowoduje zgłoszenie UnsortedIndexError albo po cichu zwróci nieprawidłowe wyniki, zależnie od wersji Pandas.
import pandas as pd
# Sorted MultiIndex
tuples_sorted = [('A', 1), ('A', 2), ('B', 1), ('B', 2)]
mi_sorted = pd.MultiIndex.from_tuples(tuples_sorted)
df_sorted = pd.DataFrame({'v': [10, 20, 30, 40]}, index=mi_sorted)
# Unsorted MultiIndex
tuples_unsorted = [('B', 2), ('A', 1), ('B', 1), ('A', 2)]
mi_unsorted = pd.MultiIndex.from_tuples(tuples_unsorted)
df_unsorted = pd.DataFrame({'v': [10, 20, 30, 40]}, index=mi_unsorted)
print('Sorted index is_monotonic_increasing:', df_sorted.index.is_monotonic_increasing)
print('Unsorted index is_monotonic_increasing:', df_unsorted.index.is_monotonic_increasing)Sortowanie za pomocą sort_index()
df.sort_index() zwraca nowy DataFrame, którego wiersze są posortowane rosnąco według etykiet indeksu. Aby sortować malejąco, należy użyć ascending=False. W przypadku MultiIndex sortowanie jest leksykograficzne: najpierw sortowany jest poziom zewnętrzny, a następnie poziomy wewnętrzne w obrębie każdej grupy poziomu zewnętrznego. Należy zawsze sortować indeks po każdej operacji, która może zaburzyć jego kolejność — na przykład po pd.concat, filtrowaniu lub dodaniu nowych wierszy.
import pandas as pd
import numpy as np
np.random.seed(0)
countries = ['USA', 'UK', 'DE']
years = [2021, 2022, 2023]
mi = pd.MultiIndex.from_product([countries, years], names=['country', 'year'])
df = pd.DataFrame({'gdp': np.random.randint(3000, 26000, 9)}, index=mi)
print('Before sort_index():')
print(df.head(4))
df_sorted = df.sort_index()
print('\nAfter sort_index():')
print(df_sorted.head(4))
print('Is sorted:', df_sorted.index.is_monotonic_increasing)Pomiar czasu wyszukiwania za pomocą timeit
Moduł timeit języka Python mierzy czas wykonania instrukcji, uruchamiając ją wiele razy i obliczając średnią. Należy użyć go do porównania wyszukiwania w indeksach posortowanych i nieposortowanych. W środowiskach IPython/Jupyter magiczna komenda %timeit zapewnia tę samą funkcjonalność i wyświetla czytelniejsze wyniki. Benchmarking to jedyny wiarygodny sposób potwierdzenia, że optymalizacja wydajności rzeczywiście pomogła — nigdy nie należy zakładać, że zmiana przyspieszyła działanie, bez wykonania pomiaru.
import pandas as pd
import numpy as np
import timeit
np.random.seed(0)
N = 500000
idx = np.random.choice(['A','B','C','D','E'], N)
df_unsorted = pd.DataFrame({'v': np.random.randn(N)}, index=idx)
df_sorted = df_unsorted.sort_index()
# Time label lookup: sorted vs unsorted
t_unsorted = timeit.timeit(lambda: df_unsorted.loc['C'], number=100)
t_sorted = timeit.timeit(lambda: df_sorted.loc['C'], number=100)
print(f'Unsorted lookup (100 runs): {t_unsorted:.3f}s')
print(f'Sorted lookup (100 runs): {t_sorted:.3f}s')
print(f'Speedup: {t_unsorted/t_sorted:.1f}x')PerformanceWarning dotyczący nieposortowanego MultiIndex
Pandas emituje ostrzeżenie PerformanceWarning, gdy wykonują Państwo wycinanie na MultiIndex, który nie jest posortowany leksykograficznie: 'indexing past lexsort depth may impact performance'. Ostrzeżenie to oznacza, że Pandas musiał przejść z wyszukiwania binarnego na skanowanie liniowe. W prostych przypadkach nadal zwracane są prawidłowe wyniki, jednak podczas wycinania wewnętrznych poziomów nieposortowanego indeksu wielopoziomowego mogą zostać zwrócone nieprawidłowe wyniki. Należy traktować to ostrzeżenie jak błąd i usunąć jego przyczynę, sortując indeks.
import pandas as pd
import warnings
# Create an unsorted MultiIndex and trigger the warning
tuples = [('B', 2), ('A', 1), ('B', 1), ('A', 2)]
mi = pd.MultiIndex.from_tuples(tuples, names=['letter', 'num'])
df = pd.DataFrame({'v': [10, 20, 30, 40]}, index=mi)
print('Index sorted?', df.index.is_monotonic_increasing)
# This may trigger PerformanceWarning in some Pandas versions
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always')
try:
result = df.loc['A':'B', :]
print('Result:', result)
if w:
print('Warning:', str(w[0].message))
except Exception as e:
print('Error (common with newer Pandas):', type(e).__name__)Benchmarking wycinania na posortowanym i nieposortowanym MultiIndex
Wycinanie na posortowanym MultiIndex jest znacznie szybsze, ponieważ Pandas może użyć wyszukiwania binarnego zarówno w tablicach poziomu zewnętrznego, jak i wewnętrznego. Wykonajmy benchmark wycinania na dużym MultiIndex zawierającym milion wierszy — to typowy rozmiar w produkcyjnych potokach analitycznych. Wersja posortowana pomija skanowanie liniowe i konsekwentnie zapewnia przyspieszenie od 5 do 50 razy, zależnie od selektywności wycinanego zakresu.
import pandas as pd
import numpy as np
import timeit
np.random.seed(42)
countries = ['DE', 'UK', 'USA', 'FR', 'JP']
dates = pd.date_range('2010-01-01', periods=200000)
# Sample a random subset for timing test
sample_countries = np.random.choice(countries, 100000)
sample_dates = np.random.choice(dates, 100000)
df = pd.DataFrame({
'country': sample_countries,
'date': sample_dates,
'value': np.random.randn(100000)
}).set_index(['country', 'date'])
df_sorted = df.sort_index()
print('Dataset size:', len(df))
print('Sorted:', df_sorted.index.is_monotonic_increasing)
t = timeit.timeit(lambda: df_sorted.loc['USA'], number=50)
print(f'Sorted lookup (50 runs): {t:.3f}s')sort_index z parametrem level
W przypadku MultiIndex można sortować według określonego poziomu zamiast sortować wszystkie poziomy, używając parametru level: df.sort_index(level='year'). Jest to przydatne, gdy chcą Państwo zachować grupowanie według poziomu zewnętrznego, ale zmienić kolejność wierszy w obrębie każdej grupy zewnętrznej. Argument sort_remaining=True (domyślny) sortuje również wszystkie nieposortowane poziomy znajdujące się za wskazanym poziomem, zapewniając pełne uporządkowanie leksykograficzne.
import pandas as pd
import numpy as np
countries = ['USA', 'UK']
years = [2023, 2021, 2022] # deliberately unordered
mi = pd.MultiIndex.from_product([countries, years], names=['country', 'year'])
df = pd.DataFrame({'v': range(6)}, index=mi)
print('Before sorting by year level:')
print(df)
# Sort by the inner level (year) only
df_ysorted = df.sort_index(level='year')
print('\nAfter sort_index(level="year"):')
print(df_ysorted)is_monotonic_increasing jako zabezpieczenie potoku
W potokach produkcyjnych należy na początku każdej funkcji, która otrzymuje DataFrame z MultiIndex i wykonuje wycinanie, dodać zabezpieczenie sortowania. Jeśli indeks nie jest posortowany, należy posortować go automatycznie i zapisać ostrzeżenie w dzienniku. Zapobiega to niezauważonemu pogorszeniu wydajności lub nieprawidłowym wynikom, gdy kod nadrzędny zmieni kolejność DataFrame. Zabezpieczenie na granicy funkcji jest bardziej niezawodne niż zakładanie, że wywołujący zawsze przekazuje posortowane dane.
import pandas as pd
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def safe_slice(df, key):
'''Slice a MultiIndex DataFrame, sorting if necessary.'''
if not df.index.is_monotonic_increasing:
logger.warning('Index not sorted — sorting now. This is a performance cost.')
df = df.sort_index()
return df.loc[key]
# Test with an unsorted DataFrame
tuples = [('B', 2), ('A', 1), ('B', 1), ('A', 2)]
mi = pd.MultiIndex.from_tuples(tuples, names=['letter', 'num'])
df = pd.DataFrame({'v': [10, 20, 30, 40]}, index=mi)
result = safe_slice(df, 'A')
print('Slice result for A:')
print(result)Posortowany indeks a wyszukiwanie binarne dla prostych indeksów
Korzyści wydajnościowe sortowania dotyczą również zwykłych indeksów (niebędących indeksami wielopoziomowymi). DatetimeIndex używany w analizie szeregów czasowych znacznie szybciej wykonuje wycinanie zakresów dat, gdy jest posortowany. Indeks tekstowy posortowany alfabetycznie umożliwia binarne wyszukiwanie etykiet. W przypadku dużej Series z cenami akcji indeksowanej sygnaturami czasowymi posortowanie DatetimeIndex może skrócić czas wycinania ze 100 ms do wartości poniżej jednej milisekundy.
import pandas as pd
import numpy as np
import timeit
np.random.seed(0)
# Random timestamps — unsorted
timestamps = pd.date_range('2020-01-01', periods=500000, freq='min')
shuffled = np.random.permutation(timestamps)
prices = pd.Series(np.random.randn(500000), index=shuffled)
prices_sorted = prices.sort_index()
# Time a date range slice
t_unsorted = timeit.timeit(lambda: prices['2020-06-01':'2020-06-30'], number=20)
t_sorted = timeit.timeit(lambda: prices_sorted['2020-06-01':'2020-06-30'], number=20)
print(f'Unsorted: {t_unsorted:.3f}s')
print(f'Sorted: {t_sorted:.3f}s')
print(f'Speedup: {t_unsorted/t_sorted:.0f}x')Koszt sortowania w pamięci
Sortowanie nie jest bezpłatne — sort_index() tworzy nową kopię DataFrame (chyba że użyją Państwo inplace=True, które modyfikuje obiekt w miejscu). W przypadku bardzo dużych DataFrame tymczasowo podwaja to szczytowe zużycie pamięci. Praktyczną strategią jest jednokrotne sortowanie podczas wczytywania i zachowanie posortowanej wersji w całym potoku zamiast wielokrotnego sortowania. Jeśli pamięć jest ograniczona, należy sortować w miejscu za pomocą df.sort_index(inplace=True), aby uniknąć tworzenia tymczasowej kopii.
import pandas as pd
import numpy as np
np.random.seed(0)
countries = ['DE', 'UK', 'USA']
years = [2021, 2022, 2023]
mi = pd.MultiIndex.from_product([countries, years], names=['country', 'year'])
df = pd.DataFrame({'v': np.random.randn(9)}, index=mi)
# Sort once at load time — best practice
df.sort_index(inplace=True) # no temporary copy
assert df.index.is_monotonic_increasing, 'Index must be sorted!'
print('Pipeline-ready DataFrame (sorted in place):')
print(df)Podsumowanie dobrych praktyk dotyczących posortowanych indeksów
Najważniejsze zasady dotyczące wydajności indeksów: 1) Należy zawsze wywoływać sort_index() po każdej operacji, która może zaburzyć kolejność indeksu (concat, merge, append, filter). 2) Należy używać is_monotonic_increasing jako zabezpieczenia w funkcjach wykonujących wycinanie indeksu. 3) Należy sortować dane podczas wczytywania i zachowywać posortowany DataFrame w całym potoku, aby uniknąć wielokrotnego sortowania. 4) W przypadku DataFrame z MultiIndex należy upewnić się, że posortowane są wszystkie poziomy, a nie tylko poziom zewnętrzny. 5) Należy używać timeit, aby sprawdzić, czy sortowanie rzeczywiście zapewnia oczekiwane przyspieszenie w konkretnym potoku.
Szybkie sprawdzenie
Sprawdź swoją wiedzę na temat wydajności posortowanych indeksów z tej lekcji.
Podsumowanie lekcji
W tej lekcji nauczyli się Państwo, że is_monotonic_increasing sprawdza, czy indeks jest posortowany i czy można użyć wyszukiwania binarnego, sort_index() sortuje w miejscu lub zwraca posortowaną kopię, a timeit mierzy rzeczywiste przyspieszenie, aby potwierdzić korzyść. W następnej części omówimy funkcje okienkowe — statystyki kroczące i kumulowane dla szeregów czasowych oraz danych finansowych.
Często zadawane pytania
Czy lekcja „Korzyści z wydajności posortowanych indeksów” jest bezpłatna?
Tak — pełny tekst „Korzyści z wydajności posortowanych indeksów” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Pandas & NumPy Academy, przejdź na CoddyKit PRO. Kurs Pandas & NumPy Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Korzyści z wydajności posortowanych indeksów”?
Sortuj MultiIndex za pomocą sort_index(), mierz wydajność wycinków za pomocą timeit i używaj is_monotonic_increasing jako warunku ochronnego. Ćwiczysz Pandas & NumPy Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Pandas & NumPy Academy?
Nie wymagamy żadnego doświadczenia. Pandas & NumPy Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Korzyści z wydajności posortowanych indeksów”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Pandas & NumPy Academy?
Tak. Każda lekcja Pandas & NumPy Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Tworzenie MultiIndex
- Wybieranie danych z MultiIndex
- Wyrównywanie indeksów i zmiana indeksu
- Korzyści z wydajności posortowanych indeksów