Wykresy funkcyjne – jak narysować „normalny” wykres
Transkrypt
Wykresy funkcyjne – jak narysować „normalny” wykres
Raportowanie Wykresy funkcyjne – jak narysować „normalny” wykres Krzysztof Kusch Predictive Solutions Powracamy, po krótkiej przerwie, do tematu tworzenia wykresów w oparciu o język GPL. W tym materiale zaprezentowana zostanie metoda pozwalająca na tworzenie wykresów mających przedstawiać przebieg funkcji. Będzie się ona opierała o metodę „tworzenia” danych w obrębie kodu GPL. Istnieje również druga metoda, co prawda moim zdaniem mniej przyjazna użytkownikowi, ale za to rozszerzająca możliwości programu, która opiera się na wygenerowaniu technicznego pliku danych, na podstawie którego stworzony zostanie wykres. Możliwości jej zastosowania były już prezentowane w materiale z [9. numeru] ]EKSPRESSu], dlatego teraz skupimy się jedynie na możliwościach, jakie daje kod GPL w tym zakresie. Do czego dążymy? W dalszych częściach materiału zaprezentuję, w jaki sposób stworzyć wykres w oparciu o równanie funkcji. Jako przykład wybrałem jedną z ulubionych funkcji w dziedzinie analizy danych – funkcję gęstości prawdopodobieństwa rozkładu normalnego. Na początku przełożymy tę funkcję na język GPL, a następnie stworzymy dla niej wykresy. Aby trochę urozmaicić tę ciężką pracę, dodamy do tych grafik kilka elementów, które sprawią, że wykres będzie miał większą wartość informacyjną. Do dzieła! Podstawowe elementy kodu Inaczej niż to miało miejsce w poprzednich artykułach o języku GPL, tym razem nie będziemy korzystać z Kreatora wykresów w celu stworzenia kodu będącego punktem wyjścia do dalszych modyfikacji. Są dwa powody takiego stanu rzeczy: po pierwsze, kod pozwalający nam stworzyć podstawowy wykres jest bardzo prosty – nie ma więc powodu, aby wyręczać się programem. Po drugie, w Kreatorze wykresów nie istnieje możliwość wygenerowania wykresu bez posiadania odpowiednich zmiennych w zbiorze danych – my natomiast nie będziemy korzystać w tej chwili z realnie występujących zmiennych. Przed wywołaniem bloku kodu GPL w programie IBM SPSS Statistics potrzebne jest umieszczenie funkcji GGRAPH pozwalającej określić sposób wywołania kodu, zakres zmiennych, traktowanie braków danych, szablon itd. Mimo, iż nie będziemy odnosić się do żadnego realnego zbioru danych, wciąż musimy z tej funkcji skorzystać, aby uruchomić w Statistics kod GPL. W obrębie tej funkcji musimy jeszcze powiedzieć programowi, że specyfikacja wykresu (tzn. blok kodu GPL) znajduje się bezpośrednio po funkcji (SOURCE = INLINE): GGRAPH /GRAPHSPEC SOURCE=INLINE. Po funkcji GGRAPH może znaleźć się już klamra BEGIN GPL – END GPL, w której umieścimy kod GPL pozwalający programowi narysować żądany wykres. W dalszych częściach będziemy omawiać już tylko kod GPL, pamiętając o tym, że jest on umieszczony w obrębie następującego kodu Syntax: GGRAPH /GRAPHSPEC SOURCE=INLINE. BEGIN GPL ... END GPL. 2 Wykresy funkcyjne – jak narysować „normalny” wykres Dziedzina funkcji W pierwszym kroku stworzymy zmienną, której wartości będą podzbiorem dziedziny funkcji, dla którego chcemy stworzyć wykres. Patrząc na ten krok z perspektywy wykresu – chcemy teraz stworzyć zmienną, którą umieścimy na osi x, i do wartości której będziemy przyporządkowywać zgodnie z równaniem funkcji wartości zmiennej, która znajdzie się na osi y. Jeśli chcemy zaprezentować na wykresie funkcję gęstości prawdopodobieństwa klasycznego rozkładu normalnego (o średniej równej 0 i odchyleniu standardowym równym 1) wydaje się być rozsądne uwzględnienie przedziału, który będzie symetryczny względem zera. Ponieważ 99% wartości znajduje się w obrębie +/−3 odchylenia standardowe od średniej, przyjmiemy na potrzeby naszego przykładu, że wartości zmiennej x na naszym wykresie będą zaczynać się od −3 do 3. Oczywiście w zależności od potrzeb możemy zdefiniować inny przedział. W języku GPL do tworzenia nowych zmiennych o wartościach pochodzących z określonego przedziału służy funkcja iter, którą wywołuje się deklaracją DATA. Posiada ona następującą składnię: iter(<od>, <do>, <krok>) gdzie od oznacza pierwszą wartość, która zostanie wprowadzona, do oznacza największą wartość, która może być wprowadzona do nowej kolumny, a krok oznacza liczbę, o którą każda kolejna wartość zmiennej będzie powiększana, aż nie zostanie osiągnięta wartość do lub najbliższa wartość znajdująca się w zdefiniowanym przedziale. Przykładowo, określając wartość od jako 1, wartość do jako 10, a krok jako 1, otrzymamy zmienną zawierająca następujące wartości: 1,2,3,4,5,6,7,8,9,10 Inaczej stanie się, gdy zdefiniujemy te same wartości od i do, ale wartość parametru krok zmienimy na 2. Wtedy zmienna będzie składała się z wartości 1,3,5,7,9. Wartość parametru do (10) nie zostanie tu umieszczona, gdyż nie może ona zostać obliczona w oparciu o poprzednią wartość (9+2=11). W pierwszym akapicie tej części zdecydowaliśmy już, że krańcami przedziału, dla którego będziemy prezentować wykres, są wartości −3 i 3. Odpowiadają one kolejno parametrom od i do w funkcji iter(), brakuje nam zatem tylko ostatniego parametru funkcji. Na poniższych wykresach zaprezentowano, jak wygląda wykres stworzony w oparciu o cztery wartości parametru krok: 1, 0,1, 0,01, 0,001. 3 Wykresy funkcyjne – jak narysować „normalny” wykres Widać, że tylko pierwszy wykres został wygenerowany w oparciu o zdecydowanie za małą liczbę punktów (mianowicie 7). Pozostałe wykresy nie różnią się wyglądem od siebie, co może prowadzić do wniosku, że wygenerowanie tego wykresu w oparciu o 70 punktów (krok = 0,1) będzie właściwą decyzją. W praktyce wszystko zależy z jednej strony od tego, jak duży wykres chcemy wygenerować, a z drugiej, jak mocnym komputerem dysponujemy – musimy wszak pamiętać, że większa liczba wartości wygenerowanych przez funkcję iter(), to większa liczba punktów do nakreślenia na wykresie, co czasami może znacznie wydłużyć czas renderowania wykresu. Ponieważ wygenerowanie wykresu w oparciu o 700 wartości nie powinno stanowić żadnego wyzwania dla naszego komputera, zdefiniujmy parametr kroku jako 0,01. Pierwsza linia kodu GPL będzie zatem wyglądać następująco: DATA: x = iter(-3,3,0.01) Znana nam z poprzednich materiałów deklaracja DATA pozwala zdefiniować zmienną, która będzie używana w kodzie GPL. W przeszłości służyła nam ona jednak do odwoływania się do istniejących w zbiorze danych zmiennych (funkcja col()). Teraz, za pomocą funkcji iter() tworzymy nową zmienną o nazwie x. Równanie funkcji gęstości prawdopodobieństwa rozkładu normalnego Skoro posiadamy już linię kodu tworzącą zmienną, którą umieścimy na osi x, czas teraz zająć się drugą potrzebną nam zmienną – tą, która zostanie umieszczona na osi y. Zadanie tym razem nie jest tak proste, jak to miało miejsce w przypadku poprzedniej zmiennej. W tym wypadku będziemy musieli wyliczyć, w oparciu o istniejące już wartości zmiennej x, wartości zmiennej y korzystając z następującego wzoru: Gdzie µ to średnia (w naszym przykładzie jest ona równa 0), a σ to odchylenie standardowe. Niestety, aby móc użyć tej formuły w kodzie, musimy ją przepisać używając odpowiednich funkcji i operatorów matematycznych. Określmy najpierw, jakie działania musimy wykonać obliczając wartość zmiennej y, a następnie zobaczmy, które mogą nam sprawić problem. W powyższym równaniu mamy zatem mnożenie (w GPL zapisywane znakiem *), dzielenie (znak/), odejmowanie (-), ale również pierwiastkowanie, podnoszenie do potęgi i obliczanie liczby eulera podniesionej do potęgi zdefiniowanej przez wyrażenie (exp). Zauważmy również, że mamy jedną stałą (liczbę pi) i dwa parametry (µ i σ). W języku GPL, aby obliczyć pierwiastek kwadratowy wyrażenia, należy umieścić je w obrębie funkcji sqrt (ang. square root). Potęgowanie jest w GPL zapisywane w ten sam sposób, co w IBM SPSS Statistics i wyraża się podwójną gwiazdką (**). Pewnym ułatwieniem dla nas jest to, że w GPL, w przeciwieństwie do Statistics, występuje liczba pi, do której można się odwołać po prostu poprzez wpisanie pi. Obliczanie eksponentu wyrażenia jest możliwe poprzez umieszczenie go w nawiasach funkcji exp(). Po przekształceniu i zastosowaniu powyższych funkcji powyższe wyrażenie przyjmie przedstawioną poniżej formę. 1/(s*sqrt(2*pi)))*exp(−1*(((x − m)**2)/(2*(s**2))) Powinniśmy pamiętać, że parametry m i s, w naszym przykładzie wynoszą odpowiednio 0 i 1 – podstawmy te wartości w odpowiednie miejsca, aby uzyskać finalną formę wzoru: 1/(1*sqrt (2*pi)))*exp (−1* (((x − 0)**2)/(2* (1**2))) Posiadamy już formułę, w oparciu o którą możliwe będzie wyliczanie wartości zmiennej y. Nie wiemy jednak, jakich funkcji należy użyć w języku GPL, aby wyliczyć te wartości. Zajmiemy się teraz tą kwestią. Po pierwsze, nie będziemy w tym miejscu korzystać z deklaracji DATA. Do tworzenia nowej zmiennej, której wartości są wynikiem przekształceń, służy deklaracja TRANS. W jej obrębie możemy skorzystać z bardzo przydatnej funkcji eval(), która dla każdej obserwacji w zbiorze oblicza wartość wynikową w oparciu o zdefiniowane w niej wyrażenie matematyczne lub logiczne. Przykładowo, korzystając z tych dwóch elementów kodu możemy w definicji wykresu obliczyć różnicę dwóch zmiennych – np. pierwotnego wynagrodzenia pracownika i aktualnego wynagrodzenia: 4 Wykresy funkcyjne – jak narysować „normalny” wykres TRANS: roznica = eval (wynagr_aktual – wynagr_pierw) Jak wspomniałem, funkcja eval() może być również wykorzystywana do tworzenia zmiennych prawda-fałsz, które zwracają wartość prawda w sytuacji, gdy spełniony jest warunek logiczny umieszczony w obrębie wspomnianej funkcji. Przykładowo, zmienna dychotomiczna flagująca pracowników zarabiających aktualnie powyżej 3000: TRANS: akt3000 = eval (wynagr_aktual>3000) W naszym przypadku chcemy, by na podstawie „przetłumaczonego” na język GPL wzoru funkcji gęstości prawdopodobieństwa każdej wartości zmiennej x została przyporządkowana wartość zmiennej y. W tym wypadku wystarczy umieścić omawiane wyrażenie w nawiasach funkcji eval: TRANS: y = eval ((1/(1*sqrt (2*pi)))*exp (-1* (((x – 0)**2)/(2* (1**2))))) Teraz brakuje nam już tylko jednej linii kodu, by stworzyć nasz pierwszy z omawianych w tym materiale wykresów. Jest to oczywiście linia deklaracji ELEMENT, dzięki której zdefiniujemy obiekt graficzny, który będzie reprezentował dane i sposób łączenia ze sobą poszczególnych wartości zmiennych (algebrę). Obiektem graficznym ma być (jak się domyślamy) linia, a algebra umieszczona w funkcji postion () sprowadza się do skrzyżowania (operator *) ze sobą zmiennej x i y: ELEMENT: line (position (x*y)) Całość kodu GPL (wraz z pozwalającym go wywołać Syntaxem IBM SPSS) została zaprezentowana poniżej: GGRAPH /GRAPHSPEC SOURCE=INLINE. BEGIN GPL DATA: x = iter(-3,3,0.01) TRANS: y = eval((1/(1*sqrt(2*pi)))*exp(-1*(((x - 0)**2)/(2*(1**2))) )) ELEMENT: line(position(x*y)) END GPL. Rozkład normalny – wykres warstwowy W poprzedniej części materiału udało nam się stworzyć liniowy wykres dla funkcji gęstości prawdopodobieństwa rozkładu normalnego bez korzystania ze zbioru danych. Teraz pójdziemy trochę dalej i stworzymy wykres, który będzie przedstawiał obszar pod krzywą rozkładu i będzie on kolorowany w zależności od ustawionego progu, np. 1,96 odchylenia standardowego. W omawianym powyżej kodzie GPL musimy wprowadzić łączenie trzy zmiany, aby uzyskać taki efekt. Po pierwsze, musimy stworzyć w kodzie GPL nową zmienną, która będzie dzielić obszar pod krzywą na taki, gdzie wartości rozkładu mieszczą się w zadeklaro- 5 Wykresy funkcyjne – jak narysować „normalny” wykres wanym przedziale wokół średniej i takie, które są poza tym przedziałem. Po drugie, musimy zmienić obiekt graficzny, który ma reprezentować dane. Po trzecie, musimy zaimplementować nowo wyliczoną zmienną do deklaracji ELEMENT, dzięki czemu będzie widoczny jej wpływ na wykresie. Zmiana pierwsza – nowa zmienna Nie bez powodu omawiając funkcję eval () wspomniałem o możliwości tworzenia z jej pomocą zmiennych prawda-fałsz. W tym miejscu skorzystamy właśnie z tej funkcjonalności. Zacznijmy od szkieletu – nową zmienną nazwijmy color: TRANS: color = eval () Teraz skonstruujemy wyrażenie, które zostanie umieszczone w nawiasach funkcji eval. Załóżmy, że chcemy oznaczyć obszar pod krzywą w odległości ±1,96 odchylenia standardowego od średniej. Ponieważ wartości oddalone o 1,96 odchylenia standardowego mogą występować z zarówno wśród wartości dodatnich jak i ujemnych, skorzystamy z funkcji abs() zwracającej wartość bezwzględną: abs (x) >1 Druga część wyrażenia będzie korzystała z konstrukcji „to … inaczej …”, którą w GPL zapisuje się w postaci „? …: …”, wpisując w miejsce wielokropków w cudzysłowach nazwy generowanych kategorii. Nasze wyrażenie może zatem przyjąć formę: abs (x) >1? "<= +/-1,96σ": "> +/-1,96σ" Cała deklaracja TRANS będzie po dołączeniu tego wyrażenia wyglądała następująco: TRANS: color = eval (abs (x) >1? "<= +/-1,96σ": "> +/-1,96σ") Zmiana 2 – zmiana obiektu graficznego Na poprzednim wykresie rysowanym obiektem graficznym była linia. Teraz, skoro chcemy by obszar pod krzywą był zaznaczony, musimy skorzystać z innego obiektu – jest nim obiekt o nazwie area, który pozwala tworzyć między innymi wykresy warstwowe. Zmiana następuje tylko w deklaracji ELEMENT i ogranicza się jedynie do podmienienia słowa line na area: ELEMENT: area (position (x*y)) Zmiana 3 – dołączenie zmiennej color do deklaracji ELEMENT Ostatnią zmianą, którą dokonamy jest dodanie funkcji color(), która wskazuje, że rysowany na wykresie obiekt graficzny będzie różnicowany kolorem. W jej obrębie umieścimy stworzoną specjalnie w tym celu zmienną color, co spowoduje, że obszar pod krzywą będzie różnił się kolorem w zależności od przyjmowanych przez tę zmienną wartości: ELEMENT: area (position (x*y), color (color)) Po zmianach – gotowy kod i wygenerowany wykres Po wprowadzeniu opisanych powyżej zmian stajemy się już posiadaczami gotowego kodu GPL, który pozwoli wygenerować żądany wykres. Całość kodu wygląda teraz następująco: GGRAPH /GRAPHSPEC SOURCE=INLINE. BEGIN GPL DATA: x = iter(-3,3,0.01) TRANS: y = eval((1/(1*sqrt(2*pi)))*exp(-1*(((x - 0)**2)/(2*(1**2))) )) TRANS: color = eval(abs(x)>1.96 ? "<= +/-1,96σ" : "> +/-1,96σ") ELEMENT: area(position(x*y), color(color)) END GPL. 6 Wykresy funkcyjne – jak narysować „normalny” wykres Wykres stworzony za jego pomocą wygląda następująco: Zamiast zakończenia – co dalej? W tym artykule chciałem przede wszystkim pokazać, w jaki sposób można w języku GPL korzystać z funkcji iter() i eval(). Udało nam się, niejako przy okazji, stworzyć wykresy, które mogą się przydać przy tworzeniu różnych materiałów (np. szkoleniowych). Mogą one również służyć za bazę do dalszych eksperymentów i mogą być dalej rozszerzane o dodatkowe informacje. Przykładowo, możemy chcieć dodać do wykresu linię informującą o tym, jaki przedział został zaznaczony: Kod, którym posłużyłem się do wygenerowania go prezentuję poniżej: GGRAPH /GRAPHSPEC SOURCE=INLINE. BEGIN GPL DATA: x = iter(-3,3,0.01) DATA: z = iter(-1.96,1.96,0.01) TRANS: y = eval((1/(1*sqrt(2*pi)))*exp(-1*(((x - 0)**2)/(2*(1**2))) )) TRANS: t = eval((1/(1*sqrt(2*pi)))*exp(-1*(((1.96)**2)/(2*(1**2))))) TRANS: color = eval(abs(x)>1.96 ? "<= +/-1,96σ" : "> +/-1,96σ") GUIDE: legend(aesthetic(aesthetic.color.interior), null()) ELEMENT: area(position(x*y), color(color)) ELEMENT: line(position(z*t), color(color.white), label("-1,96σ <--(95%)--> 1,96σ")) END GPL. Wykresy funkcyjne – jak narysować „normalny” wykres 7 Nie zawsze uda nam się wygenerować wykres bez odwoływania się do pliku danych (nawet takiego, który jest sztucznie wygenerowany). Przykładowo, aby wygenerować poniższy wykres musiałem najpierw wygenerować zbiór danych w IBM SPSS Statistics, właściwie w identyczny sposób, jaki był omawiany w tym artykule w odniesieniu do kodu GPL. Nie da się jednak ukryć, że możliwość tworzenia zmiennych w obrębie kodu GPL jest bardzo użyteczną funkcjonalnością i wydaje się ona być zdecydowanie bardziej „poręczna” niż konieczność generowania sztucznego zbioru danych do każdego wykresu funkcyjnego, który chcielibyśmy stworzyć. Krzysztof Kusch Zagadnienia wizualizacji informacji z użyciem wykresów omawiane są na [szkoleniu ST2a] Predictive Solutions ul. Racławicka 58 · 30-017 Kraków tel. 12 636 96 80 · faks wew. 102 e-mail [[email protected]] [www.predictivesolutions.pl] Absolwent socjologii Akademii Górniczo-Hutniczej w Krakowie i Uniwersytetu Jagiellońskiego o specjalizacji Badania społeczne i analiza danych. W Predictive Solutions [dawniej SPSS Polska] pracuje od 2010 roku na stanowisku konsultant ds. rozwiązań analitycznych i data mining, gdzie skupia się szczególnie na rozwiązaniach analitycznych dla sektora bankowego, ubezpieczeniowego i farmaceutycznego (CRM analityczny, prognozowanie sprzedaży), a także na kwestiach związanych z wizualizacją danych i raportowaniem. Prowadzi szkolenia z zakresu statystycznej analizy danych.