Kontekst pracy dyplomowej i rola Pythona w analizie danych
Scenariusz: od surowych danych do „rozdziału z wynikami”
Typowy etap końcówki pracy dyplomowej wygląda podobnie: dane już są zebrane, ankiety wypełnione, pomiary wykonane, a promotor oczekuje przejrzystego rozdziału z wynikami. Na tym etapie wiele osób próbuje wszystko wyklikać w Excelu, ręcznie licząc średnie, tabele krzyżowe i rysując wykresy. Przy kilku zmiennych i kilkudziesięciu obserwacjach to jeszcze działa, ale przy poważniejszym badaniu szybko zaczyna brakować czasu i kontroli nad całością.
Python rozwiązuje ten problem przez możliwość zbudowania półautomatycznego raportu z analizy. Jeden skrypt lub notatnik Jupyter wczytuje dane, liczy wszystkie statystyki, generuje tabele i zapisuje wykresy. Gdy zmienisz drobną decyzję metodyczną (np. sposób traktowania braków danych, filtr wieku) – uruchamiasz całość jeszcze raz i dostajesz spójny komplet wyników.
Dlaczego Python zamiast Excela w pracy dyplomowej
Excel jest narzędziem wygodnym do szybkich podglądów, ale ma kilka słabych punktów przy poważniejszej analizie do pracy dyplomowej:
- Brak pełnej powtarzalności – trudno odtworzyć każdy krok; formuły potrafią się „rozpłynąć” przy kopiowaniu, a filtry wpływają na obliczenia w sposób niewidoczny dla recenzenta.
- Ręczne przeklikiwanie – każda nowa tabela to nowe kopiuj–wklej, nowe formatowanie, co przy większej liczbie zmiennych pochłania godziny.
- Większe ryzyko błędów – jedna pomyłka w zakresie formuły albo przesunięcie kolumny może zmienić wynik, a wykrycie tego bywa trudne.
Python wraz z bibliotekami pandas, matplotlib i seaborn pozwala opisać analizę deklaratywnie w kodzie. Każdy krok (wczytanie danych, filtrowanie, obliczenia, wykresy) jest zapisany w sposób, który można zrecenzować. Co więcej, każdy wynik w pracy (tabela, wykres) ma jedno źródło prawdy: jest efektem uruchomienia tego samego kodu na tych samych danych.
Koncepcja raportu z analizy jako skryptu lub notatnika
Raport z analizy w Pythonie można zorganizować na dwa główne sposoby:
- Skrypt Pythona (np.
analysis_report.py) – po uruchomieniu wykonuje wszystkie kroki: wczytuje dane, liczy statystyki, zapisuje tabele i wykresy do katalogówtables/ifigures/. Nadaje się świetnie, gdy analiza jest w miarę ustalona, a tekst w pracy tylko „podpina się” pod wyniki. - Notebook Jupyter – połączenie tekstu opisowego, kodu i wyników. To wygodny format dla prac dyplomowych: jedna osoba może go czytać jako „opowieść o analizie”, inna – jako precyzyjny raport techniczny, a promotor może szybko prześledzić logikę obliczeń.
W obu wariantach kluczowa jest reprodukowalność: możliwość uruchomienia całej analizy od zera na świeżo wgranego pliku z danymi i otrzymania identycznych tabel oraz wykresów, jakie trafiły do pracy.
Oczekiwania promotora i recenzenta wobec analizy w Pythonie
Przy wykorzystywaniu Pythona w pracy dyplomowej ważne są nie tylko same obliczenia, ale też sposób ich udokumentowania. Typowe wymagania to:
- Przejrzystość metod – wyraźne wyszczególnienie, jakie miary i testy statystyczne zostały użyte dla których zmiennych (np. średnia i odchylenie standardowe dla zmiennych ilościowych, tabele częstości dla kategorycznych).
- Możliwość odtworzenia – recenzent powinien mieć techniczną możliwość odtworzenia wyników, przynajmniej w teorii. Raport z analizy w Pythonie świetnie to wspiera, szczególnie w formie notatnika dołączonego jako załącznik.
- Opis kroków przygotowania danych – wskazanie, jak traktowano braki danych, jakie zastosowano filtry (np. usunięcie respondentów poniżej określonego wieku), jak zakodowano zmienne kategoryczne.
- Estetyka prezentacji wyników – tabele i wykresy muszą być czytelne, opatrzone opisami osi, legendami, numerami tabel i rysunków zgodnie z wymaganiami uczelni.

