Baza wiedzy / Python
pandas pivot_table: suma, średnia i kontrola tabeli przestawnej
Pięć wierszy sprzedaży zamieniam w tabelę regionów i produktów. Pokazuję, dlaczego trzeba jawnie wybrać agregację i kiedy zero w pustej komórce wprowadza w błąd.

Gdy mam kilka wierszy sprzedaży dla tego samego produktu i regionu, chcę wybrać sposób ich podsumowania. Suma odpowie na inne pytanie niż średnia albo liczba obserwacji. Samo przestawienie tabeli nie podejmie tej decyzji za mnie. Właśnie dlatego przy pivot_table zaczynam od znaczenia jednej komórki wyniku.
Przykłady odnoszę do dokumentacji pandas 3.1.1 dostępnej w listopadzie 2026. Wszystkie dane są syntetyczne. Wyniki poniżej wyjaśniam krok po kroku na podstawie reguł agregacji. PL i EN używają tego samego zbioru oraz tych samych nazw kolumn.
Najpierw wiersze, kolumny i agregacja
pandas.pivot_table tworzy tabelę przestawną z agregacją. W podstawowym wywołaniu wskazuję dane, miarę, klucze wierszy i kolumn oraz funkcję agregującą. Domyślne aggfunc="mean" łatwo przeoczyć, dlatego w przykładach zapisuję wybór jawnie.
import pandas as pd
sales = pd.DataFrame({
"region": ["North", "North", "North", "South", "South"],
"product": ["A", "A", "B", "A", "B"],
"amount": [10, 30, 20, 40, 50],
})
totals = pd.pivot_table(
sales,
values="amount",
index="region",
columns="product",
aggfunc="sum",
sort=True,
)
Jedna pozycja źródła oznacza pojedynczą obserwację sprzedaży w umownej jednostce. Nie nazywam jej klientem ani unikalnym zamówieniem, bo nie ma tu takich identyfikatorów. Dwa wiersze North/A są dwiema obserwacjami, które chcemy zsumować. Gdyby były przypadkową kopią, problem należałoby rozwiązać przed agregacją.
| region | product | amount |
|---|---|---|
| North | A | 10 |
| North | A | 30 |
| North | B | 20 |
| South | A | 40 |
| South | B | 50 |
Jak odczytać wynik sumowania
Dla North/A łączę 10 i 30, więc otrzymuję 40. North/B ma jedną wartość 20. W South pozostają odpowiednio 40 i 50. Tabela wyniku wygląda następująco; nagłówki opisują produkt, a lewa kolumna region.
| region | A | B |
|---|---|---|
| North | 40 | 20 |
| South | 40 | 50 |
Kontrola jest prosta: North ma razem 60, South 90, a całość 150. Te same wartości muszą wynikać z pięciu wierszy wejścia. Zgodność sumy nie dowodzi jeszcze poprawności każdego przypisania, dlatego sprawdzam również konkretną komórkę. Przesunięcie sprzedaży między regionami mogłoby zachować sumę globalną.
Jeśli potrzebujesz tylko zestawienia regionów, możesz pominąć columns. Jeżeli rozbijasz wynik na dodatkowy klucz, każda kombinacja grup wymaga osobnego odczytu. Ja dodawałbym szczegół wtedy, gdy odpowiada na pytanie odbiorcy. Bardzo szeroka tabela może ukryć prostą informację skuteczniej niż nieudany wykres.
Średnia oznacza inne pytanie
Zmieniam wyłącznie agregację:
averages = pd.pivot_table(
sales,
values="amount",
index="region",
columns="product",
aggfunc="mean",
)
North/A to teraz średnia dwóch obserwacji: suma 40 podzielona przez 2 daje 20. Pozostałe komórki mają po jednej obserwacji, więc zostają 20, 40 i 50. Nie porównuj tego wyniku z sumą tak, jakby zmienił się poziom sprzedaży. Zmieniło się pytanie.
Dla cen 4000, 2500 i 4100 zwykła średnia obserwacji wynosi 10600 podzielone przez 3, czyli około 3533,33. Każdy wiersz ma tutaj równą wagę. Jeśli jeden opisuje jedną sztukę, a drugi sto sztuk, średnia cen z wierszy nie jest automatycznie średnią ceną sprzedanej sztuki. Przed obliczeniem trzeba uzgodnić wagę i jednostkę analizy.

