import warnings
warnings.filterwarnings('ignore')
import os
import sqlite3
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import pickle
Wizualizacja danych geoprzestrzenych w Pythonie obejmuje co najmniej dwa zagadnienia:
Informacja nieprzestrzenna przechowywana jest - tak jak w innych systemach obliczeniowych w formie serii danych (jednego typu) lub zestawu serii jednakowych długości (ramki danych - dataFrame). Przechowywanie, zarządzanie i import danych zostało omówione w poprzedniej części dotyczącej biblioteki pandas, w tej części zajmiemy się narzędziem dedykowanym wizualizacji danych nieprzestrzennych - seaborn. Jest to biblioteka zbudowana na podstawie matplotlib i w założeniu umożliwia szybkie tworzenie zaawansowanych wykresów o dużej swobodzie modyfikacji. Wykresy seaborn są wykresami matplotlib w związku z tym w przypadku braku określonej funkcjonalności w pakiecie seaborn może być uzupełniona funkcjami matplotlib, umożliwiając tworzenie bardzo zaawansowanych grafik.
Alternatywy
Podstawą pracy pakietu seaborn jest biblioteka pandas. Wiele funkcji seaborn wymaga danych przechowywanych w postaci pandas.DataFrame
i wymaga jawnego skonwertowania innych typów danych -- głównie numpy.array
do formatu pandas. Ponieważ dane przeznaczone do wizualizacji często wymagają wcześniejszej zaawansowanej obróbki (wczytania danych, zamiany, konwersji typów itp) warto takie dane zapisać bezpośrednio w formacie pandas. Służy do tego poznany już uniwersalny format zapisu danych pickle, który zachowuje strukturę wewnętrzną danych.
Do załadowania gotowych do użycia danych (przygotowanych w poprzedniej części służy polecenie picke.load()
wymagające jako źródła otwarcia wcześniej zapisanego pliku
os.chdir('/home/jarekj/Dropbox/dydaktyka/programowanie4/powiatData')
dt = pickle.load(open("dt.p","rb+"))
dt.head()
Dane kategoryzowane, to takie, które gdzie dla każdego rekordu (wiersza) wartość zmiennej jest określona przynależnością do pewnej (nieunikalnej) kategorii. W naszym zbiorze danych taką funkcję pełni zmienna "woj(ewództwo)", która wskazuje na terenie jakiego województwa leży dany powiat. Pojedyncza zmienna kategoryzowana może być przedstawiona wyłącznie w postaci liczności/udziału danej kategorii względem całej zmiennej. Do prezentacji takiej zmiennej służy wykres countplot()
, który wyświetla w postaci słupka ilość wystąpień danej kategorii w zmiennej.
Od wersji 0.9 seaborn wprowadził uogólnione typy wykresów: catplot i relplot. Wykresy domyślnie używają najlepszej opcji dla typu danych lub wymagają podania typu wykresu jawnie. Na przykład: catplot(..., kind="count")
jest równoważne: countplot(...)
. Wykresy ogólne, mimo że wygodne mają swoje ograniczenia które zostaną wskazane w dalszych częściach kursu.
sns.countplot(x="woj",data=dt)
Jednym z największych problemów grafiki prezentacyjnej jest to że etykiety często nie mieszczą się w obszarze przeznaczonym dla etykiety przez twórcę programu. W takiej sytuacji możemy:
Seaborn pozwala na wybór orientacji wykresu. Jeżeli spodziewamy się że etykiety są za długie i będą się nakładały można obrócić wykres do orientacji poziomej. Należy wtedy dodatkowo zmienić funkcję osi x z y. Niestety nie ma możliwości sterowania szerokością i odległoscią słupków.
sns.countplot(y="woj",data=dt,orient="h")
Jeżeli zależy nam na układzie pionowym można skrócić lub obrócić etykiety. Aby skrócić etykietę do określonej liczny znaków należy zastosować metodę polegającą na potraktowaniu napisu jako krotki i wybraniu z niego określonego fragmentu poprzez indeksowanie: napis[:10] zwróci nam pierwsze 10 znaków. Można więc zastosować metodę dla każdego rekordu w dataFrame poprzez metodę df.apply
. Metoda apply wymaga podania funkcji, która zostanie zastosowana, ponieważ takiej funkcji nie ma trzeba ją stworzyć tymczasowo poprzez poznany wcześniej operator lambda
umożliwiający tworzenie funkcji anonimowych. lambda x: x[:10]
utworzy funkcję zwracającą 10 pierwszych elementów listy/krotki/tektu.
Do obrócenia etykiet służy funkcja matplotlib definiująca właściwości etykiet (ticks), gdzie ustawiamy dla etykiet x rotację na 90 stopni.
dtl = dt.assign(wojewodztwo=dt.woj.apply(lambda x: x[:10]))
sns.countplot(x="wojewodztwo",data=dtl)
plt.xticks(rotation=90)
Zmienna numeryczna to taka zmienna, która przybiera dowolne wartości albo z całej dziedziny liczb rzeczywistych albo z ich pewnego przedziału. Do prezentacji tych zmiennych służą wykresy prezentujące rozkład distplot()
najczęściej w postaci histogramu, lub rozkład w postaci ciągłej: kdeplot()
(kernel density estimation). Do opisu gęstości można też użyć wykresu kreskowego rugplot()
, który najczęściej jest łączony z innymi wykresami. Jako przykład przedstawimy standardowy wykres rozkładu z pocją rug, który łączy w jednym wykresie histogram, odpowiadający mu wykres kde oraz rugplot ilustrujący gęstość poszczególnych wystąpień:
sns.distplot(dt['przyrost'],rug=True)
Wyresy gęstościowe kde mają kilka dodatkowych parametów pozwalających sterować wyglądem krzywej gęstości: typ kernela i szerokość pasma wygładzania (bandwith - bw)
import statsmodels
sns.kdeplot(dt['przyrost'],bw=0.2, kernel='tri') #kerenl density with bandwith and triangle kernel
Wykresy rozkładu można modyfikować ilością skalą przedziałów, można też dopasować do wybranych rozkładów. Tu użyjemy rozkładu normalnego. Rozkład trzeba wcześniej zaimportować z biblioteki scpipy
from scipy.stats import norm
sns.distplot(dt['przyrost'],bins=20, fit=norm,kde=False) # z dopasowaniem do rozkładu normalnego
Zmienne złożone to takie zmienne, których udział sumuje się do jedności (właściwa definicja to taka, że przynajmniej jeden ze składników jest negatywnie skorelowany z pozostałymi). Przyjmując definicję uproszczoną najprostrzą formą wykresu dla zmiennej złożonej jest wykres stertowy (stacked bar plot), niestety z powodów zrozumiałych wyłącznie dla siebie twórcy pakietu odmawiają wprowadzenia tego wykresu do biblioteki seaborn. Z tego powodu przygotowanie tego wykresu będzie nieco bardziej skomplikowane i będzie wymagało przeliczenia danych w obrębie dataFrame oraz wykorzystanie właściwości seaborn, pozwalające nakładać na siebie kolejne warstwy wykresu. W ten sposób omówimy wybrane mechanizmy związane z tworzeniem złożonych wykresów, których nie ma w bazie szablonów.
Do zilustrowania zadania użyjemy fragment dataFrame zawierający dane na temat ilości osób w wieku przedprodukcyjnym, (aprod) produkcyjnym i poprodukcyjnym (pprod)
Modelowanie danych. W na tym etapie musimy:
*
100, (B) produkcyjne (pprod+prod)/sum i dla poprodukcyjnej (C) 100 bo jest to jednocześnie suma wszystkich mieszkańców (100%).dtg = dt[["aprod","prod","pprod","woj"]].groupby("woj").sum()
dtg['sum'] = dtg['aprod']+dtg['prod']+dtg['pprod']
dtg['saprod'] = dtg['aprod']/dtg['sum']*100
dtg['sprod'] = (dtg['aprod']+dtg['prod'])/dtg['sum']*100
dtg['spprod'] = 100
Tworzenie wykresu Na tym etapie:
Wykres jest gotowy. Niestety jak poprzednio nie możemy modyfikować szerokości słupków.
sns.barplot(y=dtg.index, x="spprod",data=dtg,ci=None,color="red",orient="h")
sns.barplot(y=dtg.index, x="sprod",data=dtg,ci=None,color="yellow",orient="h")
bar=sns.barplot(y=dtg.index, x="saprod",data=dtg,ci=None,color="green",orient="h")
bar.set_xlabel("grupy wiekowe")
Najprostrzym porównaniem jest wykres porównujący rozproszenie wartości dla pary zmiennych, gdzie wartości każdej ze zmiennych przedstawiane są na odpowiednich osiach. Dodatkowo do poszczególnych zmiennych można dodać atrybut przedstawiający kolejną zmienną, zarówno numeryczną jak i kategoryzowaną. Wykresy tego typu, określane są jako scatterplot()
, seaborn używa dla nich nazwy relplot()
.
Jako przykład przedstawimy porównanie dwóch zmiennych: ilości małżeństw i przyrostu naturalnego:
sns.relplot(x="malzenstwa",y="przyrost",data=dt)
Dodatkowo, aby wykryć czy zróżnicowanie posiada strukturę przestrzenną do zróżnicowania obiektów użyjemy informacji na temat województwa. Kolor jest definiowany parametrem hue.
sns.relplot(x="malzenstwa",y="przyrost",hue="woj",data=dt)
Kolor może też przedstawiać zmienną numeryczną. Użyjemy tu wskaźnika feminizacji: czyli proporcji między kobietami i mężczyznami, który jednak trzeba wyliczyć w locie i dodać do dataFrame. Dodatkowo, dodamy parametr alpha, który nada przeźroczystość wyświetlanym punktom lepiej ilustrując rzeczywistą informację o relacji osób w wieku przed i poprodukcyjnym i wskaźnika feminizacji. Dodatkowo w celu wyeliminowania Warszawy zmniejszymy zasięg obu osi, tak aby wyeksponować zróżnicowanie w pozostałych powiatach.
sns.relplot(x="pprod",y="aprod",hue="fem",data=dt.assign(fem=dt.k/dt.m),alpha=0.5)
plt.ylim(0, 100000)
plt.xlim(0, 100000)
Wykresy pozwalające porównać wzajemnie wszystkie zmienne są jednym z najważniejszych narzędzi eksploracji danych. Pozawalają wizualnie wykryć zmienne skorelowane, skupienia w danych, czy inne subtelne zależności. Wykresy te określane jako porównawcze (pairplot) składają się z macierzy par wszystkich zmiennych oraz na przekątnej jednego z wykresów ilustrujących rozkład w obrębie danej zmiennej.
sns.pairplot(data=dt[["prod","malzenstwa","przyrost"]])
#sns.pairplot(data=dt,vars=["aprod","prod","pprod"])
Zarówno wykresy jak i przekątna mogą zostać zmodyfikowane. Jako przykład zmienimy przekątną z histogramu na wykres gęstościowy oraz zróżnicujemy kolorystycznie województwa (dwa wybrane: pomorskie i lubelskie). Dodatkowo zmodyfikujemy wielkość punktu i jego przeźroczystość. Ponieważ zarówno część macierzowa jak i przekątna to osobne typy wykresów do ich modyfikacji potrzebujemy osobne zestawy argumentów: przekazywanych jako słowniki plot_kws i diag_kws
dtm = dt.loc[dt.woj.isin(['POMORSKIE','LUBELSKIE']),["aprod","prod","pprod","woj"]]
sns.pairplot(data=dtm,hue="woj",plot_kws=dict(s=100, alpha=0.3),diag_kind="kde")
Liczna seria wykresów ma na celu zilustrowanie rozkładu zmiennej w obrębie kategorii. O ile rozkład zmiennej lepiej ilustrować wykresami rozkładu (kde, dist) o tyle porównanie rozkładu tej samej zmiennej ale w obrębie kategorii lepiej ilustrować przy pomocy wykresów pudełkowych lub ich odmian: wykresu skrzypcowego (violin) albo pasmowego (strip). Jako ilustrację dla tych wykresów użyjemy porównania rozkładu wskaźnika małżeństw dla czterech województw, dodatkowo omówimy definiowanie własnej palety kolorów dla zmiennej kategoryzowanej. Paletę kolorów definiujemy jako słownik, gdzie kategoria jest kluczem, a kolor (zapisany w formacie #KKKKKK) jest wartością słownika. Co ciekawe definiując klucze słownika możemy od razu ich użyć do redukcji wielkości dataFrame przy pomocy poznanej już wcześniej metody DataFrame.isin()
ww = ['POMORSKIE','LUBELSKIE','LUBUSKIE','WIELKOPOLSKIE']
colors = ['#4FC3FF','#FFF03D','#008004','#A5811F']
pal = dict(zip(ww,colors))
dtm = dt.loc[dt.woj.isin(ww),["malzenstwa","woj"]]
sns.catplot(x="woj",y="malzenstwa",data=dtm,kind="box",palette=pal)
plt.xticks(rotation=90)
Liczna seria wykresów ma na celu zilustrowanie rozkładu zmiennej w obrębie kategorii. O ile rozkład zmiennej lepiej ilustrować wykresami rozkładu (kde, dist) o tyle porównanie rozkładu tej samej zmiennej ale w obrębie kategorii lepiej ilustrować przy pomocy wykresów pudełkowych lub ich odmian: wykresu skrzypcowego (violin) albo pasmowego (strip). Jako ilustrację dla tych wykresów użyjemy porównania rozkładu wskaźnika małżeństw dla czterech województw, dodatkowo omówimy definiowanie własnej palety kolorów dla zmiennej kategoryzowanej. Paletę kolorów definiujemy jako słownik, gdzie kategoria jest kluczem, a kolor (zapisany w formacie #KKKKKK) jest wartością słownika. Co ciekawe definiując klucze słownika możemy od razu ich użyć do redukcji wielkości dataFrame przy pomocy poznanej już wcześniej metody DataFrame.isin()
Słabą stroną wykresu pudełkowego jest to że nie widzimy szczegółowego rozkładu wartości w obrębie kategorii a jedynie estymatory rozkładu. Alternatywą jest wykres pasmowy, który umieszcza rozproszone (jitter) punkty w miejscu rzeczywistego występowania wartości. Aby jednocześnie porównać go z wykresem pudełkowym, użyjemy metody subplot() z biblioteki matplotlib, która pozwala na umieszczanie obok siebie niezależnych wykresów.
Terminologia stosowana w w matplotlib może być myląca ze względu na dwa pojęcia: figure i axes. Figure (fig) określa nam cały rysunek, który może składać się z wykresów. Każdy z wykresów określany jest jako axes (osie) gdyż jest wyznaczany niezależnym układem współrzędnych. Różne funkcje mogą się odnosić do różnych elementów figury lub wykresów. W dalszej części będziemy stosować polskie terminu figura na obiekt nadrzędny (fig, plot) i wykres (axes) na elementy składowe figury.
fig, (ax1, ax2) = plt.subplots(1,2,figsize=(8,4))
sns.stripplot(x="woj",y="malzenstwa",data=dtm,palette=pal,ax=ax1)
_ = plt.setp(ax1.get_xticklabels(), rotation=90)
sns.boxplot(x="woj",y="malzenstwa",data=dtm,palette=pal,ax=ax2)
_ = plt.setp(ax2.get_xticklabels(), rotation=90)
Możemy też porównać kategorie w taki sposób, że każda z nich zostanie przestawiona w postaci osobnego wykresu rozkładu. Można do tego użyć wykresu typau FacetGrid, który tworzy osobne wykresy (axes) dla każdej kategorii a następnie wypełnia go odpowiednią częścią danych ze wskazanej zmiennej (małżeństwa) w zależności od wskazanej wcześniej zmiennej grupującej (woj).
g = sns.FacetGrid(dtm, col="woj", col_wrap=2)
g = g.map(sns.distplot, "malzenstwa")
Jeżeli zależy nam na przedstawieniu relacji pomiędzy dwiema zmiennymi kategoryzowanymi możemy użyć dodatkowej kategoryzowanej zmiennej grupującej, którą przypiszemy do jednej z właściwości wykresu: hue/color. Jeżeli nasza zmienna występuje w postaci pojedynczych wsystąpień musimy użyć countplot, jeżeli natomiast w postaci podsumowań: barplot. Jako przykład przedstawimy zróżnicowanie pomiędzy województwami w ilości kobiet i mężczyzn. W tym celu musimy w pierwszej kolejności zamienić format danych z szerokiego (każda kolumna to osobna cecha/zmienna) na wąski, gdzie jedna kolumna zawiera nazwy zmiennych a druga odpowidnie wartości. Dodatkowe kolumny mogą przechowywać zmienne grupujące. Do takiej operacji służy metoda df.melt()
, która dokonuje konwersji z układu szerokiego na wąski (w przeciwnymi kierunku używamy df.cast()
). Metoda wymaga wskazania zmiennych grupujących (tu "woj") oraz nazw kolumn przechowujących nazwy zmiennych _var_name_ tu: "plec" i value\ name: tu: "licznosc". Następnie wykonujemy wykres wskazując "woj" jako zmienną x, liczność jako y i płeć jako zmienną grupującą przy pomocy barwy (hue).
dtm = pd.melt(dt[["k","m","woj"]],"woj",var_name="plec", value_name="licznosc")
sns.barplot(x="woj",y="licznosc",hue="plec", data=dtm,ci=None)
plt.xticks(rotation=90)
dtm
Regresja liniowa jest najprostszą formą określenia zależności pomiędzy dwoma zmiennymi numerycznymi. Do wizualizacji regresji (liniowej i wyższych stopni) w pakiecie seaborn służy funkcja regplot()
. Funkcja tworzy zwykły wykres punktowy dla którego wylicza równanie regresji i wizualizuje go w postaci linii wraz z pasmem niepewności.
sns.regplot("aprod","pprod",data=dt)
Funkcja może wyliczyć modele regresji dla każdej kategorii z osobna. Ciekawą opcją jest również wyliczanie równania regresji z pominięciem wartości odstających (wymaga zainstalowania) biblioteki statsmodels
i ustawienia parametru robust na True
#Z rozbiciem na województwa
sns.lmplot("aprod","pprod",hue="woj",data=dt.loc[dt.woj.isin(["POMORSKIE","LUBELSKIE"])])
Wykresy regresji można również przedstawić dla każdej kategorii osobno, wskazując zmienną dla paramteru col (od kollumny nie od koloru):
sns.lmplot(x='aprod', y='pprod', data=dt, col="woj", col_wrap=4, hue="woj")
Do porównania dwóch (lub więcej) zmiennych można użyć dwuwymiarowych wykresów kde (kernel density estimation), które doskonale zastępują chmury punktów w wizualizacji danych wielowymiarowych.
sns.kdeplot(dt['malzenstwa'],dt['przyrost'],n_levels=15,cbar=True,shade=True,shade_lowest=False)
Możliwe jest również umieszczenie na jedynym wykresie kilku chmur (w celu zbadania relacji między dwoma grupami) wymaga to jednak zastosowania techniki podobnej do tworzenia wykresów stertowych tj dodawania każdego komponentu z osobna. Dwuwymiarowe wykresy KDE wymagają w pierwszym kroku przygotowania selekcji z dataFrame zawierające tylko te rekordy, które będą należały do nakładanej chmury, gdyż kde wymaga wskazania jednowymiarowego zbioru danych.
pom = dt.loc[dt.woj=='POMORSKIE',["malzenstwa","przyrost"]]
lub = dt.loc[dt.woj=='LUBELSKIE',["malzenstwa","przyrost"]]
ax = sns.kdeplot(pom.malzenstwa,pom.przyrost,cmap='Blues')
ax = sns.kdeplot(lub.malzenstwa,lub.przyrost,cmap='Greens')
import os
import numpy as np
from osgeo import gdal
from osgeo import osr