Środowisko pracy: Jupyter, struktura projektu i dane wejściowe
Wybór narzędzia: Jupyter Notebook, JupyterLab, VS Code
Do przygotowania raportu z analizy w Pythonie najbardziej praktyczne są środowiska, które obsługują notebooki:
- Jupyter Notebook – klasyczne środowisko webowe, wystarczające do większości prac. Każda komórka może zawierać kod lub tekst w Markdown.
- JupyterLab – nowsze środowisko, bardziej „IDE w przeglądarce”, z kartami, eksploratorem plików, podglądem obrazów. Dla większych projektów bywa wygodniejsze.
- VS Code + rozszerzenie Python / Jupyter – notebooki uruchamiane bezpośrednio w edytorze kodu. Dobre, gdy równolegle rozwijasz skrypty w
src/i notatnik z raportem.
Kryterium wyboru jest głównie wygoda. Dla większości osób piszących pracę dyplomową JupyterLab lub VS Code są dobrym kompromisem: dają podgląd całego projektu (katalogi, dane, wygenerowane wykresy) i jednocześnie pozwalają pisać tekst analityczny w tym samym oknie.
Struktura katalogów dla projektu dyplomowego
Porządek w projekcie bardzo ułatwia pracę, szczególnie gdy trzeba wrócić do analizy po kilku tygodniach. Praktyczna struktura katalogów może wyglądać tak:
projekt_pracy/
├── data/
│ ├── raw/
│ └── processed/
├── figures/
├── tables/
├── notebooks/
│ └── raport_analizy.ipynb
├── src/
│ ├── preprocessing.py
│ ├── stats_utils.py
│ └── plotting_utils.py
└── requirements.txt
Rola poszczególnych katalogów:
data/raw/– surowe pliki otrzymane z ankiety, systemu eksperymentalnego, SAP-a itp. Nie są modyfikowane ręcznie.data/processed/– dane po wstępnym czyszczeniu i przekształceniach podstawowych (np. usunięcie oczywistych duplikatów, poprawa kodowania).figures/– wszystkie wykresy generowane przez skrypty, numerowane i podpisane.tables/– eksportowane tabele (CSV, Excel, LaTeX, HTML) gotowe do wklejenia do pracy.notebooks/– notatniki Jupyter, w tym główny raport z analizy.src/– moduły z funkcjami pomocniczymi, które używane są w raportach i skryptach (czyszczenie, statystyka, wykresy).requirements.txt– lista używanych bibliotek i ich wersji, potrzebna do odtworzenia środowiska.
Organizacja plików z danymi i ścieżki względne
Dane często przychodzą w różnych formatach: CSV, XLSX, czasem w formie exportu z SPSS lub innego systemu. Najwygodniej jest sprowadzić całość do CSV lub XLSX i zapisać je w data/raw/. Kilka zasad organizacyjnych:
- Używaj sensownych nazw plików, np.
ankieta_studentow_2025.csv, zamiastdane_koncowe_ostatnia_wer.csv. - Pracuj na ścieżkach względnych w kodzie, np.:
data/raw/ankieta_studentow_2025.csv, zamiast pełnych ścieżek z dysku użytkownika. - Jeśli uczelnia wymaga anonimizacji, wykonaj ją jako osobny krok i trzymaj osobno wersję z danymi wrażliwymi (np. w
data/raw_private/), a w analizie używaj danych już oczyszczonych z identyfikatorów.
W Pythonie wygodnie jest używać modułu pathlib, który ułatwia pracę ze ścieżkami:
from pathlib import Path
import pandas as pd
BASE_DIR = Path(__file__).resolve().parent.parent
DATA_DIR = BASE_DIR / "data" / "raw"
df = pd.read_csv(DATA_DIR / "ankieta_studentow_2025.csv", sep=";", encoding="utf-8")
Wirtualne środowisko i plik requirements.txt
Aby raport z analizy w Pythonie był naprawdę reprodukowalny, warto spisać wszystkie używane biblioteki w jednym miejscu i wykorzystywać wirtualne środowisko. Przykładowa konfiguracja z użyciem venv:
python -m venv venv
source venv/bin/activate # Linux / macOS
# .venvScriptsactivate # Windows
pip install pandas numpy matplotlib seaborn scipy pingouin jupyter
pip freeze > requirements.txt
Plik requirements.txt można załączyć razem z kodem i danymi na płycie/pendrivie przekazywanym uczelni. Ktoś, kto będzie chciał odtworzyć analizę, może wykonać:
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
Takie podejście wzmacnia wiarygodność wyników w pracy dyplomowej i dobrze wygląda z perspektywy promotora zainteresowanego jakością warsztatu.
Import i wstępne przygotowanie danych w pandas
Wczytywanie danych: read_csv, read_excel i istotne parametry
Biblioteka pandas jest podstawą, gdy celem jest raport z analizy w Pythonie. Podstawowe funkcje importu to:
pd.read_csv()– dla plików CSV (warto ustawić odpowiedni separator, kodowanie i znak dziesiętny).pd.read_excel()– dla plików Excela (z opcją wyboru arkusza).
Fragment kodu odzwierciedlający typowy import danych ankietowych zapisanych jako CSV z separatorem średnika:
import pandas as pd
df = pd.read_csv(
"data/raw/ankieta_studentow_2025.csv",
sep=";", # separator pól
encoding="utf-8", # typowe kodowanie
decimal="," # polski separator dziesiętny
)
Dla plików Excela wygląda to analogicznie:
df = pd.read_excel(
"data/raw/ankieta_studentow_2025.xlsx",
sheet_name="Arkusz1"
)
Po imporcie dobrze jest od razu rzucić okiem na strukturę danych:
df.head()
df.info()
df.describe(include="all")
info() pokaże typy kolumn i liczbę niepustych wartości, a describe() od razu daje pierwszy szkic statystyki opisowej.
Czyszczenie kolumn: typy danych, daty i kategorie
Surowe dane rzadko przychodzą od razu w idealnym formacie. Bardzo często zmienne liczbowe są wczytywane jako tekst, daty jako stringi, a odpowiedzi typowo ankietowe („tak/nie”, „1 – zdecydowanie się nie zgadzam” itd.) jako ciągi znaków, z którymi chcemy pracować jak z kategoriami. Typowe operacje porządkujące to:
- Konwersja na typ liczbowy – np. skale od 1 do 5:
df["satysfakcja"] = pd.to_numeric(df["satysfakcja"], errors="coerce")Ustawienie
errors="coerce"zamienia niepoprawne wartości naNaN, dzięki czemu nie blokują obliczeń. - Konwersja dat – np. data wypełnienia ankiety:
df["data_wypelnienia"] = pd.to_datetime(df["data_wypelnienia"], dayfirst=True, errors="coerce") - Zmienne kategoryczne – np. płeć, kierunek studiów, grupa eksperymentalna:
from pandas.api.types import CategoricalDtype plec_cat = CategoricalDtype(categories=["kobieta", "mężczyzna", "inna"], ordered=False) df["plec"] = df["plec"].astype(plec_cat)Taka deklaracja kategorii pomaga utrzymać spójny porządek w tabelach i wykresach (np. „kobieta” zawsze będzie przed „mężczyzna”).
Braki danych: decyzje i implementacja w pandas
Braki danych (NaN) pojawiają się niemal zawsze i trzeba je obsłużyć konsekwentnie. Zanim powstanie ostateczny raport z analizy w Pythonie, dobrze mieć jasną strategię dla braków w zależności od typu zmiennej i znaczenia pytania. Technicznie w pandas używa się głównie:
df.isna().sum()– szybki przegląd braków w każdej kolumnie.df.dropna()– usunięcie wierszy z brakami (ostrożnie, może zmniejszyć próbę).df.fillna()– wypełnienie braków (np. średnią, medianą, stałą wartością):df["wiek"] = df["wiek"].fillna(df["wiek"].median())
Tworzenie zmiennych pochodnych i kodowanie odpowiedzi
Sam import i oczyszczenie danych to dopiero punkt startowy. Do raportu analitycznego zwykle dochodzą zmienne pochodne (indeksy, sumy skal, przekształcenia logarytmiczne) oraz różne formy kodowania odpowiedzi. Im bardziej automatyczne są te kroki, tym łatwiej je potem poprawić i odtworzyć.
Typowe przekształcenia:
- Sumaryczne indeksy z wielu pytań – klasyka przy skalach Likerta:
skala_satysfakcji = [ "sat_q1", "sat_q2", "sat_q3", "sat_q4" ] df["satysfakcja_total"] = df[skala_satysfakcji].sum(axis=1, min_count=1)min_count=1gwarantuje, że jeśli wszystkie odpowiedzi są puste, wynik pozostajeNaN, a nie 0. - Średnia zamiast sumy – przy nierównych liczbach odpowiedzi:
df["satysfakcja_mean"] = df[skala_satysfakcji].mean(axis=1) - Przekodowanie odpowiedzi tekstowych na liczby – na przykład dla jednokrotnego wyboru:
mapa_oceny = { "zdecydowanie się nie zgadzam": 1, "nie zgadzam się": 2, "ani się zgadzam, ani nie": 3, "zgadzam się": 4, "zdecydowanie się zgadzam": 5, } df["zgoda_1_num"] = df["zgoda_1"].map(mapa_oceny)Taki słownik można trzymać w osobnym module (
src/codings.py), żeby był jednym źródłem prawdy dla całej analizy. - Dichotomizacja zmiennych – dzielenie grup według progu:
df["wysoka_satysfakcja"] = (df["satysfakcja_total"] >= 15).astype(int)Tak utworzona zmienna zero-jedynkowa przydaje się przy tabelach kontyngencji i regresji logistycznej.
Jeżeli przekodowań jest dużo (np. kilkanaście pytań o tej samej strukturze odpowiedzi), dobrze jest zbudować prostą funkcję:
def recode_likert(df, columns, mapping, suffix="_num"):
for col in columns:
df[col + suffix] = df[col].map(mapping)
return df
likert_cols = [c for c in df.columns if c.startswith("zgoda_")]
df = recode_likert(df, likert_cols, mapa_oceny)
Filtrowanie próby i dokumentowanie decyzji
Rzadko całe surowe dane trafiają do analizy końcowej. Ktoś mógł nie wyrazić zgody, wypełnić jedynie pierwszą stronę ankiety albo należeć do grupy, która nie spełnia kryteriów badania. Selekcja próby powinna być jasno zakodowana i, co istotne, możliwa do odtworzenia.
Dobry wzorzec to utworzenie maski logicznej opisującej każdy warunek:
maska_zgoda = df["zgoda_na_udzial"] == "tak"
maska_min_odp = df["liczba_wypelnionych_pytań"] >= 10
maska_wiek = (df["wiek"] >= 18) & (df["wiek"] <= 65)
maska_koncowa = maska_zgoda & maska_min_odp & maska_wiek
df_analiza = df.loc[maska_koncowa].copy()
Takie maski można potem opisać w tekście pracy (co odfiltrowano i dlaczego) oraz użyć w tabeli „schemat selekcji próby” (flowchart lub prosta tabela z liczebnościami).
Automatyczne tabele statystyki opisowej
Statystyka opisowa to pierwsza sekcja, którą da się w dużej mierze zautomatyzować. Zamiast ręcznie liczyć średnie i odchylenia, lepiej przygotować funkcje generujące gotowe tabele, od razu z nazwami zmiennych przyjaznymi dla czytelnika pracy.
Prosta tabela dla zmiennych ilościowych
Dla zmiennych liczbowych przydaje się zestaw: liczebność, średnia, odchylenie standardowe, minimum, maksimum. Minimalna funkcja może wyglądać tak:
import numpy as np
def opis_zmiennych_ciaglych(df, cols, labels=None):
if labels is None:
labels = {c: c for c in cols}
staty = []
for col in cols:
x = df[col].dropna()
staty.append({
"zmienna": labels.get(col, col),
"N": x.shape[0],
"M": x.mean(),
"SD": x.std(ddof=1),
"min": x.min(),
"max": x.max()
})
return pd.DataFrame(staty)
labels = {
"wiek": "Wiek (lata)",
"satysfakcja_total": "Sumaryczny wynik satysfakcji"
}
tabela_opis = opis_zmiennych_ciaglych(
df_analiza,
cols=["wiek", "satysfakcja_total"],
labels=labels
)
Tak powstałą tabelę można zapisać od razu do pliku wykorzystywanego w pracy:
tabela_opis.to_csv("tables/statystyka_opisowa_ciagle.csv", index=False)
Tabela rozkładów dla zmiennych kategorycznych
Przy zmiennych nominalnych (np. płeć, kierunek, grupa) typowym zestawem jest liczebność i procent w każdej kategorii. value_counts() robi większość pracy, wystarczy to ładnie opakować:
def opis_zmiennych_kat(df, col, label=None, dropna=False):
vc = df[col].value_counts(dropna=dropna)
perc = df[col].value_counts(normalize=True, dropna=dropna) * 100
tabela = pd.DataFrame({
"kategoria": vc.index.astype(str),
"n": vc.values,
"pct": perc.values
})
tabela.insert(0, "zmienna", label or col)
return tabela
tabela_plec = opis_zmiennych_kat(df_analiza, "plec", label="Płeć")
Kilka takich tabel można potem złączyć pionowo (pd.concat) i zapisać jako jeden plik:
tabele_kat = [
opis_zmiennych_kat(df_analiza, "plec", "Płeć"),
opis_zmiennych_kat(df_analiza, "kierunek", "Kierunek studiów"),
]
tabela_kat_all = pd.concat(tabele_kat, ignore_index=True)
tabela_kat_all.to_csv("tables/statystyka_opisowa_kategorie.csv", index=False)
Ładne formatowanie tabel: zaokrąglenia, polskie przecinki i LaTeX
Surowa tabela z pandas jest dobra do analizy, ale do pracy dyplomowej potrzebna jest wersja czytelna i estetyczna. Przydaje się etap pośredni: „tabela do publikacji”.
Zaokrąglanie liczb i zmiana separatora dziesiętnego
Większość promotorskich uwag dotyczy drobnych detali: zbyt wielu miejsc po przecinku, mieszania kropek i przecinków. Tego da się uniknąć, przygotowując prostą funkcję formatowania:
def formatuj_tabele_opis(tabela, cols_float, ndigits=2, decimal=","):
tabela_fmt = tabela.copy()
for col in cols_float:
tabela_fmt[col] = (
tabela_fmt[col]
.round(ndigits)
.astype(str)
.str.replace(".", decimal)
)
return tabela_fmt
tabela_opis_fmt = formatuj_tabele_opis(
tabela_opis,
cols_float=["M", "SD", "min", "max"],
ndigits=2
)
tabela_opis_fmt.to_csv("tables/statystyka_opisowa_ciagle_fmt.csv", index=False)
Eksport do LaTeX i HTML
Jeśli praca jest składana w LaTeX-u, można wykorzystać DataFrame.to_latex():
tabela_opis_fmt.to_latex(
"tables/statystyka_opisowa_ciagle.tex",
index=False,
escape=False # umożliwia użycie np. znaków & w nazwach
)
Dla pracy w Wordzie często wygodniejszy jest HTML – da się go skopiować i wkleić z zachowaniem struktury tabeli:
tabela_opis_fmt.to_html(
"tables/statystyka_opisowa_ciagle.html",
index=False
)
Wykresy do pracy dyplomowej: standardowy szablon
Wykresy generowane „z palca” potrafią bardzo różnić się stylem. Łatwiej utrzymać spójność, jeśli na początku zdefiniuje się jeden szablon stylu wykresów i zapis do plików w ustalonym formacie.
Konfiguracja stylu matplotlib / seaborn
W module src/plotting_utils.py można trzymać funkcję ustawiającą domyślny wygląd:
import matplotlib.pyplot as plt
import seaborn as sns
def set_plot_style():
sns.set_theme(
style="whitegrid",
context="paper", # rozmiary elementów dostosowane do publikacji
font="DejaVu Sans"
)
plt.rcParams.update({
"figure.figsize": (6, 4),
"axes.titlesize": 11,
"axes.labelsize": 10,
"legend.fontsize": 9,
"xtick.labelsize": 9,
"ytick.labelsize": 9,
"savefig.dpi": 300,
"savefig.bbox": "tight"
})
W notatniku wystarczy raz na początku:
from src.plotting_utils import set_plot_style
set_plot_style()
Zapisywanie wykresów z automatycznymi nazwami plików
Ręczne wymyślanie nazw typu wykres1_final_v2.png kończy się chaosem. Prościej zdefiniować funkcję, która przyjmuje „slug” (krótki identyfikator) i sama zapisuje plik z numerem figury:
from pathlib import Path
FIG_DIR = Path(__file__).resolve().parent.parent / "figures"
def save_figure(fig, slug, ext="png"):
FIG_DIR.mkdir(parents=True, exist_ok=True)
filename = f"{slug}.{ext}"
fig_path = FIG_DIR / filename
fig.savefig(fig_path)
print(f"Zapisano wykres: {fig_path}")
Użycie w praktyce:
fig, ax = plt.subplots()
sns.histplot(df_analiza["wiek"], kde=False, ax=ax)
ax.set_title("Rozkład wieku badanych")
ax.set_xlabel("Wiek (lata)")
ax.set_ylabel("Liczebność")
save_figure(fig, "fig_rozkład_wieku")
Standardowe typy wykresów i ich implementacja
Większość prac dyplomowych korzysta z kilku powtarzalnych typów wizualizacji: rozkłady, porównania grup, zależności między dwiema zmiennymi. Każdy z tych typów można zapisać jako funkcję w src/plotting_utils.py.
Rozkład zmiennej ilościowej: histogram + gęstość
def plot_hist_density(series, title, xlabel, slug, bins=20):
fig, ax = plt.subplots()
sns.histplot(series.dropna(), bins=bins, kde=True, ax=ax)
ax.set_title(title)
ax.set_xlabel(xlabel)
ax.set_ylabel("Liczebność")
save_figure(fig, slug)
return fig, ax
plot_hist_density(
df_analiza["satysfakcja_total"],
title="Rozkład sumarycznego wyniku satysfakcji",
xlabel="Wynik skali satysfakcji",
slug="fig_rozkład_satysfakcja_total"
)
Porównanie grup: wykres pudełkowy
def plot_box_by_group(df, x, y, title, xlabel, ylabel, slug):
fig, ax = plt.subplots()
sns.boxplot(data=df, x=x, y=y, ax=ax)
ax.set_title(title)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
save_figure(fig, slug)
return fig, ax
plot_box_by_group(
df_analiza,
x="plec",
y="satysfakcja_total",
title="Wynik satysfakcji w zależności od płci",
xlabel="Płeć",
ylabel="Wynik skali satysfakcji",
slug="fig_box_satysfakcja_plec"
)
Zależność dwóch zmiennych ilościowych: wykres rozrzutu z linią regresji
def plot_scatter_with_reg(df, x, y, title, xlabel, ylabel, slug):
fig, ax = plt.subplots()
sns.regplot(data=df, x=x, y=y, ax=ax, ci=95, scatter_kws={"alpha": 0.6})
ax.set_title(title)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
save_figure(fig, slug)
return fig, ax
plot_scatter_with_reg(
df_analiza,
x="wiek",
y="satysfakcja_total",
title="Zależność pomiędzy wiekiem a satysfakcją",
xlabel="Wiek (lata)",
ylabel="Wynik skali satysfakcji",
slug="fig_scatter_wiek_satysfakcja"
)
Automatyczne tabele dla porównań grupowych
Poza statystyką opisową często dochodzi porównanie dwóch lub więcej grup: test t-Studenta, ANOVA, testy nieparametryczne. Przy dobrej organizacji można generować od razu tabele zawierające zarówno statystyki opisowe, jak i wyniki testów.
Porównanie dwóch grup: test t
Zamiast odpalać SPSS-a do każdego testu z osobna, lepiej mieć jedną funkcję, która policzy wszystko na podstawie nazw kolumn:
from scipy import stats
def compare_two_groups_ttest(df, group_col, group1, group2, dv_col):
x1 = df.loc[df[group_col] == group1, dv_col].dropna()
x2 = df.loc[df[group_col] == group2, dv_col].dropna()
# statystyki opisowe
opis = pd.DataFrame([
{
"grupa": group1,
"N": x1.shape[0],
"M": x1.mean(),
"SD": x1.std(ddof=1)
},
{
"grupa": group2,
"N": x2.shape[0],
"M": x2.mean(),
"SD": x2.std(ddof=1)
}
])
# test t (wariant z założeniem równości wariancji)
t_stat, p_val = stats.
Automatyczne tabele dla porównań grupowych – implementacja w Pythonie
Rozpoczętą wcześniej funkcję porównania dwóch grup można dokończyć tak, aby od razu zwracała zbiorczą tabelę z wynikami:
from scipy import stats
def compare_two_groups_ttest(df, group_col, group1, group2, dv_col, equal_var=True):
x1 = df.loc[df[group_col] == group1, dv_col].dropna()
x2 = df.loc[df[group_col] == group2, dv_col].dropna()
# statystyki opisowe
opis = pd.DataFrame([
{
"zmienna": dv_col,
"grupa": group1,
"N": x1.shape[0],
"M": x1.mean(),
"SD": x1.std(ddof=1)
},
{
"zmienna": dv_col,
"grupa": group2,
"N": x2.shape[0],
"M": x2.mean(),
"SD": x2.std(ddof=1)
}
])
# test t (equal_var=True → klasyczny, False → Welch)
t_stat, p_val = stats.ttest_ind(x1, x2, equal_var=equal_var)
# stopnie swobody
if equal_var:
dfree = x1.shape[0] + x2.shape[0] - 2
else:
# przybliżone df wg wzoru Welcha
s1 = x1.var(ddof=1)
s2 = x2.var(ddof=1)
n1 = x1.shape[0]
n2 = x2.shape[0]
num = (s1/n1 + s2/n2) ** 2
den = (s1**2 / ((n1**2) * (n1 - 1))) + (s2**2 / ((n2**2) * (n2 - 1)))
dfree = num / den
# wielkość efektu: d Cohena (z wariancją połączoną)
spooled = np.sqrt(
((x1.shape[0] - 1) * x1.var(ddof=1) + (x2.shape[0] - 1) * x2.var(ddof=1))
/ (x1.shape[0] + x2.shape[0] - 2)
)
d = (x1.mean() - x2.mean()) / spooled
wyniki = pd.DataFrame([{
"zmienna": dv_col,
"grupa_1": group1,
"grupa_2": group2,
"t": t_stat,
"df": dfree,
"p": p_val,
"d_Cohen": d,
"equal_var": equal_var
}])
return opis, wyniki
Przykładowe wywołanie dla satysfakcji i płci:
opis_plec, test_plec = compare_two_groups_ttest(
df_analiza,
group_col="plec",
group1="K",
group2="M",
dv_col="satysfakcja_total",
equal_var=False # wariant Welcha bez założenia równości wariancji
)
opis_plec.to_csv("tables/ttest_satysfakcja_plec_opis.csv", index=False)
test_plec.to_csv("tables/ttest_satysfakcja_plec_test.csv", index=False)
Jeśli takich testów jest kilka, wygodne jest spięcie ich pętlą i złączenie wyników:
zmienne_do_testu = ["satysfakcja_total", "zaangazowanie_total"]
lista_opis = []
lista_test = []
for zm in zmienne_do_testu:
opis, test = compare_two_groups_ttest(
df_analiza,
group_col="plec",
group1="K",
group2="M",
dv_col=zm,
equal_var=False
)
lista_opis.append(opis)
lista_test.append(test)
tabela_ttest_opis = pd.concat(lista_opis, ignore_index=True)
tabela_ttest_wyniki = pd.concat(lista_test, ignore_index=True)
tabela_ttest_opis.to_csv("tables/ttest_plec_opis_all.csv", index=False)
tabela_ttest_wyniki.to_csv("tables/ttest_plec_wyniki_all.csv", index=False)
Automatyczna tabela dla wielu grup: ANOVA jednoczynnikowa
Gdy grup jest więcej niż dwie, klasyczny wybór to ANOVA jednoczynnikowa. Schemat jest podobny: moduł statystyk + moduł formatowania wyników.
def anova_oneway(df, group_col, dv_col):
# podział danych na listę wektorów dla każdej grupy
groups = []
group_labels = []
for g, sub in df.groupby(group_col):
vals = sub[dv_col].dropna()
if len(vals) > 0:
groups.append(vals)
group_labels.append(g)
# statystyki opisowe dla każdej grupy
opis = (
df[[group_col, dv_col]]
.dropna()
.groupby(group_col)
.agg(
N=(dv_col, "count"),
M=(dv_col, "mean"),
SD=(dv_col, lambda x: x.std(ddof=1))
)
.reset_index()
.rename(columns={group_col: "grupa"})
)
opis.insert(0, "zmienna", dv_col)
# test ANOVA (klasyczny wariant jednoczynnikowy)
F_stat, p_val = stats.f_oneway(*groups)
wyniki = pd.DataFrame([{
"zmienna": dv_col,
"czynnik": group_col,
"F": F_stat,
"df1": len(groups) - 1,
"df2": df[dv_col].dropna().shape[0] - len(groups),
"p": p_val
}])
return opis, wyniki
W praktyce często łączy się kilka takich analiz w jedną tabelę:
zmienne_anova = ["satysfakcja_total", "zaangazowanie_total", "stres_total"]
lista_anova_opis = []
lista_anova_wyniki = []
for zm in zmienne_anova:
opis, wyniki = anova_oneway(df_analiza, group_col="kierunek", dv_col=zm)
lista_anova_opis.append(opis)
lista_anova_wyniki.append(wyniki)
tabela_anova_opis = pd.concat(lista_anova_opis, ignore_index=True)
tabela_anova_wyniki = pd.concat(lista_anova_wyniki, ignore_index=True)
tabela_anova_opis.to_csv("tables/anova_kierunek_opis.csv", index=False)
tabela_anova_wyniki.to_csv("tables/anova_kierunek_wyniki.csv", index=False)
Jeśli komisja wymaga testów post-hoc, dobrym uzupełnieniem są pakiety typu pingouin lub własna implementacja Tukey HSD / parowych t-testów z korektą Bonferroniego. Mechanizm generowania tabel pozostaje ten sam.
Testy nieparametryczne: U Manna-Whitneya i Kruskala-Wallisa
Gdy założenia testów parametrycznych są daleko od spełnienia (np. wyraźna skośność, skala porządkowa), można użyć wariantów nieparametrycznych i nadal generować estetyczne tabele.
def compare_two_groups_mannwhitney(df, group_col, group1, group2, dv_col, alternative="two-sided"):
x1 = df.loc[df[group_col] == group1, dv_col].dropna()
x2 = df.loc[df[group_col] == group2, dv_col].dropna()
# statystyki opisowe: mediana + kwartyle
opis = pd.DataFrame([
{
"zmienna": dv_col,
"grupa": group1,
"N": x1.shape[0],
"Me": x1.median(),
"Q1": x1.quantile(0.25),
"Q3": x1.quantile(0.75)
},
{
"zmienna": dv_col,
"grupa": group2,
"N": x2.shape[0],
"Me": x2.median(),
"Q1": x2.quantile(0.25),
"Q3": x2.quantile(0.75)
}
])
U_stat, p_val = stats.mannwhitneyu(x1, x2, alternative=alternative)
wyniki = pd.DataFrame([{
"zmienna": dv_col,
"grupa_1": group1,
"grupa_2": group2,
"U": U_stat,
"p": p_val,
"alternative": alternative
}])
return opis, wyniki
def kruskal_wallis(df, group_col, dv_col):
groups = []
labels = []
for g, sub in df.groupby(group_col):
vals = sub[dv_col].dropna()
if len(vals) > 0:
groups.append(vals)
labels.append(g)
# opis: mediana + kwartyle
opis = (
df[[group_col, dv_col]]
.dropna()
.groupby(group_col)
.agg(
N=(dv_col, "count"),
Me=(dv_col, "median"),
Q1=(dv_col, lambda x: x.quantile(0.25)),
Q3=(dv_col, lambda x: x.quantile(0.75)),
)
.reset_index()
.rename(columns={group_col: "grupa"})
)
opis.insert(0, "zmienna", dv_col)
H_stat, p_val = stats.kruskal(*groups)
wyniki = pd.DataFrame([{
"zmienna": dv_col,
"czynnik": group_col,
"H": H_stat,
"df": len(groups) - 1,
"p": p_val
}])
return opis, wyniki
Łączenie statystyk opisowych i wyników testów w jedną tabelę raportową
Komentarze recenzentów często dotyczą fragmentacji: osobno tabela ze średnimi, osobno z wynikami testu. W Pythonie można wygenerować jedną zbiorczą tabelę dla całej sekcji wyników.
def merge_desc_and_tests(opis_df, test_df, key_cols=["zmienna"]):
"""
Łączy statystyki opisowe i wyniki testów w jedną tabelę.
Kluczem jest zwykle nazwa zmiennej, czasem także nazwa czynnika.
"""
# przykład: łączenie po nazwie zmiennej i kolumnie "czynnik" (jeśli istnieje)
wspolne = [col for col in key_cols if col in opis_df.columns and col in test_df.columns]
tabela = opis_df.merge(test_df, on=wspolne, how="left")
return tabela
Zastosowanie do wyników ANOVA:
tabela_anova_all = merge_desc_and_tests(
tabela_anova_opis,
tabela_anova_wyniki,
key_cols=["zmienna"]
)
# kosmetyka: zaokrąglenie i separator dziesiętny
tabela_anova_all_fmt = formatuj_tabele_opis(
tabela_anova_all,
cols_float=["M", "SD", "F", "p"],
ndigits=3
)
tabela_anova_all_fmt.to_csv("tables/anova_kierunek_all_fmt.csv", index=False)
tabela_anova_all_fmt.to_latex("tables/anova_kierunek_all_fmt.tex", index=False)
Tip: blisko oddania pracy przydaje się prosty słownik zamiany nazw technicznych na etykiety przyjazne czytelnikowi:
label_map = {
"satysfakcja_total": "Satysfakcja (wynik ogólny)",
"zaangazowanie_total": "Zaangażowanie (wynik ogólny)",
"stres_total": "Poziom stresu",
}
tabela_anova_all_fmt["zmienna_opis"] = tabela_anova_all_fmt["zmienna"].map(label_map)
cols = ["zmienna_opis"] + [c for c in tabela_anova_all_fmt.columns if c != "zmienna_opis"]
tabela_anova_all_fmt = tabela_anova_all_fmt[cols]
Automatyzacja analiz wielozmiennowych: korelacje i regresja
Przy kilku skalach i wskaźnikach robi się gęsto: korelacje, regresja liniowa, czasem logistyczna. Te elementy też można opisać w sposób tabelaryczny, bez przepisywania liczb do Worda.
Macierz korelacji z zaznaczeniem istotności
Standardowy raport korelacji obejmuje współczynniki (np. Pearsona) i poziomy istotności. Da się to policzyć bez pętli po wszystkich parach „ręcznie”.
def corr_matrix_with_pvalues(df, cols, method="pearson"):
"""
Zwraca dwie macierze w postaci DataFrame:
- 'corr': współczynniki korelacji
- 'pval': odpowiadające im wartości p
"""
n = len(cols)
corr = pd.DataFrame(np.zeros((n, n)), columns=cols, index=cols)
pvals = pd.DataFrame(np.zeros((n, n)), columns=cols, index=cols)
for i, c1 in enumerate(cols):
for j, c2 in enumerate(cols):
if i > j:
# wykorzystaj symetrię
corr.loc[c1, c2] = corr.loc[c2, c1]
pvals.loc[c1, c2] = pvals.loc[c2, c1]
continue
if i == j:
corr.loc[c1, c2] = 1.0
pvals.loc[c1, c2] = 0.0
continue
x = df[c1].dropna()
y = df[c2].dropna()
wspolne = x.index.intersection(y.index)
x = x.loc[wspolne]
y = y.loc[wspolne]
if method == "pearson":
r, p = stats.pearsonr(x, y)
elif method == "spearman":
r, p = stats.spearmanr(x, y)
else:
raise ValueError("Nieznana metoda korelacji")
corr.loc[c1, c2] = r
corr.loc[c2, c1] = r
pvals.loc[c1, c2] = p
pvals.loc[c2, c1] = p
return corr, pvals
zmienne_korelacje = ["satysfakcja_total", "zaangazowanie_total", "stres_total", "wiek"]
corr, pvals = corr_matrix_with_pvalues(df_analiza, zmienne_korelacje, method="pearson")
corr.to_csv("tables/corr_pearson.csv")
pvals.to_csv("tables/corr_pearson_pvalues.csv")
Do pracy dyplomowej przydaje się „spłaszczona” tabela korelacji: każdy wiersz to jedna para zmiennych:
def flatten_corr_matrix(corr, pvals):
rows = []
cols = corr.columns
for i in range(len(cols)):
for j in range(i+1, len(cols)):
v1 = cols[i]
v2 = cols[j]
rows.append({
"zmienna_1": v1,
"zmienna_2": v2,
"r": corr.loc[v1, v2],
"p": pvals.loc[v1, v2]
})
return pd.DataFrame(rows)
tabela_corr_flat = flatten_corr_matrix(corr, pvals)
tabela_corr_flat_fmt = formatuj_tabele_opis(
tabela_corr_flat,
cols_float=["r", "p"],
ndigits=3
)
tabela_corr_flat_fmt.to_csv("tables/corr_pearson_flat_fmt.csv", index=False)
Prosta regresja liniowa z tabelą współczynników
Do regresji wygodne jest wykorzystanie pakietu statsmodels, który przypomina SPSS-a / R pod względem raportowania. Klucz to wyrzucenie wyników do DataFrame zamiast patrzenia w tekstowy „summary”.
Najczęściej zadawane pytania (FAQ)
Jak zacząć analizę danych do pracy dyplomowej w Pythonie zamiast w Excelu?
Na start wystarczy zainstalowany Python, podstawowa znajomość bibliotek pandas (tabele danych), matplotlib/seaborn (wykresy) i jedno środowisko do notebooków: JupyterLab albo VS Code z rozszerzeniem Jupyter. Dane z Excela lub CSV zapisujesz do katalogu data/raw/ i w notatniku robisz pierwszy krok: wczytanie danych, prosty podgląd (df.head(), df.info()), podstawowe statystyki opisowe.
Dobry schemat to jeden główny notebook z „historią” analizy (wczytanie, czyszczenie, wyniki) i ewentualnie prosty moduł w src/ z powtarzalnymi funkcjami. Dzięki temu każdy krok da się powtórzyć jednym uruchomieniem, zamiast klikania formuł w wielu plikach Excela.
Jak zorganizować foldery i pliki w projekcie pracy dyplomowej w Pythonie?
Praktyczny układ katalogów to:
data/raw/ – oryginalne pliki z ankiet/eksperymentów, nietykalne ręcznie,data/processed/ – dane po czyszczeniu (np. usunięte duplikaty, poprawione kodowanie),figures/ – wszystkie wykresy eksportowane z Pythona,tables/ – tabele wynikowe (CSV, XLSX, LaTeX, HTML),notebooks/ – notatniki Jupyter z raportem analizy,src/ – funkcje pomocnicze: czyszczenie danych, statystyki, wykresy.
Tip: korzystaj ze ścieżek względnych i pathlib, np. DATA_DIR / "raw" / "ankieta_studentow_2025.csv". Dzięki temu projekt działa niezależnie od tego, na jakim komputerze i w jakim katalogu go odpalisz.
Jak zapewnić powtarzalność (reprodukowalność) analizy wyników w Pythonie?
Kluczowe są trzy elementy: kod, dane i środowisko. Kod (notebook + moduły w src/) powinien zawierać całą logikę: od wczytania surowych danych, przez czyszczenie, do generowania tabel i wykresów. Dane odkładasz w zdefiniowanych katalogach data/raw/ i data/processed/, nie modyfikując ich „po cichu” ręcznie.
Środowisko odtwarzasz przez wirtualne środowisko (venv) i plik requirements.txt. Po zainstalowaniu bibliotek komendą pip install -r requirements.txt każda osoba (promotor, recenzent, Ty za rok) może uruchomić notebook i otrzymać ten sam zestaw tabel i wykresów z tych samych danych.
Jaki jest sens używania Jupyter Notebooka do raportu z analizy w pracy dyplomowej?
Notebook łączy kod, opis tekstowy (Markdown) i wynik (tabela, wykres) w jednym pliku. Dla pracy dyplomowej oznacza to, że możesz mieć „rozdział z wynikami” jako żywy dokument: najpierw opisujesz, co robisz (np. filtr wieku >= 18), potem pokazujesz fragment kodu, a pod spodem od razu pojawia się tabela lub wykres.
Promotor może szybko prześledzić logikę analizy (krok po kroku), a Ty – po zmianie jakiejś decyzji metodycznej – uruchamiasz cały notebook od nowa i dostajesz zaktualizowany komplet wyników. To znacznie bardziej przejrzyste niż seria statycznych zrzutów ekranu z Excela.
Czy tabele i wykresy generowane w Pythonie nadają się bezpośrednio do pracy dyplomowej?
Tak, ale trzeba zadbać o format wyjściowy i estetykę. Tabele możesz eksportować do CSV lub Excela (df.to_csv(), df.to_excel()) i dalej delikatnie sformatować w edytorze tekstu, albo generować od razu tabele LaTeX/HTML, jeśli uczelnia na to pozwala. Wykresy z matplotlib/seaborn zapisujesz do katalogu figures/ jako PNG, PDF lub SVG z odpowiednią rozdzielczością.
Uwaga: dobrze od razu w kodzie ustawić spójny styl – rozmiar czcionek, szerokość linii, kolory, opisy osi, legendy. Dzięki temu wszystkie rysunki będą wyglądały jednolicie i spełnią wymagania formalne (np. „Rys. 1. Zależność X od Y”).
Jak udokumentować w Pythonie przygotowanie danych (braki, filtry, kodowanie zmiennych)?
Najprościej: każdy istotny krok czyszczenia danych robisz jawnie w kodzie i opisujesz krótko w komórce tekstowej nad nim. Przykład: komórka Markdown „Usunięcie respondentów poniżej 18 roku życia”, pod nią linijka df = df[df["wiek"] >= 18]; potem „Zamiana kodów 1/2 na mężczyzna/kobieta”, a pod spodem df["plec"] = df["plec"].map({1: "M", 2: "K"}).
Dzięki temu masz dokładny „log” transformacji, który można zrecenzować. Jeśli w pracy opisujesz, że „usunięto obserwacje z brakami danych w kluczowych zmiennych”, recenzent może sprawdzić konkretny fragment kodu, zamiast domyślać się, co dokładnie zostało zrobione w Excelu.
Jakie biblioteki Pythona są najczęściej używane do automatycznego raportu z analizy danych?
Podstawowy zestaw do pracy dyplomowej to:
pandas – wczytywanie, filtrowanie i agregacja danych tabelarycznych,numpy – operacje numeryczne „pod spodem”,matplotlib i seaborn – wykresy (od prostych słupków po wykresy rozrzutu z regresją),scipy oraz ewentualnie pingouin – testy statystyczne (t‑testy, korelacje, ANOVA itd.),jupyter – środowisko do uruchamiania notebooków z raportem analizy.
Ten zestaw można zapisać w requirements.txt i zainstalować jedną komendą. Dla większości prac dyplomowych z obszaru nauk społecznych, ekonomii czy prostszych badań eksperymentalnych taki pakiet w zupełności wystarcza.
Najważniejsze wnioski
- Python pozwala zamienić ręczne „klikanie” w Excelu na półautomatyczny pipeline analityczny: jeden skrypt lub notebook wczytuje dane, przetwarza je, liczy statystyki i generuje komplet tabel oraz wykresów gotowych do wklejenia do pracy.
- Excel jest wygodny do szybkiego podglądu, ale przy większych badaniach przegrywa z Pythonem pod względem powtarzalności, kontroli nad krokami analizy i odporności na „niewidoczne” błędy (przesunięte formuły, filtry, ręczne poprawki).
- Kod w Pythonie tworzy jedno źródło prawdy dla wszystkich wyników w pracy: każdy wykres i każda tabela są efektem uruchomienia tego samego, jawnie zapisanego zestawu operacji, co ułatwia recenzję i korekty decyzji metodycznych.
- Raport z analizy można zorganizować jako skrypt (pełna automatyzacja, idealny gdy metodyka jest ustalona) lub notebook Jupyter (połączenie komentarza merytorycznego, kodu i wyników), co odpowiada zarówno potrzebom promotora, jak i recenzenta.
- Oczekiwania formalne wobec analizy w Pythonie obejmują: przejrzysty opis zastosowanych miar i testów, jawne pokazanie kroków przygotowania danych (braki, filtry, kodowanie zmiennych) oraz estetyczne, poprawnie opisane tabele i wykresy.
- Dobrze zorganizowana struktura projektu (osobne katalogi na dane surowe i przetworzone, figury, tabele, notebooki i moduły z funkcjami) zwiększa czytelność analizy, ułatwia jej odtworzenie i ogranicza ryzyko przypadkowych modyfikacji danych.
Bibliografia i źródła
- Python Data Science Handbook. O'Reilly Media (2016) – Pandas, matplotlib, Jupyter; praktyczne wzorce analizy danych
- Python for Data Analysis, 3rd Edition. O'Reilly Media (2022) – Pandas, czyszczenie danych, tabele, wykresy, praca z CSV/XLSX
- Guide to Reproducible Code in Ecology and Evolution. British Ecological Society (2017) – Zasady reprodukowalności analiz, struktura projektów badawczych






