Blog Analityczny. Narzędzia. Techniki. Rozwiązania Analityczne.

Szwajcarski scyzoryk do łączenia danych w pandas, czyli rzecz o pd.concat(). Poradnik.

| Pandas | Concat | Łączenie danych | Przeczytasz w 10 min.

W pracy z danymi często spotykamy się z sytuacją, gdy informacje pochodzą z różnych źródeł - aplikacji mobilnej, sklepu stacjonarnego, strony internetowej czy systemu magazynowego. Aby móc je analizować, musimy połączyć je w jedną całość. Do tego właśnie służy pd.concat(), funkcja z biblioteki pandas, która umożliwia szybkie i elastyczne łączenie zbiorów danych. Dzięki niej łatwiej zbudować spójny zestaw informacji i przygotować dane do dalszej analizy.

Spis zagadnień

W niniejszym artykule poruszamy następujące zagadnienia:

  • Wprowadzenie - ogólny opis funkcji pd.concat() i jej zastosowania
  • Czym jest pd.concat()? - definicja i podstawowa składnia funkcji
  • Podstawowe zastosowania:
    • Łączenie w pionie (axis=0)
    • Łączenie w poziomie (axis=1)
  • Zaawansowane możliwości:
    • Kontrola nad sposobem łączenia (join parameter)
    • Etykietowanie źródeł danych
    • Obsługa indeksów
  • Przykłady biznesowe z e-commerce:
    • Analiza sprzedaży wielokanałowej
    • Budowanie pełnego profilu klienta
    • Agregacja danych czasowych z różnych systemów
  • Najlepsze praktyki:
    • Przemyślane używanie ignore_index
    • Kontrola nad typami danych po concat
    • Używanie keys dla śledzenia źródeł
  • Częste pułapki i jak ich uniknąć:
    • Problem duplikowania kolumn
    • Problem z niespójnymi typami danych
    • Wydajność w praktyce
  • Praktyczne wskazówki
  • FAQ - Najczęściej zadawane pytania

Czym jest pd.concat()?

pd.concat() to funkcja pandas służąca do łączenia obiektów pandas - DataFrame'ów i Series - wzdłuż określonej osi. W przeciwieństwie do zwykłego "sklejania", concat zapewnia precyzyjną kontrolę nad sposobem łączenia danych.

import pandas as pd

# Podstawowa składnia
pd.concat(objs, axis=0, join='outer', ignore_index=False, keys=None, sort=False)

Podstawowe zastosowania

Łączenie w pionie (axis=0)

# Przykładowe dane sprzedażowe z różnych kwartałów
q1_sales = pd.DataFrame({
    'product': ['iPhone', 'Samsung', 'Pixel'],
    'revenue': [1000, 800, 600],
    'quarter': ['Q1', 'Q1', 'Q1']
})

q2_sales = pd.DataFrame({
    'product': ['iPhone', 'Samsung', 'Pixel'],
    'revenue': [1200, 900, 700],
    'quarter': ['Q2', 'Q2', 'Q2']
})

# Łączenie kwartalnych raportów
yearly_sales = pd.concat([q1_sales, q2_sales], ignore_index=True)

Łączenie w pionie to sytuacja, gdy dokładamy nowe wiersze do istniejących danych. Wyobraźmy sobie, że mamy raporty sprzedażowe z każdego miesiąca i chcemy stworzyć raport roczny. Każdy miesięczny DataFrame ma te same kolumny, różnią się tylko danymi.

Łączenie w poziomie (axis=1)

# Dane podstawowe o produktach
products = pd.DataFrame({
    'product_id': [1, 2, 3],
    'name': ['iPhone 14', 'Samsung S23', 'Pixel 7']
})

# Dane cenowe z innego systemu
prices = pd.DataFrame({
    'product_id': [1, 2, 3],
    'price': [999, 899, 599]
})

# Łączenie w poziomie - tworzenie kompletnego katalogu
product_catalog = pd.concat([products, prices[['price']]], axis=1)

