Dostałeś nowy zbiór obserwacji z rezerwatu, @name. Zanim wytresujesz na nim jakikolwiek model, musisz zrobić to, co każdy dobry tropiciel przed polowaniem: zwiedzić teren. EDA (Exploratory Data Analysis) to ten zwiad - odpowiadasz kolejno na proste pytania o dane, aż poczujesz, z czym masz do czynienia. Przejdziemy je po kolei.
Pierwszy rzut oka to zawsze rozmiar i kształt danych - ile obserwacji, ile kolumn, jak wyglądają pierwsze wiersze.
1import pandas as pd
2import numpy as np
3import matplotlib.pyplot as plt
4import seaborn as sns
5
6df = pd.read_csv('safari_observations.csv')
7
8print("Shape:", df.shape) # (liczba wierszy, liczba kolumn)
9print(df.head()) # kilka pierwszych obserwacji
10print(df.describe()) # statystyki kolumn liczbowychTe trzy linie to Twój pierwszy meldunek z terenu:
shape mówi, jak wielki jest zbiór, head pokazuje, co konkretnie zapisano w każdej kolumnie, a describe daje od razu średnie, minima i maksima. Zanim pójdziesz dalej - już wiesz, czy danych jest 100, czy milion.Model inaczej traktuje liczby, a inaczej kategorie, więc musisz rozdzielić kolumny liczbowe (populacja, waga) od kategorycznych (gatunek, siedlisko).
1categorical = df.select_dtypes(include=['object', 'category']).columns
2numerical = df.select_dtypes(include=[np.number]).columns
3
4for col in categorical:
5 print(f"{col}: {df[col].nunique()} unikalnych")
6 print(df[col].value_counts().head())select_dtypes rozdziela kolumny po typie, a value_counts pokazuje, ile jest każdej kategorii. To ważny zwiad: jeśli kolumna "gatunek" ma 3 wartości, to kategoria; jeśli 10 000 - to raczej identyfikator, którego model nie powinien traktować jak cechy.Prawdziwe dane z terenu są dziurawe - część obserwacji jest niekompletna. Musisz wiedzieć gdzie i ile, zanim braki zepsują model.
1missing = df.isnull().sum()
2missing_pct = (missing / len(df)) * 100
3missing_df = pd.DataFrame({'braki': missing, 'procent': missing_pct})
4print(missing_df[missing_df['braki'] > 0].sort_values('braki', ascending=False))
5
6sns.heatmap(df.isnull(), cbar=True, yticklabels=False)
7plt.title('Mapa brakujących wartości')
8plt.show()Tabela mówi, które kolumny są dziurawe i jak bardzo (procent jest ważniejszy niż liczba). Mapa cieplna dokłada obraz: jeśli braki układają się w pasy, może brakować całych partii obserwacji - a to już trop, że coś poszło nie tak przy zbieraniu danych, nie przypadek.
Teraz przyglądasz się pojedynczym kolumnom liczbowym: jak rozłożone są wartości i czy są wartości odstające (outliery) - obserwacje, które nie pasują do reszty.
1df[numerical].hist(figsize=(15, 10), bins=30, edgecolor='black')
2plt.tight_layout()
3plt.show()
4
5def find_outliers_iqr(df, column):
6 Q1 = df[column].quantile(0.25)
7 Q3 = df[column].quantile(0.75)
8 IQR = Q3 - Q1
9 lower, upper = Q1 - 1.5 * IQR, Q3 + 1.5 * IQR
10 return df[(df[column] < lower) | (df[column] > upper)]
11
12outliers = find_outliers_iqr(df, 'population')
13print(f"Wartości odstające w populacji: {len(outliers)}")Histogramy pokazują kształt rozkładu jednym spojrzeniem. Funkcja
find_outliers_iqr używa reguły "1,5 × rozstęp kwartylny" - klasycznej metody na wskazanie obserwacji zbyt dużych lub zbyt małych. Uwaga: outlier nie zawsze znaczy błąd. Słoń w danych o wadze to naturalny "odstający" - Twoim zadaniem jest zdecydować, czy to pomyłka do usunięcia, czy prawdziwe, ważne zwierzę.Na koniec sprawdzasz, które cechy poruszają się razem - gdy jedna rośnie, druga też. To podpowiada, co pomoże w przewidywaniu, a które kolumny mówią właściwie to samo.
1correlation = df[numerical].corr()
2
3sns.heatmap(correlation, annot=True, cmap='coolwarm', center=0, fmt='.2f')
4plt.title('Macierz korelacji')
5plt.show()Mapa korelacji koloruje pary cech: wartości blisko
1 (albo -1) to silny związek, blisko 0 - żaden. Po co to wiedzieć? Dwie cechy niemal identyczne (korelacja 0,98) niosą tę samą informację - zwykle wystarczy zostawić jedną. A cecha silnie związana z tym, co przewidujesz, to Twój najlepszy kandydat do modelu.Korelacja łączy liczby z liczbami. Ale często najciekawsze pytanie brzmi: czy kategoria wpływa na liczbę - np. czy populacja różni się między siedliskami? Do tego służy wykres pudełkowy z podziałem na kategorię.
1for cat in categorical:
2 for num in numerical:
3 sns.boxplot(data=df, x=cat, y=num)
4 plt.title(f'{num} według {cat}')
5 plt.xticks(rotation=45)
6 plt.show()Każdy
boxplot porównuje rozkład zmiennej liczbowej w podziale na grupy. Jeśli pudełka dla różnych siedlisk leżą na zupełnie innych wysokościach, to znak, że siedlisko naprawdę tłumaczy populację - i że warto dać tę kategorię modelowi. Jeśli pudełka się pokrywają, kategoria niewiele wnosi.Skoro te same pytania zadajesz każdemu nowemu zbiorowi, warto spakować pierwszy meldunek w jedną funkcję, którą odpalisz od razu po wczytaniu danych.
1def eda_summary(df):
2 print("=" * 50)
3 print("RAPORT EDA")
4 print("=" * 50)
5 print(f"Wierszy: {df.shape[0]}")
6 print(f"Kolumn: {df.shape[1]}")
7 print(f"Brakujące dane: {df.isnull().sum().sum()}")
8 print(f"Duplikaty: {df.duplicated().sum()}")
9 print(df.dtypes.value_counts())
10 print(df.describe())
11
12eda_summary(df)Ta funkcja zbiera odpowiedzi na pytania 1-3 w jeden szybki raport - rozmiar, typy, braki, duplikaty - żebyś każdy nowy zbiór zaczynał od tego samego, sprawdzonego zwiadu.
Pamiętaj z tej lekcji kolejność, nie komendy: rozmiar → typy → braki → rozkłady → korelacje. Model trenujesz dopiero, gdy przejdziesz ten zwiad - inaczej polujesz na ślepo w nieznanym terenie.