Po co w ogóle szukać obserwacji odstających?
Jeśli analizujesz dane w R, szybciej czy później natkniesz się na liczby, które „nie pasują” do reszty. Dla jednych to szum, dla innych sygnał ostrzegawczy albo szansa na ciekawą historię z danych. Zanim uruchomisz funkcje do wykrywania obserwacji odstających, zatrzymaj się na chwilę: jaki masz cel – posprzątać dane dla modelu, czy lepiej zrozumieć zjawisko?
Błąd, sygnał czy ciekawy przypadek?
Obserwacje odstające mogą mieć zupełnie różne źródła. Jedna skrajna wartość może oznaczać błąd pomiaru, inna – rzadkie, ale realne zjawisko, jeszcze inna – zmianę procesu biznesowego.
Najczęstsze scenariusze:
- Błąd techniczny – literówka w bazie, złe jednostki, pomylone kolumny, niepoprawne kodowanie braków danych (np. „9999” zamiast NA).
- Rzadkie, ale prawdziwe zdarzenie – pojedynczy bardzo duży koszyk zakupowy, ekstremalny wynik w teście, wyjątkowo długi czas obsługi klienta.
- Zmiana w systemie – nowa kampania marketingowa, modernizacja urządzeń pomiarowych, nowa polityka obsługi, która zmienia rozkład danych.
Kluczowa decyzja brzmi: czy traktujesz odstające obserwacje jako szum, który trzeba ograniczyć, czy jako sygnał, który trzeba zrozumieć? Jeśli tworzysz model predykcyjny dla codziennych przypadków – outliery mogą przeszkadzać. Jeśli robisz analitykę ryzyka lub szukasz nietypowych sytuacji – to właśnie one są najcenniejsze.
Jak outliery potrafią „zepsuć” średnią, korelację i regresję
Obserwacje odstające mają szczególnie duży wpływ na metody wrażliwe na wartości skrajne: średnią arytmetyczną, odchylenie standardowe, klasyczną korelację Pearsona i regresję liniową. Czego się spodziewać?
- Średnia przestaje reprezentować „typową” wartość, gdy kilka skrajnych punktów ciągnie ją w jedną stronę. Mediana bywa wtedy znacznie stabilniejsza.
- Odchylenie standardowe rośnie przez outliery, przez co Z-score staje się spłaszczony i trudniej wykryć kolejne nietypowe przypadki.
- Korelacja może zostać sztucznie zawyżona lub zaniżona przez jeden ekstremalny punkt. Jeden outlier na wykresie rozrzutu potrafi „uratować” lub „zabić” istotność współczynnika Pearsona.
- Regresja liniowa – kilka punktów o dużej dźwigni (leverage) może ustawić nachylenie prostej regresji w kierunku, który nie ma sensu biznesowego. Wrażliwe są też standardowe błędy współczynników, a więc wnioski z testów.
Wyobraź sobie analizę miesięcznej sprzedaży w sklepach. Wszystkie punkty mieszczą się w rozsądnym zakresie, ale jeden sklep przez pomyłkę dostał zapis „sprzedaż = 1000000” zamiast „1000”. Średnia sprzedaż wystrzela, a korelacja sprzedaży z liczbą klientów zaczyna prezentować się „dziwnie dobrze”. Bez identyfikacji i weryfikacji tej obserwacji wnioski z takiego modelu sprzedażowego będą mocno przekłamane.
Przykłady z praktyki: sprzedaż i eksperymenty
W analizie sprzedaży obserwacje odstające występują często przy:
- wielkich zamówieniach korporacyjnych wśród codziennych zakupów detalicznych,
- specjalnych akcjach promocyjnych, które „psują” typowy wzorzec sezonowości,
- błędach w imporcie danych z systemu sprzedaży (duplikaty, złe jednostki).
W danych eksperymentalnych z laboratoriów czy badań A/B odchylenia mogą wynikać z:
- jednego błędnie skalibrowanego urządzenia,
- uczestnika, który nie stosuje się do instrukcji,
- czynników środowiskowych (temperatura, hałas, przerwa w badaniu).
Za każdym razem pojawia się pytanie: czy chcesz „wygładzić” dane dla statystycznej poprawności, czy zrozumieć przyczynę nietypowego wyniku? Inaczej potraktujesz pomyłkę w zapisie temperatury w laboratorium, a inaczej realny incydent bezpieczeństwa w fabryce.
Czy tylko sprzątanie, czy także zrozumienie zjawiska?
Przed uruchomieniem funkcji do wykrywania outlierów w R warto odpowiedzieć sobie na kilka prostych pytań:
- Czy celem analizy jest budowa stabilnego modelu predykcyjnego, czy zrozumienie pełnego rozkładu zjawiska, w tym rzadkich przypadków?
- Czy nietypowe wartości mają dla ciebie znaczenie biznesowe (np. duże ryzyko, duże przychody), czy są jedynie artefaktami technicznymi?
- Czy model ma być odporny na outliery (robust statistics), czy raczej zakładasz wstępne „czyszczenie” danych?
Odpowiedź ukierunkuje wybór metody: surowe usuwanie ekstremów, winsoryzację, transformacje czy statystyki odporne. Zanim przejdziesz do IQR i Z-score, doprecyzuj: jakie decyzje chcesz podjąć na podstawie analizy odstających obserwacji?
Czym jest obserwacja odstająca – definicje i intuicja
Rzadkość kontra błąd: error vs rarity
Nie każda rzadko występująca wartość jest błędem. I odwrotnie – błędy czasem leżą w środku rozkładu i nie wyróżniają się ekstremalnością. To pierwsze rozróżnienie, które porządkuje myślenie o outlierach.
- Rarity – wartość rzadka, ale mieszcząca się w realistycznym zakresie. Przykład: bardzo wysoki, ale fizycznie możliwy wzrost człowieka.
- Error – wartość niemożliwa lub wysoce nieprawdopodobna w świetle wiedzy domenowej. Przykład: wzrost 300 cm w badaniu ogólnej populacji, ujemny czas dostawy.
Jeśli używasz w R metod takich jak IQR czy Z-score, one nie odróżniają „błędu” od „rzadkości”. Oznaczają jedynie obserwacje odstające w sensie statystycznym. To ty interpretujesz, czy taki punkt jest do poprawy, do usunięcia, czy wręcz do osobnej analizy.
Outlier statystyczny, biznesowy i techniczny
Jedna obserwacja może być odstająca w jednym sensie, a zupełnie normalna w innym. Warto te perspektywy jasno nazwać.
- Outlier statystyczny – punkt znacząco odbiegający od reszty próby, np. |Z-score| > 3 lub wartość poza „wąsami” boxplota wg IQR. Metody IQR i Z-score wyłapują właśnie ten rodzaj odstępstwa.
- Outlier biznesowy – obserwacja szczególnie istotna z perspektywy decyzji, np. duża strata, ogromna transakcja, awaria systemu. Może być statystycznie typowa, ale biznesowo krytyczna.
- Outlier techniczny – punkt wynikający z błędu zbierania lub przetwarzania danych: podwójne księgowanie, błędne jednostki (kg vs g), nieprawidłowe mapowanie kategorii.
W praktyce workflow bywa dwuetapowy: najpierw metody statystyczne (IQR, Z-score, wizualizacje) sygnalizują „podejrzane” punkty, a potem wiedza domenowa i kontakt z biznesem pomagają rozstrzygnąć, z którym typem outliera masz do czynienia.
Rozkład, skala i kontekst domenowy
To, co jest odstające w jednym rozkładzie, może być całkowicie typowe w innym. Dane o dochodach, liczbie transakcji, liczbie błędów często mają długie prawe ogony. W takich przypadkach klasyczny IQR czy Z-score z progiem 3 odchyleń może oznaczyć dużą część realnych obserwacji jako „podejrzane”.
Kilka punktów, które warto zadać sobie przed uruchomieniem kodu w R:
- Jaki jest typ rozkładu (symetryczny, skośny, dwumodalny)?
- Czy zmienna ma naturalne granice (np. 0–100%, wartości nieujemne, górny limit techniczny)?
- Czy wiesz, że mogą występować naturalne ekstrema (np. w danych pogodowych, finansowych, produkcyjnych)?
IQR oraz Z-score są najbardziej intuicyjne przy rozkładach zbliżonych do normalnych. Gdy dane są silnie skośne lub mają ciężkie ogony, często lepiej najpierw zastosować transformację (np. logarytmiczną), a dopiero potem liczyć IQR/ Z-score, albo przejść na metody odporne (MAD, robust Z-score).
Pytania pomocnicze: skąd pochodzą dane?
Zanim zaczniesz wykrywanie obserwacji odstających w R, doprecyzuj kontekst:
- Jak dane były zbierane? Ręcznie, z sensora, przez API, z systemu ERP?
- Czy istnieją znane nietypowe okresy (wdrożenia, awarie, święta, kampanie), które generują „inne” dane?
- Jakie są ograniczenia fizyczne/organizacyjne – minimalna i maksymalna możliwa wartość, typowe poziomy?
Te pytania często rozstrzygają, czy oznaczona przez IQR czy Z-score obserwacja odstająca ma być usunięta, czy raczej opisana w raporcie jako ciekawy przypadek biznesowy. W R łatwo jest odfiltrować rekordy. Pytanie brzmi: czy wiesz, co i dlaczego właśnie usuwasz?
Przygotowanie danych w R do analizy obserwacji odstających
Wczytanie danych i pierwsze spojrzenie
Bez solidnego przygotowania danych nawet najlepsze funkcje do IQR i Z-score wygenerują mylące wyniki. Zacznij od prostego, ale konsekwentnego workflow.
Jeśli pracujesz z plikami CSV, sprawdzi się pakiet readr:
library(readr)
library(dplyr)
dane <- read_csv("dane.csv")
glimpse(dane)
summary(dane)
Przy większych zbiorach możesz użyć data.table:
library(data.table)
dane <- fread("dane.csv")
str(dane)
summary(dane)
glimpse/str pozwalają szybko zobaczyć typy zmiennych (numeric, character, factor), a summary podaje podstawowe statystyki: min, max, medianę, kwartyle. Już na tym etapie często rzucają się w oczy ekstremalne wartości – szczególnie, gdy max jest „absurdalny” względem kontekstu.
Typy zmiennych, braki danych i nietypowe kody
Zanim użyjesz IQR czy Z-score, musisz mieć pewność, że kolumna faktycznie jest numeryczna i nie zawiera „zaszytych” błędów. Co zwykle wymaga sprawdzenia?
- Czy numeryczne zmienne nie są przypadkiem character (np. z powodu przecinka zamiast kropki dziesiętnej)?
- Czy braki danych są zapisane jako NA, czy jako magiczne liczby typu 9999, -1, 0?
- Czy nie masz w jednej kolumnie różnych jednostek (np. kg i g)?
Przykładowy fragment w R, który wyłapuje „podejrzane” wartości w jednej kolumnie:
dane %>%
count(wartosc) %>%
arrange(desc(n)) %>%
head(20)
Jeżeli w czołówce pojawiają się takie liczby jak 9999 czy -999, to sygnał, że ktoś kodował nimi brak danych albo błędy. Przed obliczaniem IQR lub Z-score warto podmienić je na NA:
dane <- dane %>%
mutate(
wartosc = ifelse(wartosc %in% c(9999, -999), NA, wartosc)
)
Proste statystyki opisowe: min, max, kwartyle, sd, mad
Kolejny krok to szybka diagnostyka wybranej zmiennej: minimum, maksimum, kwartyle, odchylenie standardowe i MAD (Median Absolute Deviation – miara odporna na outliery).
library(psych) # opcjonalnie, do opisowych
x <- dane$wartosc
summary(x)
sd(x, na.rm = TRUE)
mad(x, na.rm = TRUE)
quantile(x, probs = c(0, 0.25, 0.5, 0.75, 1), na.rm = TRUE)
Jeśli zauważysz ogromną różnicę między sd a mad, to sygnał, że w danych mogą siedzieć silne outliery. Już na tym etapie możesz zadać sobie pytanie: czy dane są na tyle „czyste”, że klasyczne metody (IQR, Z-score) będą miały sens?
Prosty szkic workflow: od wyboru kolumny do zapisu wyników
Jeżeli pracujesz z wieloma zmiennymi, przydaje się prosty schemat, który możesz powtarzać:
- Wybierz kolumnę numeryczną – np.
wartosc. - Policz
summary,sd,mad,quantile. - Oblicz IQR i progi, wyznacz wektor logiczny
is_outlier_iqr. - Dolna granica:
Q1 - 1.5 * IQR - Górna granica:
Q3 + 1.5 * IQR - Silna skośność rozkładu – w danych typu czas trwania, obroty, liczba klientów większość obserwacji jest niska, a ogon ciągnie się w prawo. IQR „trzyma się” środka, więc wysokie, ale realne wartości dostają etykietę outlierów.
- Małe zbiory danych – gdy masz kilkanaście–kilkadziesiąt obserwacji, szacunek kwartylów jest niestabilny. Jedna nowa obserwacja potrafi mocno przesunąć Q1/Q3, a co za tym idzie – progi.
- Mieszanka populacji – jeśli w danych masz kilka naturalnych grup z różnymi poziomami zmiennej (np. małe i duże sklepy), to globalny IQR może oznaczyć jako odstające prawie wszystkie rekordy dużych jednostek.
- Dla zmiennych dodatnich o długim ogonie przetestuj logarytmowanie (np.
log1p()) i policz IQR na przetransformowanej skali. - Przy oczywistej heterogeniczności populacji policz IQR osobno w grupach (np. per segment, kraj, typ produktu).
- Dla bardzo małych prób rozważ potraktowanie IQR raczej jako luźnej wskazówki, a nie twardego kryterium filtrowania.
- rozkład jest daleki od normalnego, lub
- odchylenie standardowe jest mocno „rozciągnięte” przez kilka ekstremów.
- dla pojedynczej zmiennej – histogram, gęstość, boxplot,
- dla zależności dwóch zmiennych – scatterplot z kolorowaniem outlierów,
- dla danych czasowych – wykres liniowy z wyróżnionymi punktami.
- pojedyncze czerwone punkty daleko nad główną chmurą – klasyczne ekstremalne transakcje,
- cały „język” punktów o większej liczbie produktów i wyższej wartości – może po prostu inny typ klienta (np. B2B),
- a może czerwone punkty układają się w linię równoległą do głównej – sugerując systematyczny błąd (np. przeskalowanie kwoty).
- winsoryzacja (przycięcie skrajnych wartości do ustalonego percentyla),
- transformacje (np. log, sqrt) zmniejszające wpływ ekstremów,
- zastosowanie metod odpornych (robust regression, mediany zamiast średnich).
- obejrzeć proste wykresy (histogram, boxplot, scatterplot),
- sprawdzić obserwacje o dużym residualu i dźwigni (np. funkcje z pakietu
car), - zastanowić się: czy model opisuje typowe zachowanie, czy głównie garstkę ekstremalnych punktów?
- Zanim zaczniesz szukać outlierów w R, odpowiedz sobie: jaki masz cel – „wyczyścić” dane pod stabilny model, czy lepiej zrozumieć zjawisko wraz z rzadkimi przypadkami?
- Ta sama skrajna obserwacja może oznaczać błąd techniczny, rzadkie, ale prawdziwe zdarzenie albo zmianę procesu biznesowego – kluczowe jest pytanie: szum do usunięcia czy sygnał do zbadania?
- Outliery potrafią mocno zniekształcić średnią, odchylenie standardowe, korelację i regresję liniową, więc przed wyciąganiem wniosków z tych miar sprawdź, czy kilka punktów nie „prowadzi” całej analizy.
- Mediana i metody odporne (robust) bywają znacznie stabilniejsze niż klasyczne statystyki, gdy w danych pojawiają się skrajne wartości – zadaj sobie pytanie, czy potrzebujesz wrażliwości na ekstrema, czy raczej odporności na nie.
- Nie każda rzadka wartość jest błędem: „rarity” to rzadki, ale możliwy przypadek, natomiast „error” łamie wiedzę domenową (np. nierealny wzrost, ujemny czas dostawy) i wymaga korekty lub usunięcia.
- Metody typu IQR i Z-score tylko zaznaczają obserwacje odstające statystycznie; dopiero twoja znajomość kontekstu biznesowego decyduje, czy dany punkt poprawić, usunąć, czy potraktować jako osobny przypadek do analizy.
- Przed wyborem techniki (usuwanie, winsoryzacja, transformacje, statystyki odporne) doprecyzuj: czy nietypowe wartości niosą ryzyko lub szansę biznesową, czy są czysto technicznymi artefaktami, które przeszkadzają w modelowaniu.
Automatyzacja prostych kroków w własnej funkcji
Jeżeli kilka razy z rzędu powtarzasz te same polecenia dla różnych kolumn, opłaca się zbudować mały „pomocnik”. Zastanów się: chcesz mieć jedną funkcję na całą analizę, czy raczej kilka prostych klocków?
Przykład prostej funkcji w R, która liczy podstawowe statystyki i zwraca ramkę danych z wynikami:
opis_zmiennej <- function(x, nazwa = deparse(substitute(x))) {
x <- x[!is.na(x)]
data.frame(
zmienna = nazwa,
n = length(x),
min = min(x),
q1 = quantile(x, 0.25),
mediana = median(x),
q3 = quantile(x, 0.75),
max = max(x),
sd = sd(x),
mad = mad(x)
)
}
opis_zmiennej(dane$wartosc)
Jeżeli potrzebujesz tego samego dla wielu kolumn numerycznych, możesz połączyć to z dplyr::across() lub pakietem purrr:
library(purrr)
num_cols <- dane %>% select(where(is.numeric))
statystyki <- map_df(num_cols, opis_zmiennej)
statystyki
Masz już szkielet pod dalsze kroki: dodanie liczby outlierów z IQR lub Z-score, proporcji odstających, a potem eksport do Excela czy bazy danych.