Łączenie w poziomie oznacza dodawanie nowych kolumn. Często spotykamy to, gdy różne systemy przechowują różne atrybuty tych samych obiektów - system produktowy ma nazwy i opisy, system cenowy ma ceny i promocje, system magazynowy ma stany zapasów.

Zaawansowane możliwości

Kontrola nad sposobem łączenia (join parameter)

# DataFrame z różnymi kolumnami
online_sales = pd.DataFrame({
    'product': ['iPhone', 'Samsung'],
    'online_revenue': [500, 300],
    'clicks': [1000, 800]
})

store_sales = pd.DataFrame({
    'product': ['iPhone', 'Samsung'],
    'store_revenue': [600, 400],
    'foot_traffic': [200, 150]
})

# Outer join - wszystkie kolumny (domyślne zachowanie)
all_data = pd.concat([online_sales, store_sales], axis=1, join='outer')

# Inner join - tylko wspólne kolumny (w tym przypadku 'product')
common_data = pd.concat([online_sales, store_sales], axis=1, join='inner')

Parameter join określa, jak pandas ma postąpić z kolumnami, które nie występują we wszystkich łączonych DataFrame'ach. Outer join zachowuje wszystkie kolumny, wypełniając brakujące wartości NaN. Inner join zachowuje tylko te kolumny, które występują we wszystkich DataFrame'ach.

Etykietowanie źródeł danych

# Dane z różnych kanałów sprzedaży
website_orders = pd.DataFrame({'amount': [100, 200, 150]})
mobile_orders = pd.DataFrame({'amount': [80, 120, 90]})
store_orders = pd.DataFrame({'amount': [300, 250, 400]})

# Łączenie z etykietami źródeł
all_orders = pd.concat(
    [website_orders, mobile_orders, store_orders],
    keys=['website', 'mobile', 'store'],
    names=['channel', 'order_id']
)

# Teraz można łatwo filtrować według kanału
website_only = all_orders.loc['website']

Parameter keys pozwala nam zachować informację o tym, z którego DataFrame'a pochodzą poszczególne wiersze. To szczególnie przydatne, gdy łączymy dane z różnych źródeł i chcemy móc później analizować je osobno lub porównywać między sobą.

Obsługa indeksów

# DataFrame'y z potencjalnie konfliktowymi indeksami
df1 = pd.DataFrame({'sales': [1000, 2000]}, index=[0, 1])
df2 = pd.DataFrame({'sales': [1500, 2500]}, index=[0, 1])

# Zachowanie oryginalnych indeksów (mogą powstać duplikaty)
with_original_index = pd.concat([df1, df2])

# Zresetowanie indeksów - nowe, unikalne wartości
clean_index = pd.concat([df1, df2], ignore_index=True)

# Weryfikacja unikalności indeksów
try:
    verified = pd.concat([df1, df2], verify_integrity=True)
except ValueError as e:
    print(f"Wykryto duplikaty indeksów: {e}")

Indeksy to często pomijany aspekt łączenia danych. ignore_index=True tworzy nowy, sekwencyjny indeks, co jest przydatne, gdy oryginalny indeks nie ma znaczenia biznesowego. verify_integrity=True pozwala wyłapać potencjalne problemy z duplikatami indeksów.

Przykłady biznesowe z e-commerce

Analiza sprzedaży wielokanałowej

# Różne kanały sprzedaży z różnymi metrykami
channels_data = {
    'online': pd.DataFrame({
        'product_category': ['Electronics', 'Clothing', 'Books'],
        'revenue': [50000, 30000, 15000],
        'sessions': [10000, 8000, 5000],
        'conversion_rate': [0.05, 0.04, 0.03]
    }),

    'mobile': pd.DataFrame({
        'product_category': ['Electronics', 'Clothing', 'Home'],
        'revenue': [35000, 25000, 20000],
        'app_downloads': [5000, 3000, 2000],
        'push_notifications_sent': [15000, 12000, 8000]
    }),

    'retail': pd.DataFrame({
        'product_category': ['Electronics', 'Clothing'],
        'revenue': [80000, 45000],
        'foot_traffic': [2000, 1500],
        'avg_basket_size': [400, 300]
    })
}

