Narzędzia i analiza danych
Wydział Nauk Społecznych, Uniwersytet SWPS
25 kwietnia 2026
Każdy projekt data journalism — niezależnie od tematu i skali — przechodzi przez te same cztery etapy:
W praktyce proces jest iteracyjny — podczas analizy odkrywacie, że trzeba wrócić do czyszczenia; podczas wizualizacji okazuje się, że potrzeba dodatkowej zmiennej. To normalne.
Zasada: zaczynaj od najprostszej metody. Scraping i API to ostateczność, nie punkt startu.
Zawsze zacznijcie od podstaw:
Dopiero potem:
| Pytanie | Typ wizualizacji |
|---|---|
| Jak coś zmieniało się w czasie? | Wykres liniowy |
| Jak porównać kilka kategorii? | Wykres słupkowy |
| Jaka jest struktura całości? | Wykres kołowy / słupkowy skumulowany |
| Jak zjawisko rozkłada się w przestrzeni? | Mapa choropleth |
| Czy istnieje związek między zmiennymi? | Scatter plot |
| Etap | Narzędzie | Kiedy używać |
|---|---|---|
| Pozyskiwanie | dane.gov.pl, GUS BDL | Oficjalne dane polskie |
| Czyszczenie | OpenRefine, Google Sheets | Do ok. 50 tys. wierszy |
| Analiza | jamovi, Orange | Statystyki opisowe bez kodu |
| Wizualizacja | Datawrapper, Flourish, Orange | Publikacja interaktywna |
⚠️ Pełny przegląd narzędzi zaawansowanych (Python, R, pandas, ggplot2, QGIS, Scrapy) znajdziecie w notatkach — dla większości projektów na tym kursie tego zestawu nie będziecie potrzebować.
Prześledźmy cały proces na jednym przykładzie — od surowego pliku do opublikowanego wykresu.
Pytanie: Jak zmieniał się ruch na poszczególnych odcinkach granicy zewnętrznej Polski w pierwszym kwartale 2026 roku — i gdzie przyjazdów cudzoziemców jest więcej niż wyjazdów?
Źródło: Bazy ruchu granicznego — Straż Graniczna (dostawca: Komenda Główna Straży Granicznej, licencja CC0, aktualizacja miesięczna)
Konkretny plik: Baza ruchu granicznego STYCZEŃ–MARZEC 2026 — CSV/XLSX, ok. 18 tys. wierszy
Co otrzymujemy: jeden wiersz = jedno przejście graniczne × dzień × kategoria podróżnych (C = cudzoziemcy, RP = obywatele RP…) × kierunek (przyjazd/wyjazd). 24 kolumny: Placówka SG, Przejście, Rodzaj przejścia (drogowe/kolejowe/lotnicze/morskie), Odcinek, Oddział SG, Data, Kto, Kierunek, szczegóły odprawy (Paszportowy, MRG, Pozasystemowa…) i Razem.
raw_ruch_graniczny_2026_Q1.csv💡 Zasada: surowy plik zostaje nietknięty. Wszystkie modyfikacje robimy na kopii.
Uruchamiamy OpenRefine lokalnie (działa w przeglądarce, ale dane nie opuszczają komputera). Importujemy CSV.
Typowe operacje na tych danych:
clean_ruch_graniczny_2026_Q1.csv⚠️ Najważniejsze: OpenRefine zachowuje historię wszystkich operacji (JSON) — możecie ją wyeksportować i załączyć do projektu jako dokumentację czyszczenia. To wasz log decyzji czyszczenia.
Kiedy klikacie Edit cells → Common transforms → To date na kolumnie Data i widzicie:
Invalid Date Invalid Date — Thu Jan 01 1970 01:00:00 GMT+0100
Co to znaczy: OpenRefine spróbował sparsować tekst jako datę, ale nie rozpoznał formatu. „1 stycznia 1970, 01:00 GMT+1” to epoch zero — uniksowy zerowy znacznik czasu, który pokazuje się, gdy parser dostał pusty ciąg lub coś, czego nie umie zinterpretować.
Dlaczego tak się dzieje tutaj: w pliku Straży Granicznej Data przychodzi jako tekst „2026-01-01” (lub czasem „01.01.2026”), ale OpenRefine nie zawsze wykrywa format ISO samodzielnie — zwłaszcza po imporcie z CSV z mieszanymi separatorami.
Na kolumnie Data klikamy Edit cells → Transform… i wpisujemy wyrażenie GREL zależnie od formatu:
Jeśli wartości wyglądają jak „2026-01-01” (ISO):
value.toDate("yyyy-MM-dd")
Jeśli jak „01.01.2026” (polski format z kropkami):
value.toDate("dd.MM.yyyy")
Jeśli są mieszane lub niepewne — toDate() z kilkoma formatami po kolei:
value.toDate("yyyy-MM-dd", "dd.MM.yyyy", "dd/MM/yyyy")
Po transformacji komórki zmieniają kolor na zielony (oznaczenie typu date w OpenRefine) i Timeline facet zaczyna działać.
Otwieramy jamovi (darmowy, desktop) i wczytujemy clean-ruch-graniczny-2026-Q1.csv (dostępny w Resources/ruch-graniczny-walkthrough w repo kursu).
Zanim zaczniemy: w nagłówkach kolumn ustawiamy typ pomiaru (measure type) — Odcinek, Kierunek, Kto_pelne jako Nominal; Razem jako Continuous. Bez tego Split by nie zadziała.
Co liczymy na początek — zawsze:
Wyniki jamovi są w formacie APA, gotowe do skopiowania do raportu.
Zanim policzymy cokolwiek dalej — co w ogóle jest w tym pliku?
⚠️ Pierwsze odkrycie: asymetria liczby wierszy przyjazd vs wyjazd wymaga sprawdzenia, zanim cokolwiek zsumujemy — to nie jest równoważne pokrycie.
| Odcinek | Valid | średnia | mediana | max |
|---|---|---|---|---|
| droga lotnicza | 4 546 | 1 083 | 363 | 9 978 |
| z Ukrainą | 4 554 | 790 | 82 | 8 814 |
| z Rep. Federalną Niemiec | 3 007 | 15 | 8 | 125 |
| z Republiką Białorusi | 2 173 | 362 | 68 | 4 648 |
| granica morska | 1 890 | 19 | 10 | 246 |
| z Republiką Litewską | 1 276 | 27 | 6 | 291 |
| z Federacją Rosyjską | 744 | 161 | 48 | 2 227 |
| Odcinek | przyjazd | wyjazd | stosunek |
|---|---|---|---|
| droga lotnicza | 2 472 420 | 2 451 208 | 1,01 |
| z Ukrainą | 1 790 109 | 1 809 218 | 0,99 |
| z Republiką Białorusi | 403 839 | 383 752 | 1,05 |
| z Federacją Rosyjską | 64 122 | 55 846 | 1,15 |
| z Rep. Federalną Niemiec | 45 441 | 8 | 5 680 |
| z Republiką Litewską | 34 141 | 0 | ∞ |
| granica morska | 17 032 | 18 673 | 0,91 |
💡 Najciekawsze odkrycie: na granicach wewnętrznych (DE, LT) wyjazd ≈ 0 — TPKG rejestruje tylko wjazdy do Polski. Gdybyśmy policzyli „saldo migracji” bez tego rozróżnienia, wyszedłby absurd.
| Przejście | Razem |
|---|---|
| Warszawa-Okęcie | 1 720 515 |
| Kraków-Balice | 1 041 105 |
| Medyka – Szeginie | 879 072 |
| Korczowa – Krakowiec | 670 145 |
| Katowice-Pyrzowice | 585 865 |
| Hrebenne – Rawa Ruska | 463 364 |
| Dorohusk – Jagodzin | 460 560 |
| Terespol – Brześć | 444 428 |
| Wrocław-Strachowice | 381 807 |
| Gdańsk-Rębiechowo | 349 402 |
Pierwsze 4 przejścia to prawie połowa całego ruchu Q1 2026. Typowy long-tail — histogram potwierdza: mediana całego Razem = 48, ale 99. percentyl = 5 283.
| Odcinek | przyjazd | wyjazd | saldo |
|---|---|---|---|
| z Ukrainą | 1 731 776 | 1 753 058 | −21 282 |
| droga lotnicza | 810 455 | 816 169 | −5 714 |
| z Republiką Białorusi | 374 845 | 357 227 | +17 618 |
| z Federacją Rosyjską | 58 413 | 50 499 | +7 914 |
| granica morska | 13 006 | 13 780 | −774 |
Historia do opowiedzenia: w Q1 2026 więcej cudzoziemców wyjeżdża z Polski na Ukrainę niż przyjeżdża — saldo minus 21 tys. Odwrotnie na granicy białoruskiej (+17,6 tys.) i rosyjskiej (+7,9 tys.). Każda z tych trzech liczb to potencjalny artykuł.
Z analizy w jamovi wyszły trzy różne pytania wizualizacyjne, a nie jedno:
Nie wybieramy wizualizacji zanim nie zobaczymy liczb. Gdybyśmy od razu skoczyli do „mapy choropleth Polski”, zgubilibyśmy to, że ciekawy jest kierunek, nie geografia.
Bierzemy pytanie 2 (przepływ cudzoziemców) i robimy wykres słupkowy rozdzielony (split bars) w Datawrapper — dwa oddzielne słupki (przyjazd i wyjazd) obok siebie dla każdego odcinka.
| Krok | Narzędzie | Wynik |
|---|---|---|
| 1. Pozyskanie | dane.gov.pl (dataset 3595) | raw_ruch_graniczny_2026_Q1.csv |
| 2. Czyszczenie | OpenRefine | clean_ruch_graniczny_2026_Q1.csv + log operacji |
| 3. Analiza | jamovi | Opis rozkładu, asymetria kierunków, saldo cudzoziemców → wybór pytania wizualizacyjnego |
| 4. Wizualizacja | Datawrapper | Opublikowany wykres split bars (przyjazd vs wyjazd) + kod embed |
.omv (jamovi), historia zmian DatawrapperPięć sposobów, na które liczby kłamią w twoim imieniu — nawet kiedy tego nie chcesz. Po przerwie wrócimy do nich jako filtra przy waszej własnej wizualizacji w Datawrapper.
Dwie zmienne mogą rosnąć razem bez żadnego związku przyczynowego — bo obie reagują na coś trzeciego, albo zwyczajnie przez przypadek.
Czerwone flagi w nagłówku: „powoduje”, „prowadzi do”, „wywołuje” bez badania eksperymentalnego.
Jak pisać uczciwie: „wiąże się z”, „idzie w parze z”, „towarzyszy” — i zapytajcie eksperta o mechanizm.
Pokazywanie tylko fragmentu danych, który potwierdza tezę, i milczenie o reszcie.
Test uczciwości: czy mogę pokazać te same dane w pełnym zakresie czasowym i nadal mam tę samą historię? Jeśli nie — to nie mam historii.
To najczęstsza pułapka, bo popełnia się ją nieświadomie — Excel, Datawrapper i inne narzędzia same proponują ustawienia, które wyolbrzymiają różnice.
Regułka: domyślnie oś od zera. Wyjątek (np. skala log dla danych silnie prawoskośnych — jak nasz ruch graniczny) wymaga podpisu w nocie wykresu.
Wykres A: oś Y 49–51 %, słupki dramatycznie różnej wysokości, tytuł „POPARCIE ZAŁAMAŁO SIĘ”
Wykres B: oś Y 0–100 %, słupki prawie identyczne, tytuł „Poparcie praktycznie bez zmian”
Dane: 50,3 % → 49,7 %.
Średnia krajowa to wygodna wymówka, żeby nie patrzeć dalej.
Praktyczna reguła dla waszych projektów: jeśli jednostką jest kraj/region/miasto, zawsze sprawdźcie wskaźnik per capita obok liczby bezwzględnej. Wybór między nimi to decyzja redakcyjna — ale musicie znać oba.
Dane z dane.gov.pl, GUS czy NBP są wiarygodnym punktem wyjścia — ale nie są prawdą objawioną.
Co robicie z tym w praktyce: czytacie notę metodologiczną (zawsze jest), porównujecie z drugim źródłem, zapisujecie datę odniesienia w artykule.
Każda z pięciu pułapek ma ten sam kształt: dane są poprawne, ale historia wokół nich wprowadza w błąd.
To nie jest problem techniczny — to kwestia uczciwości narracji.
Po przerwie dostaniecie zbiór danych o przestępczości w polskich powiatach i wasze zadanie będzie proste: przeanalizować je w jamovi i zrobić wykres, który nie popełnia żadnej z tych pięciu pułapek.
Pytanie redakcyjne: „Gdzie w Polsce jest najwięcej przestępstw — i czy to, co widzimy w nagłówkach, zmienia się, kiedy policzymy to per capita?“
Do końca tej części każdy z was opublikuje własny wykres oparty na własnej analizie w jamovi.
Na Classroom (i w Resources kursu) znajdziecie plik przestepstwa-powiaty-2025.csv — fikcyjny, ale zbudowany tak, żeby przypominał realne statystyki policyjne.
12 kolumn, wszystkie 380 powiatów Polski:
⚠️ 10 par powiatów ma te same nazwy (brzeski, grodziski, świdnicki…) w różnych województwach → dlatego w pliku jest kolumna teryt — klucz do poprawnego łączenia z mapą.
⚠️ Dane są dydaktyczne, nie publikujcie ich jako rzeczywistych. Celem jest warsztat analityczny, nie źródło cytowania.
W polskich mediach regularnie pojawiają się nagłówki typu „Warszawa najbardziej niebezpiecznym miastem Polski” — na podstawie liczb bezwzględnych. To jest Pułapka 4: agregacja ukrywa różnice.
Wasze zadanie: sprawdzić, czy to prawda, i zdecydować jako redakcja — jaką historię naprawdę opowiadają te dane?
Otwórzcie jamovi i wczytajcie CSV. Ustawcie typy pomiaru w nagłówkach: typ_powiatu, województwo jako Nominal; zmienne liczbowe jako Continuous.
Policzcie trzy rzeczy:
Po zakończeniu analizy zapiszcie sobie odpowiedzi (nie oddajemy, to dla waszej dyscypliny):
💡 To nagłówek będzie waszym tytułem wykresu w Datawrapper. Nie opisowym („Przestępczość w powiatach 2025”), tylko komunikującym wniosek („Najwięcej przestępstw w Warszawie — ale per capita liderują małe powiaty na południu”).
Zalogujcie się na datawrapper.de. Macie do wyboru trzy formaty — wybierzcie jeden, który najlepiej odpowiada waszej historii z Części 1:
Szczegółowe kroki — handout Datawrapper.
Zanim klikniecie Publish, sprawdźcie własny wykres:
Jeśli trzy lub więcej haków jest czerwone — wracacie do Datawrapper i poprawiacie. Nie publikujecie wykresu, który sami byście skrytykowali.
Do końca części 2 seminarium.
Po 40 minutach praktyki w Datawrapper macie znacznie lepszą podstawę do podjęcia decyzji o narzędziach:
Każda grupa w 60 sekundach:
Pozostałe grupy: czy widzicie lepszą opcję?
Seminarium 4 (09.05) jest warsztatem wizualizacji waszych własnych danych projektu w Datawrapper. Bez pobranych danych nie będziecie mogli w pełni uczestniczyć w ćwiczeniu.
To nie jest opcja. Grupy bez danych na seminarium 4 będą musiały nadgonić w czasie warsztatu zamiast wykorzystać go do prototypowania finalnych wizualizacji projektu.
Ten rytm praca → deliverable → check-in działa tylko wtedy, gdy deliverables są na czas. Prześlijcie wcześniej, jeśli możecie.
Podstawy dziennikarstwa danych (DZ.N13.T26.A)