Podsumowania i pułapka średniej ze średnich
Wariant z margins=True dodaje podsumowania. Dokumentacja przekształceń pokazuje takie agregaty dla przekrojów tabeli. Przy sumowaniu naszego zbioru oczekuję układu:
with_totals = pd.pivot_table(
sales,
values="amount",
index="region",
columns="product",
aggfunc="sum",
margins=True,
)
| region | A | B | All |
|---|---|---|---|
| North | 40 | 20 | 60 |
| South | 40 | 50 | 90 |
| All | 80 | 70 | 150 |
Przy mean globalny wynik wynosi 150 podzielone przez 5, czyli 30. Średnia regionu North to 60 podzielone przez 3, czyli 20. South ma 90 podzielone przez 2, czyli 45. Prosta średnia z 20 i 45 daje 32,5, więc nie odtwarza całości. Grupy mają różne liczebności. Podsumowanie średnich trzeba czytać z uwzględnieniem danych, z których powstało.
Brak komórki, brak wartości i zero
Argument fill_value działa na wynik po agregacji. Rozważ osobny zbiór:
partial = pd.DataFrame({
"region": ["North", "South"],
"product": ["A", "B"],
"amount": [10, 50],
})
filled = pd.pivot_table(
partial,
values="amount",
index="region",
columns="product",
aggfunc="sum",
fill_value=0,
)
W takim zestawieniu North/B i South/A pokażą zero. Z danych wiemy jednak tylko, że nie ma obserwacji tych kombinacji. Jeśli źródło jest pełne i brak wiersza oznacza brak sprzedaży, to sensowna prezentacja. Jeśli brakuje pliku, zero zamaskuje lukę.
Nie traktuję też count jak bezwarunkowej liczby rekordów: Series.count liczy wartości niebrakujące. Gdy liczę wypełnione kwoty, muszę pamiętać o wierszach bez kwoty. Liczba klientów wymaga jeszcze innego pytania o identyfikator i unikalność. Sama nazwa agregacji nie ustala jednostki.
Kategorie i wersja pandas
Dla kluczy typu kategorycznego znaczenie ma observed. W pandas 3.0 domyślna wartość zmieniła się na True. Jeśli układ raportu zależy od pokazywania niezaobserwowanych kategorii, ustaw ten argument świadomie i sprawdź wersję środowiska. Przykład główny nie definiuje kategorii, więc nie opiera wyniku na ich pełnym katalogu.
Osobnej decyzji wymagają braki w kluczach grup. W dokumentacji 3.1.1 dropna=True usuwa grupy z brakującymi kluczami; wpływa też na dane uwzględniane przy podsumowaniach. Przed raportowaniem ustal, czy brak regionu ma być osobną kategorią kontrolną. Utrata takich wierszy może sprawić, że kompletna tabela będzie tylko pozornie kompletna.
Kiedy użyć pivot lub crosstab
DataFrame.pivot zmienia układ bez agregacji. W naszym głównym zbiorze North/A występuje dwa razy, więc takie przestawienie zgłosi błąd. To przydatny sygnał, jeśli oczekiwałeś jednej wartości na parę kluczy. Nie naprawiałbym go automatycznie średnią bez wyjaśnienia duplikatu.
Gdy interesuje Cię głównie liczba obserwacji w kombinacjach kategorii, zobacz hasło o pandas crosstab. Do zamiany szerokiej tabeli na długą służy pandas melt. Narzędzie wybieram po określeniu oczekiwanego kształtu i znaczenia wyniku.
Ćwiczenie: zmień jedną obserwację
Dopisz w myślach do naszego zbioru North/A z kwotą 20. Najpierw przewidź nową sumę tej komórki i liczbę obserwacji. Suma wzrośnie do 60, liczba do trzech, ale średnia pozostanie równa 20. To prosty sposób, żeby zobaczyć różnicę między wzrostem wolumenu a zmianą wartości przeciętnej.
Potem usuń jedyną obserwację North/B i zastanów się, co powinien zobaczyć odbiorca. Zero, brak czy ostrzeżenie zależy od kompletności danych i definicji raportu. Ja zakończyłbym pracę dopiero wtedy, gdy potrafię wyjaśnić zarówno liczbę w tabeli, jak i znaczenie pustego miejsca.
Najważniejsze w pigułce
Czym pivot_table różni się od pivot?
pivot_table agreguje wiersze należące do tej samej kombinacji grup. pivot zmienia układ bez agregacji i zgłasza błąd, gdy kombinacja indeksu i kolumny ma kilka wartości. Wybór zależy od znaczenia danych.
Czy fill_value=0 uzupełnia brakujące dane źródłowe?
Nie. Ten argument zastępuje braki w wynikowej tabeli po agregacji. Zero jest właściwe tylko wtedy, gdy odpowiada znaczeniu danej komórki; nie dowodzi kompletności źródła.
Czy margins=True zawsze sumuje komórki?
Podsumowania korzystają z wybranej funkcji agregującej. Przy mean są średnimi z danych dla odpowiedniego przekroju, a nie prostą średnią widocznych średnich grup.
Przy pracy nad artykułem użyto w zautomatyzowany sposób sztucznej inteligencji. Jeśli też chcesz tak umieć, sprawdź ten projekt.