# Łączenie wszystkich kanałów z zachowaniem informacji o źródle
multichannel_analysis = pd.concat(
    channels_data.values(),
    keys=channels_data.keys(),
    names=['channel', 'category_id']
)

# Analiza całkowitych przychodów per kategoria
total_revenue_by_category = multichannel_analysis.groupby('product_category')['revenue'].sum()

Budowanie pełnego profilu klienta

# Różne aspekty danych klienta z różnych systemów
customer_id = [1001, 1002, 1003]

# Dane demograficzne z CRM
demographics = pd.DataFrame({
    'customer_id': customer_id,
    'age': [28, 35, 42],
    'city': ['Warsaw', 'Krakow', 'Gdansk']
})

# Historia transakcji z systemu płatności
transactions = pd.DataFrame({
    'customer_id': customer_id,
    'total_spent': [1200, 2500, 800],
    'orders_count': [5, 12, 3],
    'avg_order_value': [240, 208, 267]
})

# Dane behawioralne z analityki web
web_behavior = pd.DataFrame({
    'customer_id': customer_id,
    'page_views': [150, 300, 80],
    'time_on_site': [25, 45, 15],
    'newsletter_subscriber': [True, True, False]
})

# Tworzenie pełnego profilu klienta
# Ustawiamy customer_id jako indeks dla precyzyjnego łączenia
demographics_indexed = demographics.set_index('customer_id')
transactions_indexed = transactions.set_index('customer_id')
web_behavior_indexed = web_behavior.set_index('customer_id')

# Łączenie wszystkich aspektów
complete_customer_profile = pd.concat([
    demographics_indexed,
    transactions_indexed,
    web_behavior_indexed
], axis=1)

Agregacja danych czasowych z różnych systemów

# Dane dzienne z różnych systemów monitoringu
dates = pd.date_range('2024-01-01', periods=30, freq='D')

# Metryki ze sklepu online
online_metrics = pd.DataFrame({
    'date': dates,
    'online_revenue': [1000 + i*50 for i in range(30)],
    'website_visitors': [500 + i*25 for i in range(30)]
})

# Metryki z aplikacji mobilnej
mobile_metrics = pd.DataFrame({
    'date': dates,
    'mobile_revenue': [800 + i*30 for i in range(30)],
    'app_sessions': [300 + i*15 for i in range(30)]
})

# Metryki ze sklepów stacjonarnych (tylko pierwsze 20 dni)
retail_metrics = pd.DataFrame({
    'date': dates[:20],  # Tylko 20 dni danych
    'retail_revenue': [2000 + i*100 for i in range(20)],
    'foot_traffic': [100 + i*5 for i in range(20)]
})

# Ustawienie daty jako indeks
online_indexed = online_metrics.set_index('date')
mobile_indexed = mobile_metrics.set_index('date')
retail_indexed = retail_metrics.set_index('date')

# Łączenie z outer join - zachowamy wszystkie daty
daily_business_metrics = pd.concat([
    online_indexed,
    mobile_indexed,
    retail_indexed
], axis=1, join='outer')

# Wypełnienie brakujących wartości zerami tam gdzie to ma sens
daily_business_metrics = daily_business_metrics.fillna(0)

Najlepsze praktyki

Przemyślane używanie ignore_index

# Gdy łączymy dane czasowe - zachowujemy indeks dat
time_series_concat = pd.concat([jan_sales, feb_sales, mar_sales])

# Gdy łączymy listy transakcji - tworzymy nowy indeks
transactions_concat = pd.concat([morning_orders, afternoon_orders], ignore_index=True)

ignore_index=True używamy, gdy oryginalny indeks nie niesie wartościowej informacji i chcemy po prostu uporządkowaną sekwencję numerów wierszy. Zachowujemy oryginalny indeks, gdy ma znaczenie biznesowe - jak daty w szeregach czasowych czy ID produktów.

Kontrola nad typami danych po concat

# Sprawdzenie typów danych po łączeniu
result = pd.concat([df1, df2])
print("Typy danych po concat:")
print(result.dtypes)

# Wymuszenie właściwych typów jeśli potrzeba
result['order_id'] = result['order_id'].astype('int64')
result['order_date'] = pd.to_datetime(result['order_date'])