Metoda IQR w R: teoria, implementacja i pułapki
Intuicja IQR: ile danych mieści się „w środku”?
Interquartile Range (IQR) to odległość między pierwszym a trzecim kwartylem (Q1, Q3). Obejmuje środkowe 50% danych. Pytanie pomocnicze: interesują cię głównie „typowe” obserwacje, czy raczej ekstremalne przypadki?
Klasyczne podejście do outlierów z użyciem IQR stosuje tzw. „regułę 1,5 IQR”:
Wartości poza tym zakresem traktuje się jako obserwacje odstające. W wersji „silniejszej” używa się współczynnika 3 zamiast 1.5, aby łapać tylko najbardziej ekstremalne punkty.
Ręczne liczenie IQR i progów w R
Zanim użyjesz gotowego boxplot, dobrze jest policzyć IQR samodzielnie. To ułatwi kontrolę nad progiem i logiką flagowania.
x <- dane$wartosc
q1 <- quantile(x, 0.25, na.rm = TRUE)
q3 <- quantile(x, 0.75, na.rm = TRUE)
iqr <- IQR(x, na.rm = TRUE)
dolna <- q1 - 1.5 * iqr
gorna <- q3 + 1.5 * iqr
dolna; gorna
Teraz wystarczy zbudować wektor logiczny oraz policzyć, jak duża część danych wypada poza progi:
is_outlier_iqr <- x < dolna | x > gorna
table(is_outlier_iqr, useNA = "ifany")
mean(is_outlier_iqr, na.rm = TRUE)
Jaki odsetek obserwacji „wycina” ci ta reguła? Jeśli to kilka procent – zwykle da się to obronić. Jeśli kilkadziesiąt, to sygnał, że rozkład jest mocno skośny lub reguła 1.5 IQR jest za agresywna.
Implementacja w dplyr: flaga outliera jako nowa kolumna
W praktyce przydaje się mieć informację o outlierach w samej ramce danych. Możesz wtedy filtrować, wizualizować i modelować na tej bazie. Jak to zrobić dla jednej kolumny?
prog_iqr <- function(x, k = 1.5) {
q1 <- quantile(x, 0.25, na.rm = TRUE)
q3 <- quantile(x, 0.75, na.rm = TRUE)
iqr <- q3 - q1
c(dolna = q1 - k * iqr,
gorna = q3 + k * iqr)
}
progi <- prog_iqr(dane$wartosc)
dane <- dane %>%
mutate(
outlier_iqr = wartosc < progi["dolna"] | wartosc > progi["gorna"]
)
Jeżeli chcesz tę samą logikę zastosować do wielu kolumn numerycznych, możesz zautomatyzować to z across():
flaga_iqr <- function(x, k = 1.5) {
q1 <- quantile(x, 0.25, na.rm = TRUE)
q3 <- quantile(x, 0.75, na.rm = TRUE)
iqr <- q3 - q1
dolna <- q1 - k * iqr
gorna <- q3 + k * iqr
x < dolna | x > gorna
}
dane <- dane %>%
mutate(
across(
.cols = where(is.numeric),
.fns = ~ flaga_iqr(.x),
.names = "{.col}_out_iqr"
)
)
Masz teraz dla każdej numerycznej zmiennej kolumnę typu TRUE/FALSE wskazującą potencjalne outliery. Pytanie: chcesz je usunąć, przyciąć czy tylko opisać?
Pułapki metody IQR: skośność, ciężkie ogony i małe próby
Reguła 1.5 IQR jest prosta, ale ma swoje ograniczenia. Gdzie najczęściej się wywraca?
Jak reagować?
IQR w grupach: przykład segmentacji klientów
Jeżeli liczysz np. miesięczny koszyk zakupowy, naturalne jest pytanie: czy łączysz klientów indywidualnych i korporacyjnych, czy jednak rozbijasz ich na grupy?
dane <- dane %>%
group_by(segment_klienta) %>%
mutate(
outlier_iqr_koszyk = flaga_iqr(koszyk_miesieczny)
) %>%
ungroup()
Dzięki temu klient korporacyjny z wyższym koszykiem nie będzie porównywany do detalicznego, tylko do swoich „podobnych”. Od razu zmienia się interpretacja ekstremów.
Winsoryzacja w oparciu o IQR: przycinanie zamiast usuwania
Czasem nie chcesz wyrzucać rekordów, tylko „przyciąć” ekstremalne wartości do progów. Takie podejście nazywa się winsoryzacją. Kiedy może się przydać? Na przykład w modelach, które źle znoszą bardzo duże liczby (regresja liniowa, niektóre algorytmy ML), a jednocześnie liczba outlierów jest spora.
winsor_iqr <- function(x, k = 1.5) {
q1 <- quantile(x, 0.25, na.rm = TRUE)
q3 <- quantile(x, 0.75, na.rm = TRUE)
iqr <- q3 - q1
dolna <- q1 - k * iqr
gorna <- q3 + k * iqr
x[x < dolna] <- dolna
x[x > gorna] <- gorna
x
}
dane <- dane %>%
mutate(
wartosc_winsor = winsor_iqr(wartosc)
)
Zanim zastosujesz winsoryzację, zadaj sobie jedno pytanie: czy ekstremalne wartości są spójne z biznesem? Jeśli to błędne rekordy (np. podwojenie kwoty), lepiej je poprawić lub usunąć niż „ukrywać” przycinaniem.
Metoda Z-score w R: standaryzacja i progi odcięcia
Co mierzy Z-score i kiedy ma sens?
Z-score mówi, o ile odchyleń standardowych dana obserwacja odbiega od średniej. Działa najlepiej, gdy rozkład jest w miarę symetryczny i przypomina normalny. Jak jest u ciebie – widzisz w histogramie dzwon, czy raczej długi ogon?
Definicja jest prosta:
z = (x - mean(x)) / sd(x)
Klasyczny próg odcięcia to |Z| > 3, czasem używa się 2.5 przy większej tolerancji na fałszywe alarmy. W danych idealnie normalnych odsetek obserwacji poza 3 sd jest znikomy, więc wszystko, co tam trafia, traktuje się jako potencjalny outlier.
Liczenie Z-score w R dla jednej zmiennej
Podstawowa standaryzacja w R to funkcja scale(). Zwraca macierz, ale możesz ją łatwo zamienić na wektor:
x <- dane$wartosc
z <- scale(x) # domyślnie (x - mean) / sd
z <- as.numeric(z)
summary(z)
Teraz flaga outliera według Z-score:
prog <- 3 # możesz spróbować 2.5
is_outlier_z <- abs(z) > prog
table(is_outlier_z, useNA = "ifany")
mean(is_outlier_z, na.rm = TRUE)
Jeśli wynik pokazuje, że np. 10–20% danych ma |Z| > 3, to sygnał, że:
Z-score w dplyr i dla wielu kolumn
W codziennej pracy wygodniej jest dodać Z-score jako nową kolumnę w ramce danych. Dzięki temu możesz później łatwo filtrować lub kolorować punkty na wykresach.
dane <- dane %>%
mutate(
wartosc_z = as.numeric(scale(wartosc)),
outlier_z = abs(wartosc_z) > 3
)
Dla wielu zmiennych numerycznych:
standaryzuj <- function(x) as.numeric(scale(x))
dane <- dane %>%
mutate(
across(
.cols = where(is.numeric),
.fns = list(z = ~ standaryzuj(.x)),
.names = "{.col}_{.fn}"
)
) %>%
mutate(
across(
.cols = ends_with("_z"),
.fns = ~ abs(.x) > 3,
.names = "{.col}_out"
)
)
Zastanów się: potrzebujesz jednego globalnego progu (np. 3), czy różne progi dla różnych zmiennych (np. bardziej konserwatywny dla kluczowych metryk ryzyka)?
Robust Z-score: gdy klasyczny Z-score się rozjeżdża
Jeżeli w danych siedzą silne outliery, to średnia i odchylenie standardowe stają się niestabilne. Wtedy klasyczny Z-score może dawać paradoksalne wyniki – im więcej ekstremów, tym bardziej rośnie sd, a Z-score maleje.
Jedno z prostych rozwiązań to użycie robust Z-score, opartego na medianie i MAD:
robust_z <- function(x) {
med <- median(x, na.rm = TRUE)
mad_val <- mad(x, na.rm = TRUE)
(x - med) / (mad_val * 1.4826) # współczynnik dla rozkładu normalnego
}
dane <- dane %>%
mutate(
wartosc_rz = robust_z(wartosc),
outlier_rz = abs(wartosc_rz) > 3
)
Robust Z-score jest szczególnie przydatny przy skośnych, ciężkoogonowych rozkładach, gdzie kilka bardzo dużych wartości nie powinno „psuć” miary rozproszenia.
Z-score w grupach: inne normy dla różnych populacji
Czy każdy klient, produkt albo oddział powinien być oceniany tą samą miarą? Często nie. W wielu zastosowaniach kluczowa jest ocena „odstępstwa od typowego poziomu w danej grupie”.
Przykład: liczba zgłoszeń do helpdesku na pracownika, liczona osobno w każdym dziale:
dane <- dane %>%
group_by(dzial) %>%
mutate(
zgłoszenia_z = as.numeric(scale(liczba_zgloszen)),
zgłoszenia_out = abs(zgłoszenia_z) > 3
) %>%
ungroup()
Dzięki temu dział wsparcia IT z natury wyższą liczbą zgłoszeń nie jest porównywany jeden do jednego z administracją, lecz każdy jest oceniany względem własnej „normy”.
Wizualna diagnostyka outlierów: R jako lupa na dane
Po co rysować, skoro można policzyć?
Masz już flagi IQR i Z-score. Kusi, żeby od razu filtrować dane, ale zanim to zrobisz, zadaj sobie pytanie: czy widzisz te outliery na wykresie?
Prosty rysunek często mówi więcej niż długa tabela. Widzisz, czy outliery tworzą oddzielny „obłok”, czy są częścią płynnej struktury danych. Widzisz też, czy dotyczą konkretnego zakresu wartości, okresu czasu albo jednej grupy.
Jakich wykresów użyjesz jako pierwszych?
Boxplot w R: szybki screening outlierów
Boxplot używa tej samej idei IQR, ale w wersji graficznej. Dla startu wystarczy baza R:
boxplot(dane$wartosc, main = "Boxplot wartosci")
Wersja z ggplot2 daje więcej kontroli:
library(ggplot2)
ggplot(dane, aes(x = "", y = wartosc)) +
geom_boxplot(outlier.colour = "red", outlier.alpha = 0.6) +
labs(x = NULL, y = "Wartosc")
Masz dane z kilkoma kategoriami (np. segment klienta, kraj)? Wtedy boxplot per grupa szybko pokaże, w której „szufladce” siedzi najwięcej ekstremów.
ggplot(dane, aes(x = segment_klienta, y = wartosc)) +
geom_boxplot(outlier.colour = "red", outlier.alpha = 0.5) +
coord_flip() # czasem wygodniej czytać poziomo
Jeżeli widzisz, że prawie każdy boxplot ma „chmurę” czerwonych punktów daleko od pudełka, zapytaj: czy próg outlierów nie jest po prostu zbyt agresywny wobec twojego rozkładu?
Histogram i gęstość: gdzie kończy się „normalne”?
Boxplot jest szybki, ale histogram i krzywa gęstości pokazują kształt rozkładu. Przy skośności lub wielu modach od razu widać, że mechaniczne progi IQR/Z-score będą mylić się częściej.
ggplot(dane, aes(x = wartosc)) +
geom_histogram(bins = 30, fill = "grey70", color = "white") +
labs(x = "Wartosc", y = "Liczba obserwacji")
Jeśli rozkład jest gładki i w miarę symetryczny, kilka punktów w ogonie to naturalni kandydaci na odstające. Jeżeli zamiast tego widzisz dwie wyraźne „górki”, to raczej mieszanka dwóch populacji niż pojedyncze outliery. Pytanie do ciebie: czy nie powinieneś rozdzielić tych populacji wcześniej?
Wersja z gęstością:
ggplot(dane, aes(x = wartosc)) +
geom_density(fill = "steelblue", alpha = 0.4) +
labs(x = "Wartosc", y = "Gestosc")
Dla długiego prawego ogona (np. obroty, czas trwania) spróbuj od razu wersji logarytmicznej:
ggplot(dane, aes(x = wartosc)) +
geom_histogram(bins = 30, fill = "grey70", color = "white") +
scale_x_log10() +
labs(x = "Wartosc (skala log10)", y = "Liczba obserwacji")
Jeśli po logarytmowaniu ogon „cywilizuje się” i przestaje wyglądać jak zbiór błędów, sens mechanicznego usuwania skrajnych wartości mocno spada.
Kolorowanie outlierów na wykresach: IQR i Z-score jako filtry
Masz już w danych kolumny typu outlier_iqr i outlier_z. Wykorzystaj je jak lupę: zobacz, gdzie w przestrzeni zmiennych lądują oznaczone obserwacje.
Przykład: zależność wartości zamówienia od liczby produktów w koszyku:
ggplot(dane, aes(x = liczba_produktow, y = wartosc, color = outlier_iqr)) +
geom_point(alpha = 0.6) +
scale_color_manual(values = c("FALSE" = "grey60", "TRUE" = "red")) +
labs(color = "Outlier IQR")
Co widzisz?
Gdy porównasz kolorowanie według IQR i Z-score, często okaże się, że nie te same punkty są flagowane:
ggplot(dane, aes(x = liczba_produktow, y = wartosc, color = outlier_z)) +
geom_point(alpha = 0.6) +
scale_color_manual(values = c("FALSE" = "grey60", "TRUE" = "blue")) +
labs(color = "Outlier Z-score")
Pytanie diagnostyczne: którą metodę chcesz potraktować jako główną, a którą jako uzupełniającą?
Wykresy czasowe: skoki, które psują trend
Jeżeli pracujesz z danymi w czasie (sprzedaż dzienna, liczba zgłoszeń, obciążenie systemu), odchylenia od trendu są szczególnie ważne. Jedno ekstremum potrafi zmienić parametry modelu prognozującego lub maskować realny wzrost/spadek.
Przykład: dzienna liczba zamówień z zaznaczonymi outlierami Z-score:
ggplot(dane, aes(x = data, y = liczba_zamowien)) +
geom_line(color = "grey60") +
geom_point(aes(color = outlier_z), size = 2) +
scale_color_manual(values = c("FALSE" = NA, "TRUE" = "red")) +
labs(color = "Outlier Z-score")
Czerwone punkty w okolicach świąt lub kampanii marketingowych mogą być „dobrymi” outlierami. Extremum w losowym dniu – sygnał do sprawdzenia logów systemu lub zmian konfiguracji. Co chcesz z nimi zrobić w modelu – wykluczyć, czy zamodelować jako osobny efekt (np. zmienna kampania/święto)?
Dwuwymiarowe outliery: nie tylko „za duże” wartości
Outlier nie musi mieć skrajnej wartości w pojedynczej kolumnie. Może mieć dość zwyczajne marginesy, ale nietypową kombinację (np. niski rabat przy bardzo wysokiej wartości zamówienia).
Prosty scatterplot z kolorowaniem robust Z-score już trochę pomaga:
ggplot(dane, aes(x = rabat_proc, y = wartosc, color = outlier_rz)) +
geom_point(alpha = 0.6) +
scale_color_manual(values = c("FALSE" = "grey70", "TRUE" = "red")) +
labs(color = "Robust outlier")
Jeśli chcesz dodatkowo zobaczyć Z-score na skali, spróbuj facetów:
ggplot(dane, aes(x = rabat_proc, y = wartosc)) +
geom_point(alpha = 0.6) +
facet_wrap(~ outlier_rz) +
labs(title = "Zachowanie punktow robust Z-score: TRUE vs FALSE")
Przy większej liczbie zmiennych możesz zacząć myśleć o podejściu wielowymiarowym (np. odległość Mahalanobisa lub algorytmy klastrowania), ale zanim do tego dojdziesz, podstawowe wykresy 2D i 3D często wystarczą, by wyłapać oczywiste „dziwne kombinacje”.
Porównywanie rozkładów z i bez outlierów
Masz flagę outliera – użyj jej do prostego porównania: jak wygląda rozkład ze wszystkimi obserwacjami, a jak po ich wycięciu lub winsoryzacji?
dane %>%
mutate(out_flag = ifelse(outlier_iqr, "Outlier", "Normalne")) %>%
ggplot(aes(x = wartosc, fill = out_flag)) +
geom_histogram(position = "identity", alpha = 0.5, bins = 30) +
scale_fill_manual(values = c("Normalne" = "grey70", "Outlier" = "red")) +
labs(fill = "Typ obserwacji")
Jeżeli rozkład „normalnych” obserwacji wygląda sensownie i stabilnie, a outliery są wyraźnie odseparowane, możesz śmielej rozważać ich usunięcie lub winsoryzację. Jeśli natomiast wycięcie outlierów radykalnie zmienia kształt rozkładu lub usuwa całe ogony, filtr może być zbyt agresywny.
Możesz też porównać średnią i medianę przed i po:
dane %>%
summarise(
srednia_all = mean(wartosc, na.rm = TRUE),
mediana_all = median(wartosc, na.rm = TRUE),
srednia_clean = mean(wartosc[!outlier_iqr], na.rm = TRUE),
mediana_clean = median(wartosc[!outlier_iqr], na.rm = TRUE)
)
Jak silnie zmienia się średnia? Czy mediana pozostaje stabilna? Jeśli średnia „przeskakuje” o duży procent, a mediana prawie się nie rusza, masz klasyczny przypadek wpływu kilku ekstremów.
Jak outliery zmieniają korelacje i regresje?
Czas na pytanie kluczowe: po co ci ta cała gimnastyka? Najczęściej po to, żeby wnioski z modeli były sensowne. Pojedyncze punkty potrafią wygiąć prostą regresji i mocno zmienić wartości współczynników. Sprawdź to na małym przykładzie.
Najpierw regresja z pełnym zbiorem:
model_full <- lm(wartosc ~ liczba_produktow, data = dane)
summary(model_full)
Teraz wersja po usunięciu obserwacji z ekstremalnym Z-score (lub IQR):
dane_clean <- dane %>%
filter(!outlier_z)
model_clean <- lm(wartosc ~ liczba_produktow, data = dane_clean)
summary(model_clean)
Jak zmienia się nachylenie (liczba_produktow) i współczynnik determinacji R²? Jeśli po filtracji linia regresji jest bliższa „intuicji biznesowej”, a R² rośnie z podejrzanie niskiego do rozsądnego poziomu, outliery prawdopodobnie zaciemniały obraz.
To samo możesz obejrzeć graficznie:
ggplot(dane, aes(x = liczba_produktow, y = wartosc)) +
geom_point(alpha = 0.4) +
geom_smooth(method = "lm", se = FALSE, color = "red") +
geom_smooth(data = dane_clean, method = "lm", se = FALSE, color = "blue") +
labs(
color = NULL,
title = "Regresja z outlierami (czerwony) i bez (niebieski)"
)
Podobny eksperyment zrób dla korelacji:
cor_all <- cor(dane$liczba_produktow, dane$wartosc, use = "complete.obs")
cor_clean <- cor(dane_clean$liczba_produktow, dane_clean$wartosc, use = "complete.obs")
c(cor_all = cor_all, cor_clean = cor_clean)
Jeżeli korelacja zmienia znak lub mocno rośnie/maleje, to sygnał, że kilka ekstremów miało ogromny wpływ na wniosek typu „jest silna/znikoma zależność”. Zastanów się: który wariant bliżej odzwierciedla logikę procesu, który opisują dane?
Wpływ na modele predykcyjne: stabilność vs. uogólnianie
Przy modelach ML decyzja co zrobić z outlierami mocno wpływa na jakość prognoz. Algorytmy liniowe i drzewa często zachowują się inaczej niż np. metody oparte na medianach.
Dla prostoty porównaj jakości regresji liniowej i modelu drzewiastego z i bez outlierów. Załóżmy, że estymujesz wartość zamówienia:
set.seed(123)
# Przykladowy podzial
idx <- sample(c(TRUE, FALSE), size = nrow(dane), replace = TRUE, prob = c(0.7, 0.3))
train <- dane[idx, ]
test <- dane[!idx, ]
train_clean <- train %>% filter(!outlier_iqr)
# Model liniowy
m_lm_full <- lm(wartosc ~ ., data = train)
m_lm_clean <- lm(wartosc ~ ., data = train_clean)
# Prognozy
pred_full <- predict(m_lm_full, newdata = test)
pred_clean <- predict(m_lm_clean, newdata = test)
# Prost y RMSE
rmse <- function(y, yhat) sqrt(mean((y - yhat)^2, na.rm = TRUE))
c(
rmse_full = rmse(test$wartosc, pred_full),
rmse_clean = rmse(test$wartosc, pred_clean)
)
Jeśli RMSE na zbiorze testowym spada po wyczyszczeniu outlierów z treningu, to znaczy, że model lepiej generalizuje do „typowych” przypadków. Z drugiej strony możesz stracić zdolność do sensownej prognozy ekstremalnych scenariuszy. Jakie masz priorytety – dokładność dla większości czy wrażliwość na skrajne ryzyka?
Analogiczny test zrób dla modeli bardziej odpornych na outliery (np. medianowej regresji, niektórych metod drzewiastych z przycinaniem). Często okaże się, że dla nich obróbka outlierów jest mniej krytyczna, ale wciąż wpływa na interpretacje cech.
Outliery a metryki biznesowe: jak bardzo zmieniają się KPI?
Najczęściej zadawane pytania (FAQ)
Jak w praktyce rozpoznać, czy obserwacja odstająca to błąd czy rzadkie, ale prawdziwe zdarzenie?
Zacznij od prostego pytania: czy dana wartość jest w ogóle możliwa fizycznie lub biznesowo? Jeśli widzisz wzrost 300 cm, ujemny czas dostawy czy sprzedaż wyższą niż maksymalna pojemność magazynu, masz mocną przesłankę, że to błąd, a nie rzadki przypadek. Tu pomaga wiedza domenowa: jakie są naturalne granice zjawiska?
Drugi krok: sprawdź kontekst w danych źródłowych. Czy w tym dniu była awaria systemu, ręczna korekta, migracja danych? Jeśli możesz, zajrzyj do surowego logu, porównaj z innymi systemami lub zapytaj zespół biznesowy. Rzadkie, ale prawdziwe zdarzenia zwykle mają sens po krótkiej rozmowie z kimś „z terenu” (np. dział sprzedaży, produkcja), podczas gdy błędy nie bronią się logiką.
Trzeci test: czy wokół tej obserwacji jest „grupka” podobnych punktów, czy stoi zupełnie samotnie? Pojedynczy punkt bez żadnego podobnego sąsiada częściej bywa błędem technicznym, ale jeśli np. w czasie kampanii reklamowej widać kilka bardzo wysokich wyników, to raczej realna zmiana procesu niż jeden błąd w bazie.
Czy zawsze trzeba usuwać obserwacje odstające przed modelowaniem w R?
Najpierw zapytaj siebie: jaki masz cel? Jeśli budujesz model predykcyjny do codziennych prognoz (np. typowa dzienna sprzedaż, standardowy czas dostawy), ekstremalne przypadki często psują jakość modelu i sensownie jest je ograniczyć, przetransformować lub użyć metod odpornych. Gdy jednak analizujesz ryzyko, awarie, fraudy czy incydenty bezpieczeństwa – właśnie te „ogonowe” przypadki są kluczowym sygnałem i usuwanie ich nie ma sensu.
W praktyce często lepiej niż „twarde” kasowanie działa:
Jeśli nie jesteś pewien, przygotuj dwa warianty modelu w R: z outlierami i po ich obróbce. Porównaj wyniki i odpowiedz sobie: który model lepiej wspiera decyzje, które chcesz podjąć?
Kiedy lepiej użyć IQR, a kiedy Z-score do wykrywania outlierów w R?
Podstawowe pytanie brzmi: jaki rozkład ma Twoja zmienna? Jeśli dane są w miarę symetryczne i zbliżone do normalnych, Z-score (np. próg |Z| > 3) jest intuicyjny, bo opiera się na średniej i odchyleniu standardowym. Gdy jednak rozkład jest silnie skośny, ma długi ogon (np. przychody, liczba transakcji), klasyczny Z-score potrafi oznaczyć spory kawałek „normalnych” danych jako odstający.
IQR (przedziały oparte na kwartylach) jest bardziej odporny na skrajne wartości i często lepiej sprawdza się przy rozkładach nienormalnych lub z outlierami już na starcie. Typowa reguła to: wartości poniżej Q1 − 1.5*IQR lub powyżej Q3 + 1.5*IQR traktujesz jako kandydatów na outliery.
Jeśli widzisz silną skośność, rozważ dwa kroki: transformacja (np. log(x + 1)) i dopiero potem Z-score, albo od razu metody oparte na medianie (MAD, robust Z-score). Zadaj sobie pytanie: czy chcesz precyzyjnie złapać „prawdziwe ekstrema”, czy raczej wstępnie przefiltrować oczywiste odstępstwa?
Jak outliery wpływają na średnią, korelację i regresję w R w praktycznych analizach?
Jedno skrajne „fiasko” lub „sukces” potrafi wystrzelić średnią w górę lub w dół tak, że przestaje ona reprezentować typowy przypadek. Jeśli opierasz decyzje biznesowe na średniej (np. średnia wartość koszyka, średni czas obsługi), zadaj sobie pytanie: czy ta liczba opisuje „zwykłego” klienta, czy kilku ekstremalnych?
W korelacji Pearsona jeden ekstremalny punkt może sztucznie „napompować” albo zabić współczynnik. Na wykresie rozrzutu często widać to gołym okiem: wszystkie punkty tworzą chmurę bez wyraźnej zależności, ale jeden skrajny punkt „ciągnie” prostą korelacji w konkretną stronę. Podobnie w regresji: punkty o dużej dźwigni (leverage) mogą ustawić nachylenie prostej, zmienić istotność współczynników i wywołać złudzenie dobrze dopasowanego modelu.
Dlatego przed interpretacją średnich, korelacji i regresji w R warto:
Jak podejść do obserwacji odstających w danych sprzedażowych lub z eksperymentów A/B?
W sprzedaży najpierw rozróżnij kilka typów ekstremów: ogromne zamówienia korporacyjne, jednorazowe akcje promocyjne, błędy importu (np. złe jednostki, duplikaty). Zadaj sobie pytanie: czy analizujesz codzienny retail, czy chcesz uwzględnić także „grube ryby” i kampanie? Dla prognoz typowej sprzedaży zazwyczaj izolujesz kampanie i nietypowe okresy (osobna zmienna, filtr czasowy), a błędy techniczne po prostu czyścisz lub poprawiasz.
W eksperymentach A/B warto sprawdzić, czy outliery są równomiernie rozłożone pomiędzy grupą kontrolną i eksperymentalną. Jeśli większość z nich leży w jednej grupie, zadaj kluczowe pytanie: to realny efekt zmiany, czy np. problem z implementacją wariantu na części użytkowników? Przy pojedynczych skrajnych wynikach sprawdź logi: czy uczestnik działał zgodnie z protokołem, czy urządzenie nie zgubiło kalibracji.
Dobrym nawykiem jest zrobienie dwóch analiz: pełnej oraz po ustalonych zasadach obróbki outlierów (np. przycięcie do 99 percentyla). Jeśli wnioski są podobne, możesz spać spokojniej. Jeśli radykalnie się różnią, zatrzymaj się i zapytaj: które z tych wniosków ma sens biznesowy i metodologiczny?






