Fiszki Online pandas (Preview)
Darmowy podgląd 15 z 53 dostępnych pytań
Podstawy pandas
Czym jest pandas i jakie problemy rozwiązuje w analizie i przetwarzaniu danych?
Odpowiedź w 30 sekund:
pandas to biblioteka Pythona do pracy z danymi tabelarycznymi i szeregami czasowymi. Dostarcza dwie kluczowe struktury — Series (jednowymiarową) i DataFrame (dwuwymiarową) — oraz zestaw narzędzi do wczytywania, czyszczenia, transformacji, łączenia i agregacji danych. Rozwiązuje problem wygodnej, wydajnej i czytelnej manipulacji danymi, której czysty Python (listy, słowniki) nie zapewnia.
Odpowiedź w 2 minuty: pandas powstała, by dać analitykom i programistom w Pythonie narzędzie zbliżone wygodą do arkusza kalkulacyjnego, ale z mocą języka programowania i wydajnością obliczeń wektorowych. Zamiast pisać pętle po wierszach, operuje się na całych kolumnach naraz (operacje wektoryzowane oparte o NumPy), co jest jednocześnie szybsze i bardziej zwięzłe.
W praktyce pandas rozwiązuje cały cykl pracy z danymi: wczytywanie z wielu formatów (CSV, Excel, JSON, SQL, Parquet), czyszczenie (obsługa braków danych NaN, duplikatów, niespójnych typów), transformację (filtrowanie, dodawanie kolumn, zmiana kształtu), łączenie wielu zbiorów (merge, join, concat), grupowanie i agregację (groupby) oraz pracę z indeksem czasowym. Dzięki temu jest podstawą ekosystemu data science w Pythonie i naturalnym etapem przed wizualizacją (matplotlib, seaborn) czy uczeniem maszynowym (scikit-learn).
Typowe błędne przekonania: (1) że pandas jest „tylko ładnym Excelem" — w rzeczywistości jest w pełni programowalna, powtarzalna i skalowalna do milionów wierszy; (2) że pętla po wierszach (for/iterrows) to dobry sposób przetwarzania — niemal zawsze lepsze są operacje wektorowe; (3) że pandas jest dobra do danych większych niż RAM — przy bardzo dużych zbiorach lepiej sięgnąć po Dask, Polars lub bazę danych.
Przykład kodu:
import pandas as pd
# Wczytanie danych z pliku CSV
df = pd.read_csv("sprzedaz.csv")
# Szybki podgląd: pierwsze wiersze, typy kolumn, statystyki
print(df.head()) # pierwsze 5 wierszy
print(df.info()) # typy danych i liczba braków
print(df.describe()) # statystyki kolumn liczbowych
# Filtrowanie i agregacja w kilku liniach
sprzedaz_pln = df[df["waluta"] == "PLN"]
suma_wg_regionu = sprzedaz_pln.groupby("region")["kwota"].sum()
print(suma_wg_regionu)
Materiały
↑ Powrót na góręCzym różni się Series od DataFrame?
Odpowiedź w 30 sekund:
Series to jednowymiarowa, etykietowana tablica wartości jednego typu — odpowiednik pojedynczej kolumny. DataFrame to dwuwymiarowa struktura tabelaryczna: zbiór kolumn (Series), które współdzielą jeden wspólny Index wierszy. Wyciągnięcie jednej kolumny z DataFrame zwraca Series.
Odpowiedź w 2 minuty:
Series można sobie wyobrazić jako tablicę NumPy wzbogaconą o etykiety (indeks). Ma jeden wymiar, jeden typ danych (dtype) i własny Index, który pozwala adresować elementy po etykietach, a nie tylko po pozycji. Posiada też atrybut name, który po umieszczeniu w DataFrame staje się nazwą kolumny.
DataFrame to dwuwymiarowa tabela — kolekcja kolumn, z których każda jest osobną Series. Kluczowe jest to, że wszystkie kolumny dzielą ten sam Index wierszy. Każda kolumna może mieć inny typ danych (jedna int64, druga object/tekst, trzecia datetime64), podczas gdy w obrębie jednej kolumny typ jest spójny. DataFrame ma dwa wymiary etykiet: df.index (etykiety wierszy) oraz df.columns (etykiety kolumn).
Najczęstsze pułapki: (1) df["kolumna"] zwraca Series, a df[["kolumna"]] (lista z jedną nazwą) zwraca jednokolumnowy DataFrame — to różne typy; (2) wiele metod (np. apply, agregacje) zachowuje się inaczej na Series niż na DataFrame; (3) początkujący mylą jednowymiarowy charakter Series z jednokolumnowym DataFrame — to nie to samo.
Przykład kodu:
import pandas as pd
# Series — jeden wymiar, własny indeks, jeden typ
ceny = pd.Series([10.0, 25.5, 7.99], index=["a", "b", "c"], name="cena")
print(type(ceny)) # <class 'pandas.core.series.Series'>
print(ceny["b"]) # dostęp po etykiecie -> 25.5
# DataFrame — wiele kolumn (Series) o wspólnym indeksie
df = pd.DataFrame({
"produkt": ["Jabłko", "Banan", "Wiśnia"],
"cena": [10.0, 25.5, 7.99],
"ilosc": [3, 1, 12],
})
# Wyciągnięcie kolumny -> Series
print(type(df["cena"])) # <class 'pandas.core.series.Series'>
# Wyciągnięcie listy kolumn -> DataFrame (nawet jednokolumnowy)
print(type(df[["cena"]])) # <class 'pandas.core.frame.DataFrame'>
Porównanie:
| Cecha | Series |
DataFrame |
|---|---|---|
| Wymiary | 1 (jednowymiarowa) | 2 (tabela) |
| Typy danych | jeden dtype na całość |
różne dtype per kolumna |
| Etykiety | jeden Index |
index (wiersze) + columns |
| Analogia | jedna kolumna / wektor | cała tabela / arkusz |
| Dostęp | s["etykieta"] |
df["kolumna"], df.loc[...] |
Diagram:
flowchart TB
subgraph DF["DataFrame"]
IDX["Index (wspólny dla wierszy)<br/>0, 1, 2"]
C1["Series: 'produkt'<br/>dtype object"]
C2["Series: 'cena'<br/>dtype float64"]
C3["Series: 'ilosc'<br/>dtype int64"]
end
IDX --- C1
IDX --- C2
IDX --- C3
Materiały
↑ Powrót na góręJaka jest relacja między pandas a NumPy i jak pandas wykorzystuje tablice NumPy „pod maską"?
Odpowiedź w 30 sekund:
pandas jest zbudowana na NumPy — kolumny Series/DataFrame przechowują dane w tablicach ndarray (lub w nowszych typach ExtensionArray), a operacje wektorowe pandas delegują obliczenia do skompilowanego kodu NumPy. pandas dodaje na wierzchu etykietowane indeksy, obsługę braków danych, heterogeniczne typy kolumn i bogaty zestaw metod do analizy. Mówiąc skrótowo: NumPy to silnik liczbowy, a pandas to etykietowana, „tabelaryczna" warstwa nad nim.
Odpowiedź w 2 minuty:
Historycznie pandas powstała jako warstwa wygody nad NumPy. Tablica NumPy (ndarray) jest szybka i pamięciowo zwarta, ale operuje na czystych pozycjach (0, 1, 2...) i zwykle jeden typ na całą tablicę. pandas dokłada do tego: etykietowane osie (Index), automatyczne wyrównywanie danych po etykietach, pierwszorzędną obsługę braków (NaN/NA) oraz możliwość trzymania w jednej tabeli kolumn różnych typów.
„Pod maską" klasyczny DataFrame przechowuje dane w blokach (BlockManager) — kolumny tego samego typu są często grupowane we wspólne tablice NumPy. Gdy wywołasz operację wektorową (np. df["a"] + df["b"] czy df["a"].sum()), pandas przekazuje ją do NumPy, które wykonuje obliczenia w skompilowanym C — dlatego operacje wektorowe są wielokrotnie szybsze niż pętle w Pythonie. Dostęp do surowych danych daje Series.to_numpy() lub df.to_numpy(); starszy atrybut .values działa podobnie, ale jest mniej zalecany dla nowych typów.
Warto znać niuanse: (1) od pandas 1.0+ istnieją typy nullable (Int64, boolean, string) oparte o ExtensionArray, które nie są zwykłym ndarray — pozwalają na braki danych bez konwersji do float; (2) backend Apache Arrow (dtype_backend="pyarrow") to alternatywa dla NumPy przy tekstach i dużych zbiorach; (3) mieszanie typów w DataFrame powoduje, że df.to_numpy() zwróci tablicę typu object, tracąc wydajność — to typowa pułapka. Mimo rosnącej roli Arrow, NumPy pozostaje fundamentem dla danych liczbowych w pandas.
Przykład kodu:
import pandas as pd
import numpy as np
s = pd.Series([1, 2, 3, 4], name="x")
# Pod spodem Series trzyma tablicę NumPy
print(type(s.to_numpy())) # <class 'numpy.ndarray'>
# Funkcje NumPy działają bezpośrednio na obiektach pandas
print(np.sqrt(s)) # zwektoryzowane, liczone w C
# Operacja wektorowa delegowana do NumPy (bez pętli w Pythonie)
df = pd.DataFrame({"a": [1, 2, 3], "b": [10, 20, 30]})
df["suma"] = df["a"] + df["b"] # szybkie dodawanie kolumn
# Surowe dane całego DataFrame jako tablica NumPy
arr = df.to_numpy()
print(arr.dtype) # wspólny typ; przy mieszanych -> object
Diagram:
flowchart TB
A["Twój kod pandas<br/>df['a'] + df['b']"] --> B["Warstwa pandas<br/>Index, etykiety, obsługa NaN, dtypes"]
B --> C["BlockManager<br/>grupowanie kolumn w bloki"]
C --> D["NumPy ndarray / ExtensionArray"]
D --> E["Skompilowany kod C / SIMD<br/>obliczenia wektorowe"]
Materiały
- pandas — Package overview (relation to NumPy)
- pandas — Internals (Block & data structure)
- pandas.DataFrame.to_numpy — API reference
Czym jest indeks (Index) w pandas i jaką pełni rolę przy dostępie do danych oraz wyrównywaniu (alignment)?
Odpowiedź w 30 sekund:
Index to etykietowana oś Series i DataFrame — zbiór nazw przypisanych do wierszy (i, w postaci columns, do kolumn). Pełni dwie kluczowe role: pozwala adresować dane po etykietach (.loc) zamiast tylko po pozycji oraz umożliwia automatyczne wyrównywanie (alignment) danych po wspólnych etykietach podczas operacji arytmetycznych i łączenia.
Odpowiedź w 2 minuty:
Index to niemutowalny obiekt podobny do tablicy, przechowujący etykiety osi. W DataFrame istnieją dwa indeksy: df.index (etykiety wierszy) i df.columns (etykiety kolumn). Indeksem mogą być liczby, napisy, daty (DatetimeIndex), a nawet wiele poziomów naraz (MultiIndex). Domyślnie pandas tworzy RangeIndex (0, 1, 2...), ale często ustawia się własny indeks znaczący, np. identyfikator klienta czy znacznik czasu, przez set_index.
Najważniejsza, często niedoceniana, funkcja indeksu to wyrównywanie danych (alignment). Gdy wykonujesz operację na dwóch obiektach pandas — np. dodawanie dwóch Series — pandas najpierw dopasowuje je po etykietach indeksu, a nie po pozycji. Dzięki temu sumowane są wartości o tej samej etykiecie, niezależnie od ich kolejności, a tam, gdzie etykieta występuje tylko w jednym obiekcie, wynikiem jest NaN. To samo dotyczy przypisywania kolumn czy łączenia operacji join. Alignment eliminuje całą klasę cichych błędów znanych z arkuszy, gdzie wiersze trzeba ręcznie układać w tej samej kolejności.
Indeks ma też wpływ na wydajność i poprawność: dobrze dobrany, unikalny indeks przyspiesza wyszukiwanie (.loc) i scalanie; indeks z duplikatami może dawać niejednoznaczne wyniki. Typowe pułapki: (1) mylenie dostępu po etykiecie (.loc) z dostępem po pozycji (.iloc) — przy indeksie liczbowym łatwo o pomyłkę; (2) zapomnienie o reset_index() po groupby/set_index, gdy indeks staje się niewygodny; (3) zaskoczenie wartościami NaN po operacji na obiektach o różnych indeksach — to nie błąd, lecz właśnie efekt wyrównywania.
Przykład kodu:
import pandas as pd
# Ustawienie znaczącego indeksu zamiast domyślnego 0,1,2...
df = pd.DataFrame({
"klient": ["Anna", "Bartek", "Celina"],
"obroty": [1200, 800, 1500],
})
df = df.set_index("klient")
# Dostęp po etykiecie (.loc) vs po pozycji (.iloc)
print(df.loc["Bartek"]) # po etykiecie
print(df.iloc[0]) # po pozycji (pierwszy wiersz)
# Wyrównywanie (alignment) po etykietach indeksu
q1 = pd.Series({"Anna": 100, "Bartek": 200})
q2 = pd.Series({"Bartek": 50, "Celina": 70})
razem = q1 + q2
print(razem)
# Anna NaN <- brak w q2
# Bartek 250.0 <- 200 + 50, dopasowane po etykiecie
# Celina NaN <- brak w q1
# Aby uniknąć NaN, podaj wartość wypełnienia
razem2 = q1.add(q2, fill_value=0)
print(razem2) # Anna 100, Bartek 250, Celina 70
Materiały
- pandas — Index objects (API reference)
- pandas — Intro to data structures: Data alignment
- pandas — Indexing and selecting data
Czym pandas różni się od arkusza kalkulacyjnego i relacyjnej bazy danych z perspektywy analityka?
Odpowiedź w 30 sekund: pandas łączy interaktywność arkusza z programowalnością i powtarzalnością kodu. W odróżnieniu od arkusza (np. Excela) każda transformacja jest zapisana jako kod — powtarzalny, wersjonowalny i skalowalny do milionów wierszy. W odróżnieniu od relacyjnej bazy (SQL) pandas działa w pamięci procesu, ma uporządkowany indeks oraz pełnię Pythona do dowolnej logiki, ale gorzej radzi sobie z danymi większymi niż RAM i z trwałym, współbieżnym przechowywaniem.
Odpowiedź w 2 minuty: Wobec arkusza kalkulacyjnego główna różnica to powtarzalność i skala. W Excelu manipulacje wykonuje się ręcznie, mieszając dane z formułami w tych samych komórkach, co utrudnia audyt i odtworzenie wyniku. W pandas proces opisuje kod: ten sam skrypt da identyczny wynik na nowych danych, można go testować, wersjonować w git i uruchamiać automatycznie. pandas obsługuje też znacznie większe zbiory niż praktyczny limit arkusza i daje pełną moc Pythona (pętle warunkowe, funkcje, integracje, uczenie maszynowe). Arkusz wygrywa za to natychmiastową wizualną interaktywnością i niskim progiem wejścia dla osób nietechnicznych.
Wobec relacyjnej bazy danych pandas też ma wiele wspólnego — merge odpowiada JOIN, groupby to GROUP BY, filtrowanie to WHERE. Różnice są jednak istotne. Baza danych jest zaprojektowana do trwałego, spójnego, współbieżnego przechowywania danych (transakcje ACID, integralność, dostęp wielu użytkowników) i potrafi pracować na zbiorach znacznie większych niż pamięć dzięki optymalizatorowi zapytań i indeksom dyskowym. pandas natomiast trzyma wszystko w RAM jednego procesu — jest szybka i elastyczna do eksploracji oraz transformacji, ale nie jest magazynem danych. Dodatkowo pandas zachowuje kolejność wierszy i ma uporządkowany Index z natywnym wyrównywaniem, podczas gdy w SQL relacja jest z definicji zbiorem nieuporządkowanym.
W praktyce te narzędzia się uzupełniają: typowy przepływ to wyciągnięcie danych z bazy (SQL) lub arkusza, wczytanie do pandas (pd.read_sql, pd.read_excel), przetworzenie i analiza w Pythonie, a następnie zapis wyniku z powrotem lub do raportu. Pułapki analityka: (1) traktowanie pandas jak bazy danych przy danych większych niż RAM (lepiej Dask/Polars/SQL); (2) przenoszenie do Pythona całej tabeli, którą baza przefiltrowałaby taniej po stronie serwera; (3) zakładanie, że wyniki SQL przyjdą w określonej kolejności bez ORDER BY.
Porównanie:
| Cecha | Arkusz (Excel) | pandas | Baza relacyjna (SQL) |
|---|---|---|---|
| Powtarzalność | niska (ręczne kliknięcia) | wysoka (kod, git) | wysoka (zapytania) |
| Skala danych | tysiące–setki tys. wierszy | do rozmiaru RAM | bardzo duża (dysk) |
| Trwałość / współbieżność | plik | brak (w pamięci) | mocna (ACID, wielu użytkowników) |
| Kolejność / indeks | wizualna | uporządkowany Index |
zbiór nieuporządkowany |
| Elastyczność logiki | formuły, makra | pełnia Pythona | SQL + procedury |
| Próg wejścia | bardzo niski | średni | średni–wysoki |
Przykład kodu:
import pandas as pd
# Te same operacje w stylu SQL, ale w pandas
# WHERE region = 'PL'
filtr = df[df["region"] == "PL"]
# GROUP BY region; SUM(kwota)
agg = df.groupby("region")["kwota"].sum()
# JOIN po kluczu 'id_klienta'
polaczone = pd.merge(zamowienia, klienci, on="id_klienta", how="left")
# Most między światami: wczytaj z bazy, przetwórz, zapisz do Excela
# df = pd.read_sql("SELECT * FROM sprzedaz WHERE rok = 2025", conn)
# df.to_excel("raport.xlsx", index=False)
Materiały
↑ Powrót na góręInspekcja i eksploracja danych
Jakich metod używasz do wstępnego przeglądu danych (head, tail, info, describe, shape)?
Odpowiedź w 30 sekund:
Po wczytaniu danych zaczynam od df.shape (ile wierszy i kolumn), df.head()/df.tail() (podgląd pierwszych i ostatnich rekordów), df.info() (typy kolumn i liczba wartości niepustych) oraz df.describe() (statystyki opisowe). Ta sekwencja w kilkanaście sekund daje obraz rozmiaru, struktury, typów i jakości zbioru.
Odpowiedź w 2 minuty: Wstępny przegląd danych (tzw. pierwszy rzut oka, część EDA) ma odpowiedzieć na pytania: jak duży jest zbiór, jakie ma kolumny, jakiego typu są dane i czy nie ma w nich oczywistych problemów. Każda z metod pełni tu inną rolę i warto je traktować jako uzupełniający się zestaw, a nie wymienne.
df.shape zwraca krotkę (liczba_wierszy, liczba_kolumn) — to pierwsza informacja, która mówi mi o skali danych i o tym, czy import w ogóle się powiódł (np. czy nie wczytałem wszystkiego do jednej kolumny przez zły separator). df.head(n) i df.tail(n) pokazują domyślnie 5 pierwszych/ostatnich wierszy — pozwalają zobaczyć realne wartości, format dat, jednostki, ewentualne wiersze nagłówkowe lub podsumowujące doklejone na końcu pliku. df.sample(n) jest cennym uzupełnieniem, bo losowa próbka czasem ujawnia problemy ukryte w środku zbioru, których nie widać na początku ani końcu.
df.info() to moja najważniejsza metoda diagnostyczna: pokazuje nazwy kolumn, ich dtype, liczbę wartości niepustych (z czego od razu wnioskuję o brakach) oraz zużycie pamięci. Jeśli kolumna, która powinna być liczbą, ma typ object, to sygnał, że gdzieś są wartości tekstowe lub błędne separatory dziesiętne. df.describe() domyślnie liczy statystyki tylko dla kolumn numerycznych (liczność, średnia, odchylenie, min, kwartyle, max). Aby objąć też kolumny tekstowe i kategoryczne, używam df.describe(include='all'), co dodaje unique, top i freq. Dobrym nawykiem jest też df.columns, df.dtypes oraz df.index, gdy interesuje mnie struktura, a nie wartości.
Przykład kodu:
import pandas as pd
df = pd.read_csv("sprzedaz.csv")
# Skala zbioru: (liczba_wierszy, liczba_kolumn)
print(df.shape)
# Podgląd realnych wartości z początku i końca
df.head() # domyślnie 5 wierszy
df.tail(3) # ostatnie 3 wiersze
df.sample(5) # losowa próbka — ujawnia problemy w środku zbioru
# Typy, liczba wartości niepustych, zużycie pamięci
df.info()
# Statystyki opisowe — tylko kolumny numeryczne
df.describe()
# Statystyki dla WSZYSTKICH kolumn (też tekstowych/kategorycznych)
df.describe(include="all")
# Szybki przegląd struktury bez wartości
df.dtypes # typy kolumn
df.columns # nazwy kolumn
Materiały
↑ Powrót na góręCzyszczenie danych i braki
Jak pandas reprezentuje wartości brakujące (NaN, None, NaT, pd.NA) i czym się one różnią?
Odpowiedź w 30 sekund:
Historycznie pandas używa NaN (typu float) jako uniwersalnego markera braku — dlatego kolumna z brakiem często „awansuje" do typu float64. None to obiekt Pythona (pojawia się w kolumnach typu object), NaT (Not a Time) oznacza brak w datach (datetime64/timedelta64), a pd.NA to nowszy, typowo-neutralny marker dla nullable dtypes (Int64, boolean, string).
Odpowiedź w 2 minuty:
NaN (Not a Number) pochodzi ze standardu IEEE 754 i jest wartością typu float. To powód, dla którego pandas, wstawiając brak do kolumny liczb całkowitych, zamienia całą kolumnę na float64 — w klasycznym typie int64 nie da się zapisać „pustki", więc 1, 2, brak staje się 1.0, 2.0, NaN. Druga konsekwencja: NaN != NaN, więc braków nie wykrywa się porównaniem == NaN, tylko funkcjami isna/notna.
None to obiekt NoneType Pythona. Gdy trafia do kolumny object, pandas trzyma go dosłownie; gdy do kolumny liczbowej float, pandas często konwertuje go „w locie" na NaN. NaT to odpowiednik NaN dla typów czasowych — używany w kolumnach datetime64[ns] oraz timedelta64[ns], bo NaN (float) nie pasuje do typu daty.
pd.NA (od pandas 1.0) to nowy, skalarny marker braku, niezależny od konkretnego typu. Jest używany przez tzw. nullable dtypes: Int64 (z dużej litery!), Float64, boolean oraz string. Dzięki niemu można mieć kolumnę liczb całkowitych z brakami BEZ konwersji na float — typ pozostaje całkowity, a brak reprezentuje pd.NA. Kluczowa różnica semantyczna: pd.NA propaguje się trójwartościowo (logika Kleene'a), np. pd.NA == 1 daje pd.NA, a nie False. Mimo różnic, pd.isna() rozpoznaje wszystkie cztery markery jednolicie — i to jest sposób, którego należy używać.
Przykład kodu:
import pandas as pd
import numpy as np
# NaN psuje typ całkowity -> kolumna staje się float64
s_int = pd.Series([1, 2, None])
print(s_int.dtype) # float64
print(s_int.tolist()) # [1.0, 2.0, nan]
# nullable Int64 zachowuje typ całkowity, brak to <NA>
s_nullable = pd.Series([1, 2, None], dtype="Int64")
print(s_nullable.dtype) # Int64
print(s_nullable.tolist()) # [1, 2, <NA>]
# NaT dla dat
s_date = pd.Series(pd.to_datetime(["2024-01-01", None]))
print(s_date.dtype) # datetime64[ns]
print(s_date[1]) # NaT
# NaN nie jest równy samemu sobie - dlatego nie porównujemy przez ==
print(np.nan == np.nan) # False
print(pd.isna(np.nan)) # True -> poprawny sposób wykrywania
# pd.isna rozpoznaje wszystkie markery jednolicie
print(pd.isna([np.nan, None, pd.NaT, pd.NA])) # [ True True True True]
Diagram:
flowchart TD
A["Typ kolumny?"] --> B["float64 / int64"]
A --> C["datetime64 / timedelta64"]
A --> D["object"]
A --> E["nullable: Int64 / Float64 / boolean / string"]
B --> B1["Marker braku: NaN (float)"]
C --> C1["Marker braku: NaT"]
D --> D1["Marker braku: None lub NaN"]
E --> E1["Marker braku: pd.NA"]
B1 --> F["pd.isna() wykrywa wszystkie"]
C1 --> F
D1 --> F
E1 --> F
Materiały
↑ Powrót na góręGrupowanie i agregacja
Jak działa groupby i na czym polega model „split-apply-combine"?
Odpowiedź w 30 sekund:
groupby dzieli DataFrame na grupy według wartości jednej lub wielu kolumn, do każdej grupy stosuje operację (agregację, transformację lub filtrowanie), a wyniki łączy z powrotem w jeden obiekt. To realizacja wzorca „split-apply-combine" (podziel-zastosuj-połącz), który jest fundamentem analizy danych w pandas.
Odpowiedź w 2 minuty:
Model „split-apply-combine" składa się z trzech kroków. W fazie split (podział) pandas dzieli dane na grupy na podstawie klucza grupowania, którym najczęściej są wartości jednej lub kilku kolumn. W fazie apply (zastosuj) do każdej grupy z osobna stosowana jest funkcja: agregacja (sum, mean, count), transformacja (transform) lub filtrowanie (filter). W fazie combine (połącz) pandas scala wyniki cząstkowe w finalną strukturę – Series lub DataFrame.
Kluczowa obserwacja: samo wywołanie df.groupby('kolumna') nie wykonuje jeszcze żadnych obliczeń. Zwraca leniwy obiekt DataFrameGroupBy, który przechowuje informację o przynależności wierszy do grup. Obliczenia uruchamiają się dopiero, gdy wywołasz na nim metodę agregującą lub transformującą.
Domyślnie wartości klucza grupowania stają się indeksem wyniku. Możesz to zmienić argumentem as_index=False, aby klucze pozostały zwykłymi kolumnami – wygodne, gdy planujesz dalsze łączenia (merge). Warto też pamiętać, że domyślnie groupby pomija grupy z wartościami NaN w kluczu; aby je zachować, użyj dropna=False.
W praktyce groupby zastępuje ręczne pętle po unikalnych wartościach – jest znacznie szybszy (operacje wektorowe na poziomie C) i czytelniejszy. To pierwszy odruch, gdy odpowiadasz na pytanie typu „jaka jest średnia X w podziale na Y".
Przykład kodu:
import pandas as pd
df = pd.DataFrame({
"dzial": ["IT", "IT", "HR", "HR", "IT"],
"pracownik": ["Anna", "Bartek", "Celina", "Damian", "Ewa"],
"pensja": [12000, 9000, 7000, 7500, 11000],
})
# Podział na grupy wg działu i agregacja (split-apply-combine)
srednie = df.groupby("dzial")["pensja"].mean()
print(srednie)
# dzial
# HR 7250.0
# IT 10666.7
# Name: pensja, dtype: float64
# Klucz jako zwykła kolumna zamiast indeksu
srednie_kolumna = df.groupby("dzial", as_index=False)["pensja"].mean()
# Sam obiekt groupby jest leniwy – nie liczy nic od razu
g = df.groupby("dzial")
print(type(g)) # <class 'pandas.core.groupby.generic.DataFrameGroupBy'>
print(g.ngroups) # 2 (liczba grup)
print(g.size()) # liczność każdej grupy
Diagram:
flowchart LR
A["Dane wejściowe (DataFrame)"] --> B["SPLIT: podział na grupy wg klucza"]
B --> C1["Grupa: IT"]
B --> C2["Grupa: HR"]
C1 --> D1["APPLY: zastosuj funkcję (np. mean)"]
C2 --> D2["APPLY: zastosuj funkcję (np. mean)"]
D1 --> E["COMBINE: połącz wyniki"]
D2 --> E
E --> F["Wynik (Series / DataFrame)"]
Materiały
↑ Powrót na góręReshaping i struktura danych
Do czego służą melt i pivot oraz jak się wzajemnie uzupełniają?
Odpowiedź w 30 sekund:
melt przekształca format szeroki w długi — „topi" wiele kolumn w dwie: nazwę zmiennej i wartość. pivot robi odwrotnie: rozkłada format długi z powrotem na szeroki, zamieniając unikalne wartości jednej kolumny w nagłówki kolumn. To operacje wzajemnie odwrotne: melt rozwija, pivot zwija.
Odpowiedź w 2 minuty:
DataFrame.melt() bierze zestaw kolumn-identyfikatorów (id_vars), które zostają nietknięte, oraz zestaw kolumn do „stopienia" (value_vars, domyślnie wszystkie pozostałe). Z każdej takiej kolumny tworzy parę wierszy: jeden w nowej kolumnie var_name (etykieta) i jeden w value_name (wartość). Efekt to klasyczne dane tidy, gotowe do groupby i wizualizacji.
DataFrame.pivot() to operacja odwrotna. Przyjmuje index (co zostaje wierszami), columns (kolumna, której unikalne wartości staną się nowymi nagłówkami) i values (skąd wziąć liczby do wypełnienia). Ważne: pivot zakłada, że kombinacja index + columns jest unikalna — jeśli zdarzą się duplikaty, rzuci błąd ValueError. Gdy musisz zagregować duplikaty (np. zsumować), użyj pivot_table z parametrem aggfunc, który działa jak skrót na groupby + reshape.
Wzajemne uzupełnianie się: typowy pipeline analityczny to pivot lub pivot_table jako narzędzie podsumowania (np. macierz sprzedaży produkt × miesiąc do raportu) i melt jako narzędzie „rozpakowania" danych przyszłych z powrotem do postaci długiej, gdy ktoś dostarczył je w szerokiej tabeli. Często stosuje się je naprzemiennie: melt, żeby uporządkować dane wejściowe, potem analiza w formacie długim, a na końcu pivot_table, żeby zbudować czytelne podsumowanie krzyżowe.
Drobna pułapka nazewnicza: nie myl pivot z pivot_table. Pierwszy tylko przestawia (wymaga unikalności), drugi dodatkowo agreguje. W praktyce do raportów częściej sięga się po pivot_table, bo jest odporniejszy na zduplikowane klucze.
Przykład kodu:
import pandas as pd
# Dane w formacie długim (tidy)
long = pd.DataFrame({
"produkt": ["Fiszki PL", "Fiszki PL", "Fiszki EN", "Fiszki EN"],
"miesiac": ["styczen", "luty", "styczen", "luty"],
"przychod": [120, 140, 90, 110],
})
# pivot: long -> wide (każdy miesiąc staje się kolumną)
wide = long.pivot(index="produkt", columns="miesiac", values="przychod")
print(wide)
# miesiac luty styczen
# produkt
# Fiszki EN 110 90
# Fiszki PL 140 120
# melt: wide -> long (operacja odwrotna do pivot)
back_to_long = wide.reset_index().melt(
id_vars="produkt",
var_name="miesiac",
value_name="przychod",
)
print(back_to_long.head())
# Gdy klucze się powtarzają, pivot rzuca błąd -> użyj pivot_table z aggfunc
summary = long.pivot_table(
index="produkt",
columns="miesiac",
values="przychod",
aggfunc="sum", # agreguje duplikaty zamiast się wywracać
)
print(summary)
Diagram:
flowchart LR
WIDE["Tabela szeroka<br/>(kolumny = miesiące)"]
LONG["Tabela długa<br/>(kolumny: 'zmienna', 'wartość')"]
WIDE -- "melt" --> LONG
LONG -- "pivot" --> WIDE
LONG -- "pivot_table<br/>(z 'aggfunc')" --> WIDE
Materiały
- pandas.melt — dokumentacja API
- pandas.DataFrame.pivot — dokumentacja API
- Reshaping and pivot tables — dokumentacja pandas
Wydajność, pamięć i typowe pułapki
Dlaczego iterrows i apply bywają wolne i jakie są szybsze alternatywy (wektoryzacja, eval, query)?
Odpowiedź w 30 sekund:
iterrows jest wolny, bo dla każdego wiersza tworzy nowy obiekt Series i wykonuje pętlę w czystym Pythonie, tracąc całą przewagę wektoryzacji NumPy. apply po osi wierszy działa podobnie — to ukryta pętla Pythona. Szybsze są operacje wektorowe na całych kolumnach, a dla dużych wyrażeń arytmetycznych/filtrów — df.eval() i df.query(), które potrafią ominąć tworzenie kosztownych tymczasowych tablic.
Odpowiedź w 2 minuty:
Pandas i NumPy są szybkie, gdy operacje wykonują się na całych tablicach naraz — pętle dzieją się wtedy w skompilowanym kodzie C, bez narzutu interpretera Pythona. iterrows łamie ten model: iteruje wiersz po wierszu, a dla każdego wiersza konstruuje obiekt Series (z indeksem, wnioskowaniem typu itd.). Dodatkowo, ponieważ wiersz miesza typy z różnych kolumn, wartości są często rzutowane na wspólny typ object, co dodatkowo spowalnia i może zmieniać typy danych. Dla milionów wierszy te koszty kumulują się dramatycznie. apply(axis=1) jest tylko nieco lepszy — wciąż wywołuje funkcję Pythona raz na wiersz.
Pierwsza i najważniejsza alternatywa to wektoryzacja: zamiast pętli operuj na całych kolumnach. df["c"] = df["a"] + df["b"] jest o rzędy wielkości szybsze niż jakakolwiek pętla. Logikę warunkową wyrażaj przez np.where, np.select, maski boolowskie czy Series.map. Jeśli naprawdę musisz iterować (bo logika jest sekwencyjna), itertuples() jest znacznie szybszy od iterrows(), bo zwraca lekkie nazwane krotki zamiast obiektów Series. Dla operacji nieuniknienie elementowych rozważ wyniesienie danych do tablic NumPy (.to_numpy()) i pętlę po nich, ewentualnie Numbę do kompilacji JIT.
Dla dużych ramek przydatne są df.eval() i df.query(). Pozwalają zapisać wyrażenie jako string (df.eval("c = a * b + d"), df.query("a > 0 and b < 10")). Przy wystarczająco dużych danych pandas może użyć silnika numexpr, który ewaluuje wyrażenie blokami, unikając materializacji wielu pełnych tablic pośrednich — oszczędza to pamięć i bywa szybsze niż naiwna wektoryzacja łańcuchem operatorów. Dla małych ramek narzut parsowania stringa sprawia, że zwykła wektoryzacja jest szybsza, więc eval/query opłaca się głównie przy dużych zbiorach i złożonych wyrażeniach. Złota zasada: najpierw zmierz (%timeit), potem optymalizuj — i zawsze szukaj rozwiązania wektorowego, zanim sięgniesz po pętlę.
Przykład kodu:
import pandas as pd
import numpy as np
df = pd.DataFrame({"a": np.random.rand(1_000_000),
"b": np.random.rand(1_000_000)})
# NAJWOLNIEJ — iterrows: Series na każdy wiersz, pętla w Pythonie.
def wolno():
out = []
for _, row in df.iterrows():
out.append(row["a"] * row["b"])
return out
# WOLNO — apply po wierszach: ukryta pętla Pythona.
srednio = df.apply(lambda r: r["a"] * r["b"], axis=1)
# SZYBKO — wektoryzacja na całych kolumnach (operacja w C).
df["iloczyn"] = df["a"] * df["b"]
# Warunki bez pętli: np.where / np.select zamiast if-ów na wiersz.
df["flaga"] = np.where(df["a"] > 0.5, "duze", "male")
# eval/query — opłacalne dla dużych ramek i złożonych wyrażeń.
df.eval("wynik = a * b + (a - b) ** 2", inplace=False)
duze = df.query("a > 0.9 and b < 0.1")
# Jeśli MUSISZ iterować — itertuples zamiast iterrows.
for t in df.head().itertuples(index=False):
_ = t.a * t.b
Porównanie podejść (rosnące tempo, malejący narzut):
| Podejście | Mechanizm | Względna szybkość | Kiedy używać |
|---|---|---|---|
iterrows() |
Series na wiersz, pętla Pythona |
najwolniej | praktycznie nigdy |
apply(axis=1) |
funkcja Pythona na wiersz | wolno | gdy brak wersji wektorowej, mały zbiór |
itertuples() |
nazwane krotki, pętla Pythona | średnio | nieunikniona iteracja sekwencyjna |
eval / query |
string + silnik numexpr |
szybko (duże dane) | złożone wyrażenia na dużych ramkach |
| wektoryzacja (NumPy) | operacje na całych tablicach w C | najszybciej | domyślny wybór |
Materiały
↑ Powrót na góręTworzenie i wczytywanie danych
Jakie znasz sposoby utworzenia obiektu DataFrame (ze słownika, listy słowników, tablicy NumPy, listy krotek)?
Odpowiedź w 30 sekund:
DataFrame najczęściej tworzy się z słownika kolumn ({nazwa: lista_wartości}), gdzie klucze stają się nazwami kolumn. Można go też zbudować z listy słowników (każdy słownik to jeden wiersz), z tablicy NumPy 2D (z osobnym argumentem columns) oraz z listy krotek. Wybór zależy od tego, czy dane mamy „kolumnowo" (słownik), czy „wierszowo" (lista słowników/krotek).
Odpowiedź w 2 minuty:
Konstruktor pd.DataFrame(...) jest bardzo elastyczny i akceptuje wiele struktur wejściowych. Najbardziej naturalny dla pandas jest słownik kolumn — klucze to nazwy kolumn, a wartości to listy/tablice o równej długości. To podejście jest czytelne i wydajne, bo pandas wewnętrznie przechowuje dane kolumnowo. Gdy dane przychodzą „wierszowo" (np. z API zwracającego rekordy JSON), wygodniejsza jest lista słowników — każdy słownik to jeden wiersz, a pandas automatycznie scala wszystkie klucze w komplet kolumn, wstawiając NaN tam, gdzie dany klucz w rekordzie nie wystąpił.
Z tablicy NumPy (2D) tworzymy DataFrame, podając dane oraz osobno columns i ewentualnie index. To popularne w obliczeniach naukowych, ale uwaga: tablica NumPy ma jeden wspólny dtype, więc liczby i tekst zmieszane w jednej tablicy staną się typem object. Lista krotek działa jak lista wierszy bez nazw — trzeba samodzielnie podać columns, inaczej pandas użyje domyślnych etykiet liczbowych.
Najczęstsze pułapki: (1) niezgodne długości list w słowniku kolumn rzucają błąd; (2) lista słowników z brakującymi kluczami daje NaN — to bywa pożądane, ale czasem maskuje błąd w danych; (3) kolejność kolumn z listy słowników w nowszych wersjach pandas jest zachowana zgodnie z pierwszym wystąpieniem klucza; (4) przy tablicy NumPy łatwo zgubić informację o typach. Dla bardzo wielu wierszy budowanych pojedynczo unikaj wielokrotnego append/konkatenacji w pętli — zbierz dane do listy i zbuduj DataFrame raz.
Przykład kodu:
import pandas as pd
import numpy as np
# 1) Ze słownika kolumn — klucze to nazwy kolumn (najbardziej naturalne)
df_slownik = pd.DataFrame({
"imie": ["Anna", "Bartek", "Celina"],
"wiek": [30, 25, 41],
"miasto": ["Kraków", "Gdańsk", "Wrocław"],
})
# 2) Z listy słowników — każdy słownik to jeden wiersz
# Brakujące klucze stają się NaN
rekordy = [
{"imie": "Anna", "wiek": 30},
{"imie": "Bartek", "wiek": 25, "miasto": "Gdańsk"},
]
df_rekordy = pd.DataFrame(rekordy)
# 3) Z tablicy NumPy 2D — trzeba podać nazwy kolumn osobno
tablica = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]])
df_numpy = pd.DataFrame(tablica, columns=["x", "y"])
# 4) Z listy krotek — wiersze bez nazw, kolumny podajemy ręcznie
krotki = [("Anna", 30), ("Bartek", 25), ("Celina", 41)]
df_krotki = pd.DataFrame(krotki, columns=["imie", "wiek"])
print(df_slownik)
print(df_rekordy)
Materiały
↑ Powrót na góręIndeksowanie i selekcja danych
Jaka jest różnica między loc a iloc?
Odpowiedź w 30 sekund:
loc wybiera dane po etykietach (nazwach indeksu i kolumn), natomiast iloc po pozycjach całkowitoliczbowych (0, 1, 2, ...). Kluczowa różnica praktyczna: wycinki w loc są domknięte na obu końcach (etykieta końcowa jest dołączana), a w iloc działają jak zwykłe slice'y Pythona — koniec jest wyłączony.
Odpowiedź w 2 minuty:
loc i iloc to dwa podstawowe, jawne sposoby wyboru danych w pandas, które zastąpiły niejednoznaczne indeksowanie nawiasami df[...]. loc[wiersze, kolumny] operuje na etykietach — odwołujesz się do wartości indeksu i nazw kolumn. iloc[wiersze, kolumny] operuje na pozycjach — odwołujesz się do liczb porządkowych niezależnie od tego, jakie są rzeczywiste etykiety. Oba akcesory przyjmują pojedyncze wartości, listy, wycinki oraz maski boolowskie.
Najczęstsza pułapka to zachowanie wycinków. df.loc['a':'c'] zwróci wiersze o etykietach a, b oraz c — koniec jest włącznie. Natomiast df.iloc[0:3] zwróci wiersze na pozycjach 0, 1, 2 — koniec wyłącznie, dokładnie jak list[0:3]. To źródło wielu błędów off-by-one, gdy ktoś miesza oba style.
Druga ważna rzecz: jeśli indeks DataFrame to liczby całkowite (np. RangeIndex lub indeks po reset_index), loc[0:3] i iloc[0:3] mogą dać różne wyniki, bo loc potraktuje liczby jako etykiety (domknięte), a iloc jako pozycje. Gdy indeks jest niemonotoniczny albo nie zawiera danej etykiety, loc rzuci KeyError. Z kolei iloc rzuci IndexError przy pozycji poza zakresem. Dobrą praktyką jest świadomy wybór: gdy zależy ci na nazwach/etykietach (czytelność, odporność na zmianę kolejności) — używaj loc; gdy operujesz pozycyjnie (np. „pierwsze 5 wierszy", iteracja pozycyjna) — iloc.
Przykład kodu:
import pandas as pd
df = pd.DataFrame(
{"imie": ["Ala", "Bartek", "Celina", "Darek"],
"wiek": [23, 31, 45, 28]},
index=["a", "b", "c", "d"],
)
# loc -> etykiety; wycinek DOMKNIĘTY (włącznie z 'c')
print(df.loc["a":"c", "imie"])
# a Ala
# b Bartek
# c Celina
# iloc -> pozycje; wycinek WYŁĄCZA koniec (pozycje 0,1,2)
print(df.iloc[0:3, 0])
# a Ala
# b Bartek
# c Celina
# Wybór konkretnej komórki
print(df.loc["b", "wiek"]) # 31 (etykieta wiersza, etykieta kolumny)
print(df.iloc[1, 1]) # 31 (pozycja wiersza, pozycja kolumny)
# Lista etykiet vs lista pozycji
print(df.loc[["a", "d"], ["wiek"]]) # wiersze a i d
print(df.iloc[[0, 3], [1]]) # te same wiersze, ale po pozycji
# loc przyjmuje też maskę boolowską (iloc nie przyjmuje serii boolowskiej z etykietami)
print(df.loc[df["wiek"] > 30])
Diagram:
flowchart TD
A["Chcę wybrać dane z DataFrame"] --> B{"Odwołuję się przez etykietę czy pozycję?"}
B -->|"Etykieta (nazwa indeksu / kolumny)"| C["df.loc[...]"]
B -->|"Pozycja (liczba 0,1,2...)"| D["df.iloc[...]"]
C --> E["Wycinek DOMKNIĘTY<br/>koniec włącznie<br/>brak etykiety -> KeyError"]
D --> F["Wycinek jak w Pythonie<br/>koniec wyłączony<br/>pozycja poza zakresem -> IndexError"]
Materiały
- pandas — Indexing and selecting data (Selection by label / by position)
- pandas — DataFrame.loc
- pandas — DataFrame.iloc
Transformacja danych
Czym różnią się apply, map i applymap i kiedy użyłbyś której z tych metod?
Odpowiedź w 30 sekund:
map działa na pojedynczej Series element-po-elemencie, applymap (w nowszych pandas zastąpione przez DataFrame.map) działa element-po-elemencie na całym DataFrame, a apply działa wzdłuż osi (po wierszach lub kolumnach) i może przyjmować/zwracać całe wektory. Wybór zależy od obiektu (Series vs DataFrame) i granularności (element vs wiersz/kolumna).
Odpowiedź w 2 minuty:
Series.map służy do mapowania wartości pojedynczej kolumny — przyjmuje funkcję, słownik lub inną Series. Najczęściej używa się go do podmiany wartości przez słownik (np. kody na nazwy) albo do prostej transformacji każdego elementu. Jest to metoda dostępna wyłącznie na Series.
DataFrame.applymap (przestarzała od pandas 2.1, zastąpiona przez DataFrame.map) stosuje funkcję skalarną do każdego pojedynczego elementu całego DataFrame. Używa się jej, gdy chcemy tę samą transformację elementową na wszystkich komórkach — np. formatowanie liczb do stringa czy zaokrąglanie. Jeśli pracujesz na nowszych pandas, używaj DataFrame.map; applymap nadal działa, ale emituje FutureWarning.
apply jest najbardziej elastyczna: na Series działa element-po-elemencie (podobnie do map), a na DataFrame domyślnie przekazuje całe kolumny (axis=0) lub całe wiersze (axis=1) jako Series do funkcji. Dzięki temu funkcja ma dostęp do wielu wartości naraz — możesz np. liczyć kombinacje kolumn w jednym wierszu albo agregować kolumnę. To kosztuje wydajnościowo, bo apply po wierszach jest w praktyce pętlą Pythona.
Praktyczna zasada: do podmiany wartości w jednej kolumnie używaj map; do tej samej transformacji elementowej na całej ramce używaj DataFrame.map; gdy potrzebujesz logiki obejmującej wiele kolumn w wierszu lub agregacji wzdłuż osi, sięgaj po apply. Zawsze jednak najpierw sprawdź, czy nie da się tego zrobić wektorowo — zwykle się da i jest szybciej.
Przykład kodu:
import pandas as pd
df = pd.DataFrame({
"imie": ["Anna", "Bartek", "Cezary"],
"wiek": [28, 34, 41],
"miasto_kod": ["WAW", "KRK", "WAW"],
})
# map na Series — podmiana wartości przez słownik
kody = {"WAW": "Warszawa", "KRK": "Kraków"}
df["miasto"] = df["miasto_kod"].map(kody)
# DataFrame.map — ta sama transformacja elementowa na wybranych kolumnach
# (odpowiednik dawnego applymap)
df[["imie"]] = df[["imie"]].map(str.upper)
# apply z axis=1 — logika łącząca wiele kolumn w jednym wierszu
df["opis"] = df.apply(lambda w: f"{w['imie']} ({w['wiek']} lat)", axis=1)
# apply z axis=0 — agregacja po kolumnie liczbowej
print(df[["wiek"]].apply(lambda kol: kol.max() - kol.min()))
| Metoda | Obiekt docelowy | Granularność | Typowe użycie |
|---|---|---|---|
map |
Series |
element-po-elemencie | podmiana wartości przez słownik/funkcję na jednej kolumnie |
applymap / DataFrame.map |
DataFrame |
element-po-elemencie | ta sama transformacja skalarna na wszystkich komórkach |
apply (axis=0/1) |
Series lub DataFrame |
element lub cały wiersz/kolumna | logika wielokolumnowa, agregacje wzdłuż osi |
Materiały
↑ Powrót na gór꣹czenie i scalanie danych
Jaka jest różnica między merge, join i concat?
Odpowiedź w 30 sekund:
merge to odpowiednik SQL-owego JOIN-a — łączy dwie ramki po wartościach we wspólnych kolumnach kluczowych (lub po indeksie). join to wygodna metoda DataFrame, która domyślnie łączy po indeksie i jest cienką nakładką na merge. concat w ogóle nie dopasowuje po kluczu — po prostu skleja (konkatenuje) ramki wzdłuż jednej osi: osi 0 (dokładanie wierszy) lub osi 1 (dokładanie kolumn).
Odpowiedź w 2 minuty:
Najważniejsza intuicja: merge i join odpowiadają na pytanie „dopasuj wiersze z jednej tabeli do pasujących wierszy z drugiej po kluczu", natomiast concat odpowiada na pytanie „doklej te ramki do siebie wzdłuż wybranej osi". To dwa różne zadania i mylenie ich jest jednym z najczęstszych błędów początkujących.
pd.merge(left, right, on='klucz') to relacyjne złączenie po kolumnach — można podać on, left_on/right_on (gdy klucze mają różne nazwy), left_index/right_index (gdy klucz jest w indeksie) oraz how (typ złączenia). Jest to funkcja najbardziej elastyczna i to ona realizuje całą logikę dopasowywania.
df.join(other) to metoda obiektu DataFrame, która pod spodem wywołuje merge, ale ma inne domyślne ustawienia: domyślnie łączy other po jego indeksie z indeksem df i domyślnie używa how='left' (zachowuje wszystkie wiersze lewej ramki). To skrót na typową sytuację „dołóż kolumny z innej ramki dopasowane po indeksie".
pd.concat([df1, df2]) skleja listę ramek. Z axis=0 (domyślnie) układa je jedna pod drugą — dokłada wiersze, wyrównując kolumny po nazwach; brakujące kolumny zostaną wypełnione NaN. Z axis=1 ustawia je obok siebie — dokłada kolumny, wyrównując wiersze po indeksie. concat przyjmuje dowolnie wiele ramek naraz (a nie tylko dwie) i jest właściwym narzędziem do scalania wielu plików o tej samej strukturze.
Przykład kodu:
import pandas as pd
klienci = pd.DataFrame({"id": [1, 2, 3], "imie": ["Ala", "Bartek", "Cela"]})
zamowienia = pd.DataFrame({"id": [1, 1, 2], "kwota": [100, 50, 200]})
# merge: złączenie po wspólnej kolumnie kluczowej "id" (jak SQL JOIN)
m = pd.merge(klienci, zamowienia, on="id")
# wynik: każdy klient sparowany ze swoimi zamówieniami
# join: domyślnie łączy po INDEKSIE, domyślnie how="left"
lewa = klienci.set_index("id")
prawa = zamowienia.set_index("id")
j = lewa.join(prawa) # dołącza kolumny "prawa" po indeksie
# concat axis=0: doklejamy wiersze (np. dane ze stycznia + lutego)
styczen = pd.DataFrame({"id": [1], "kwota": [100]})
luty = pd.DataFrame({"id": [2], "kwota": [200]})
razem = pd.concat([styczen, luty], ignore_index=True) # jedna pod drugą
# concat axis=1: doklejamy kolumny obok siebie (wyrównanie po indeksie)
obok = pd.concat([lewa, prawa], axis=1)
Diagram:
flowchart TD
subgraph CONCAT["concat — sklejanie wzdłuż osi"]
direction LR
A1["axis=0 (dokłada wiersze)"]
A2["axis=1 (dokłada kolumny)"]
end
subgraph MERGE["merge / join — dopasowanie po kluczu"]
direction LR
B1["merge: po kolumnach (jak SQL JOIN)"]
B2["join: domyślnie po indeksie, how=left"]
end
START["Dwie lub więcej ramek"] --> Q{"Czy dopasowujesz po kluczu?"}
Q -->|"Nie, tylko sklej"| CONCAT
Q -->|"Tak, po wartościach klucza"| MERGE
Materiały
- pandas — Merge, join, concatenate and compare
- pandas.merge — dokumentacja API
- pandas.concat — dokumentacja API
Szeregi czasowe
Jak pandas reprezentuje daty i czas (Timestamp, DatetimeIndex, Period, Timedelta)?
Odpowiedź w 30 sekund:
Pandas ma cztery podstawowe typy do pracy z czasem: Timestamp to pojedynczy punkt w czasie (odpowiednik datetime), Period to przedział czasu (np. cały miesiąc „2024-01"), a Timedelta to różnica/odstęp między dwoma punktami. DatetimeIndex to indeks zbudowany z wielu obiektów Timestamp, który zamienia zwykły DataFrame w szereg czasowy i odblokowuje operacje takie jak resample czy częściowe indeksowanie po datach.
Odpowiedź w 2 minuty:
Timestamp to fundament — reprezentuje jeden konkretny moment z dokładnością nawet do nanosekund. Jest to wzbogacona wersja datetime.datetime ze standardowej biblioteki Pythona, ale przechowywana wewnętrznie jako liczba nanosekund od epoki (1970-01-01), co czyni operacje wektorowymi i bardzo szybkimi. Z punktów tego typu zbudowany jest DatetimeIndex — gdy ustawimy go jako indeks DataFrame, otrzymujemy „prawdziwy" szereg czasowy z wygodnym wycinaniem (df['2024-01'] zwraca cały styczeń) i metodami czasowymi.
Period różni się tym, że reprezentuje rozpiętość czasu, a nie pojedynczy punkt. Period('2024-01', freq='M') oznacza „cały styczeń 2024", a nie konkretną sekundę. To rozróżnienie ma znaczenie semantyczne: gdy mówimy „sprzedaż w styczniu", chodzi o okres, a nie o moment. Z okresów buduje się PeriodIndex. Pomiędzy Timestamp a Period można konwertować metodami .to_period() i .to_timestamp().
Timedelta to czas trwania lub różnica — wynik odjęcia jednego Timestamp od drugiego (np. Timedelta('2 days 6 hours')). Służy do arytmetyki na datach: dodanie Timedelta(days=7) do daty przesuwa ją o tydzień. Warto odróżnić go od DateOffset (np. pd.offsets.MonthEnd()), który respektuje kalendarz (zmienna długość miesięcy), podczas gdy Timedelta operuje na stałych jednostkach (dzień = zawsze 24h). Typowa pułapka: dodanie Timedelta(days=30) to nie to samo co „przesunięcie o miesiąc".
Przykład kodu:
import pandas as pd
# Timestamp — pojedynczy punkt w czasie
ts = pd.Timestamp('2024-01-15 14:30:00')
print(ts.year, ts.month, ts.day_name()) # 2024 1 Monday
# Period — okres (cały miesiąc)
okres = pd.Period('2024-01', freq='M')
print(okres.start_time, okres.end_time) # początek i koniec stycznia
# Timedelta — różnica między datami
delta = pd.Timestamp('2024-01-20') - pd.Timestamp('2024-01-15')
print(delta) # 5 days 00:00:00
print(delta.days) # 5
# DatetimeIndex — indeks czasowy w DataFrame
idx = pd.date_range('2024-01-01', periods=5, freq='D')
df = pd.DataFrame({'sprzedaz': [10, 20, 15, 30, 25]}, index=idx)
print(df.loc['2024-01-02':'2024-01-04']) # wycinanie po datach
# Konwersje między typami
print(ts.to_period('M')) # Timestamp -> Period (2024-01)
print(okres.to_timestamp()) # Period -> Timestamp (początek okresu)
Diagram:
flowchart TD
A["Reprezentacja czasu w pandas"] --> B["Timestamp<br/>(punkt w czasie)"]
A --> C["Period<br/>(okres / rozpiętość)"]
A --> D["Timedelta<br/>(różnica / czas trwania)"]
B -->|"wiele punktów"| E["DatetimeIndex<br/>(indeks szeregu)"]
C -->|"wiele okresów"| F["PeriodIndex"]
B -.->|".to_period()"| C
C -.->|".to_timestamp()"| B
B -.->|"odejmowanie dat"| D
Materiały
- Time series / date functionality — dokumentacja pandas
- pandas.Timestamp — API reference
- pandas.Timedelta — API reference