concat może czasem zmienić typy danych, szczególnie gdy łączymy DataFrame'y z różnymi typami w tych samych kolumnach. Warto sprawdzać i korygować typy po operacji łączenia.

Używanie keys dla śledzenia źródeł

# Zamiast tracić informację o pochodzeniu danych
simple_concat = pd.concat([online_data, offline_data], ignore_index=True)

# Lepiej zachować źródło - umożliwia późniejsze analizy per kanał
tracked_concat = pd.concat([online_data, offline_data],
                          keys=['online', 'offline'],
                          names=['channel', 'transaction_id'])

Częste pułapki i jak ich uniknąć

Problem duplikowania kolumn

# Niebezpieczne - może prowadzić do duplikacji kolumn
df1 = pd.DataFrame({'product_id': [1, 2], 'price': [100, 200]})
df2 = pd.DataFrame({'product_id': [1, 2], 'discount': [0.1, 0.15]})

# Złe podejście - kolumna product_id zostanie zduplikowana
bad_result = pd.concat([df1, df2], axis=1)

# Dobre podejście - usunięcie duplikującej się kolumny
df2_clean = df2.drop('product_id', axis=1)
good_result = pd.concat([df1, df2_clean], axis=1)

# Jeszcze lepsze podejście - łączenie przez indeks
df1_indexed = df1.set_index('product_id')
df2_indexed = df2.set_index('product_id')
best_result = pd.concat([df1_indexed, df2_indexed], axis=1)

Problem z niespójnymi typami danych

# DataFrame z różnymi typami w tej samej kolumnie
df1 = pd.DataFrame({'amount': [100, 200]})  # int
df2 = pd.DataFrame({'amount': [150.5, 250.7]})  # float

# Po concat typ będzie float (pandas wybierze bardziej ogólny typ)
result = pd.concat([df1, df2])
print(result.dtypes)  # amount będzie float64

# Jeśli potrzebujemy określonego typu, konwertujemy po concat
result['amount'] = result['amount'].astype('int64')  # jeśli nie ma ułamków

Wydajność w praktyce

import time
import pandas as pd

# Generowanie testowych danych
test_dfs = [pd.DataFrame({'values': range(1000)}) for _ in range(50)]

# Test wydajności concat
start_time = time.time()
result = pd.concat(test_dfs, ignore_index=True)
concat_time = time.time() - start_time

print(f"Łączenie 50 DataFrame'ów po 1000 wierszy: {concat_time:.4f} sekund")
print(f"Wynikowy DataFrame ma {len(result)} wierszy")

pd.concat() jest zoptymalizowany pod kątem wydajności, szczególnie gdy łączymy wiele DataFrame'ów jednocześnie. To znacznie szybsze niż iteracyjne dodawanie DataFrame'ów do siebie.


Praktyczne wskazówki

pd.concat() to narzędzie, które wymaga zrozumienia, ale nagradza elastycznością. Kluczem jest świadome wybieranie parametrów w zależności od celu analizy. Gdy łączymy dane z różnych źródeł, zawsze warto zachować informację o pochodzeniu używając parametru keys. Gdy pracujemy z danymi czasowymi, zachowujemy oryginalny indeks. Gdy budujemy zbiory treningowe do machine learning, często używamy ignore_index=True dla prostoty.

To fundament efektywnej pracy z danymi w pandas - narzędzie, które transformuje chaos rozproszonej informacji w uporządkowaną bazę do analiz. Opanowanie pd.concat() to inwestycja zwracająca się przy każdym kolejnym projekcie analitycznym.


FAQ - Najczęściej zadawane pytania

Jaka jest różnica między pd.concat() a DataFrame.append()?

DataFrame.append() został usunięty w pandas 2.0. pd.concat() to jego następca - szybszy, bardziej elastyczny i oferujący lepszą kontrolę nad procesem łączenia. Append() był często mylący, ponieważ nie modyfikował oryginalnego DataFrame'a.

Kiedy używać axis=0 a kiedy axis=1?

axis=0 używamy do łączenia w pionie (dodawanie wierszy), axis=1 do łączenia w poziomie (dodawanie kolumn). Jeśli masz dane sprzedażowe z różnych miesięcy - użyj axis=0. Jeśli masz różne atrybuty tych samych produktów - użyj axis=1.

Co oznacza ignore_index=True?

ignore_index=True tworzy nowy, sekwencyjny indeks od 0 do n-1. Używaj tego parametru, gdy oryginalny indeks nie ma znaczenia biznesowego. Nie używaj go dla danych czasowych czy gdy indeks identyfikuje konkretne obiekty.

Dlaczego otrzymuję duplikaty kolumn przy łączeniu w poziomie?

To częsty problem przy axis=1. Dzieje się tak, gdy łączone DataFrame'y mają wspólne nazwy kolumn. Rozwiązanie: usuń duplikujące się kolumny przed concat lub ustaw wspólne kolumny jako indeks.

Jak łączyć DataFrame'y z różnymi kolumnami?

Domyślnie pd.concat() używa outer join - zachowuje wszystkie kolumny i wypełnia brakujące wartości NaN. Możesz użyć join='inner' aby zachować tylko wspólne kolumny.

Co to znaczy keys parameter i kiedy go używać?

keys pozwala etykietować źródło danych w wynikowym DataFrame'ie. Używaj tego parametru zawsze, gdy łączysz dane z różnych źródeł i chcesz móc później je rozróżnić - np. dane z różnych kanałów sprzedaży.

Jak obsługiwać różne typy danych w tej samej kolumnie?

pandas automatycznie wybierze najbardziej ogólny typ. Sprawdzaj dtypes po concat i konwertuj typy jeśli potrzeba. Uważaj szczególnie na kolumny z liczbami całkowitymi i NaN.

Czy pd.concat() jest wydajny dla dużych zbiorów danych?

Tak, pd.concat() jest zoptymalizowany pod kątem wydajności. Jest znacznie szybszy od iteracyjnego dodawania DataFrame'ów. Dla bardzo dużych zbiorów rozważ użycie chunking lub Dask.

Jak łączyć dane gdy mam różne indeksy?

Masz kilka opcji: ustaw wspólną kolumnę jako indeks przed concat, użyj ignore_index=True dla nowego indeksu, lub zostaw różne indeksy jeśli to ma sens biznesowy.

Co zrobić gdy concat zwraca błąd o duplikatach indeksów?

Użyj verify_integrity=False (domyślne) lub ignore_index=True. Błąd występuje tylko gdy ustawisz verify_integrity=True i masz rzeczywiście duplikujące się indeksy.


Podsumowanie

  • Funkcja pd.concat() to wszechstronne narzędzie do łączenia zbiorów danych w pandas
  • Podstawowe metody łączenia to stack (axis=0) dodający wiersze oraz join (axis=1) dodający kolumny
  • Kluczowe parametry to axis, ignore_index, keys i join wpływające na sposób integracji danych
  • Zachowanie źródeł danych jest możliwe dzięki parametrowi keys, co pomaga w późniejszych analizach
  • Typowe pułapki to duplikujące się kolumny i niespójne typy danych, które wymagają uwagi
  • Prawidłowe użycie pd.concat() wymaga zrozumienia struktury danych i świadomego wyboru parametrów
  • Funkcja jest wydajna i zoptymalizowana nawet dla dużych zbiorów danych

Darmowe narzędzia analityczne

Kalkulatory i generatory dla marketerów i analityków

Encyklopedia GA4 Wszystkie zdarzenia GA4
Audytor GA4 Sprawdź konfigurację
Symulator Atrybucji Porównaj modele atrybucji
Kalkulator BigQuery Oszacuj koszty GA4 + BQ
Kreator Linków UTM Taguj kampanie GA4/Piwik
Generator dataLayer Ecommerce, formularze, eventy
Kalkulator ROAS/ROI Rentowność kampanii
Kalkulator LTV Wartość życiowa klienta
Zobacz wszystkie narzędzia →

Przeczytaj również

Najnowsze artykuły z bloga

Porady i wskazówki

Szybkie tipy dla analityków

Więcej porad →