Introduction to Data Analysis with Python

PART 2 Matplotlib

Podstawy analizy danych

  1. Numpy
  2. Matplotlib
  3. Pandas
  4. Math & Scipy
  5. OpenCV
  6. Tensorflow & Scikit-learn

1. Matplotlib


  1. Podstawowe informacje
  2. Plot
  3. Scatter + Stem + subplots
  4. Boxplots - wykresy pudełkowe
  5. Barplots - wykresy kolumnowe
  6. Pieplots- wykresy kołowe
  7. Polar - wykresy liczb urojonych
  8. Obsługa obrazów - PIL

1. Podstawowe informacje

Matplotlib jest to podstawowa biblioteka do generowania wykresów graficznych i wizualizacji danych w Pythonie.

Przydatne linki:

Dodatkowe biblioteki bazujące lub podobne do matplotlib:

Seaborn - biblioteka oparta na matplotlib stworzona konkretnie do wizualizacji danych statystycznych, posiada dobrą współpracę z biblioteką pandas. Gdzie matplotlib nie może, tam seaborn pośle.

Plotly - biblioteka do tworzenia pięknych i interaktywnych wykresów z wykorzystaniem biblioteki/frameworku Dash. Istnieje wersja płatna , lecz rozwiązanie w wersji darmowej oferuje sporą część swojej rozległej funkcjonalności.

2. Plot

Spróbujmy wygenerować nasz pierwszy wykres. W tym celu należy pobrać bibliotekę

import matplotlib.pyplot as plt

podać oś X, oś Y i poleceniem

plt.plot( X,  Y)

wygenerować wykres

import matplotlib.pyplot as plt

x = [2005,2010,2015,2020]
y = [2385.39, 3231.13, 3942.78, 5282.8]
plt.plot(x,y)
plt.show() # bez tej komendy w jupiter notebook wyświetli nam wykres, ale w normalnych warunkach ta komenda jest niezbędna

Mamy nasz wykres, ale co oznaczają konkretne dane? Podpiszmy wykres oraz osie. Służą do tego polecenia

plt.xlabel("Opis osi X")
plt.ylabel("Opis osi Y")
plt.title("Opis wykresu")
import matplotlib.pyplot as plt

x = [2005,2010,2015,2020]
y = [2385.39, 3231.13, 3942.78, 5282.8]
plt.plot(x,y)
plt.title("Przeciętne zarobki w Polsce w sektorze przedsiębiorstw - Polska")
plt.xlabel("Rok")
plt.ylabel("Wynagrodznie (zł)")
Text(0, 0.5, 'Wynagrodznie (zł)')

Jest już trochę lepiej, ale jak popatrzymy na nasze dane, to mamy informacje z konkretnych lat, zaś biblioteka sobie automatycznie dopasowała krok w osiach. Jeżeli chcemy sami ustawić dla osi X:

plt.xticks(ticks=None, labels=None)

Dla osi Y:

plt.yticks(ticks=None, labels=None)

gdzie:

  • ticks - lista wartości, które chcemy wyświetlać
  • label - lista o takim samym rozmiarze co ticks, która precyzuje co ma być wyświetlane, jeżeli istnieje jakiś alternatywny zapis, nie jest obowiązkowa

Przykład:

import matplotlib.pyplot as plt
import numpy as np

x = np.arange(0, 2*np.pi, 0.1)
y = np.sin(x)
plt.plot(x,y)
plt.title("y = sin(x)")
plt.show()

Po zastosowaniu formatowania tekstu na osiach

import matplotlib.pyplot as plt
from math import sqrt

x = np.arange(0, 2*np.pi, 0.1)
y = np.sin(x)
plt.plot(x,y)
plt.title("y = sin(x)")
plt.xticks([0,np.pi/6, np.pi/4, np.pi/3, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi], ['0', r'$\frac{\pi}{6}$',r'$\frac{\pi}{4}$', r'$\frac{\pi}{3}$', r'$\frac{\pi}{2}$', r'$\pi$', r'$\frac{3\pi}{2}$', r'$2\pi$' ] )
plt.yticks([0,0.5, sqrt(2)/2, sqrt(3)/2, 1],['0',r'$\frac{1}{2}$', r'$\frac{\sqrt{2}}{2}$', r'$\frac{\sqrt{3}}{2}$', '1'])
plt.show()

Wracając do przykładu z wynagrodzeniem

import matplotlib.pyplot as plt

x = [2005,2010,2015,2020]
y = [2385.39, 3231.13, 3942.78, 5282.8]
plt.plot(x,y)
plt.title("Przeciętne zarobki w Polsce w sektorze przedsiębiorstw - Polska")
plt.xlabel("Rok")
plt.ylabel("Wynagrodznie (zł)")
plt.xticks([2005,2010,2015,2020])
plt.yticks([2000,3000,4000,5000])
plt.show()

Do ograniczenia zakresu wykresy wykorzystywane są metody

Dla osi X:

plt.xlim(limit_dolny, limit_górny)

Dla osi Y:

plt.ylim(limit_dolny, limit_górny)

Domyślnie biblioteka stara się dopasować zakresy do danych, które jej podamy. Ale nic nie stoi na przeszkodzie byśmy ograniczyli tego do naszych potrzeb.

import matplotlib.pyplot as plt

x = [2005,2010,2015,2020]
y = [2385.39, 3231.13, 3942.78, 5282.8]
plt.plot(x,y)
plt.title("Przeciętne zarobki w Polsce w sektorze przedsiębiorstw - Polska")
plt.xlabel("Rok")
plt.ylabel("Wynagrodznie (zł)")
plt.xticks([2005,2010,2015,2020])
plt.yticks([2000,3000,4000,5000])
plt.xlim(2010,2020)
plt.ylim(3000, 5000)
plt.show()

By wykres był lepiej sformatowany można skorzystać z wielu dodatkowych parametrów, które można przekazać do metody plot, takich jak:

Nazwa parametru Opis
color/c kolor wykresu
alpha przezroczystość, od 0 do 1
label opis wykresu do wykorzystania w legendzie
marker definiuje styl punktów charakterystycznych
linestyle styl linii


i wiele innych

import matplotlib.pyplot as plt

x = [2005,2010,2015,2020]
y = [2385.39, 3231.13, 3942.78, 5282.8]

plt.title("Przeciętne zarobki w Polsce w sektorze przedsiębiorstw - Polska")
plt.xlabel("Rok")
plt.ylabel("Wynagrodznie (zł)")
plt.xticks([2005,2010,2015,2020])
plt.yticks([2000,3000,4000,5000])

plt.plot(x,y, color="cyan", alpha=0.5, label="Wynagrodzenie", marker="o", linestyle="--")
plt.legend() #polecenie potrzebne by dodać legendę
plt.show()

Jako, że styl linii i kolor podaje się często, istnieje uproszczony zapis

litera koloru, marker, styl linii

np.

import matplotlib.pyplot as plt

x = [2005,2010,2015,2020]
y = [2385.39, 3231.13, 3942.78, 5282.8]

plt.title("Przeciętne zarobki w Polsce w sektorze przedsiębiorstw - Polska")
plt.xlabel("Rok")
plt.ylabel("Wynagrodznie (zł)")
plt.xticks([2005,2010,2015,2020])
plt.yticks([2000,3000,4000,5000])

plt.plot(x,y, "ro:", alpha=0.5, label="Wynagrodzenie")
plt.legend() #polecenie potrzebne by dodać legendę
plt.show()

3. Scatter + Stem + subplots

Do dużej liczby danych rozproszonych zamiast metody plot warto skorzystać z metody scatter. Reszta parametrów nie ulega zmianie i jest bardzo podoba do tego co przed chwilą omówiliśmy.

Źródło: https://matplotlib.org/3.3.2/gallery/lines_bars_and_markers/scatter_masked.html

import matplotlib.pyplot as plt
import numpy as np

# Fixing random state for reproducibility
np.random.seed(19680801)


N = 100
r0 = 0.6
x = 0.9 * np.random.rand(N)
y = 0.9 * np.random.rand(N)
area = (20 * np.random.rand(N))**2  # 0 to 10 point radii
c = np.sqrt(area) # kolor zależy od pola
r = np.sqrt(x ** 2 + y ** 2)
area1 = np.ma.masked_where(r < r0, area)
area2 = np.ma.masked_where(r >= r0, area)
plt.scatter(x, y, s=area1, marker='^', c=c)
plt.scatter(x, y, s=area2, marker='o', c=c)

plt.show()

Wykresem który łączy plot ze scatter jest stem.

plt.stem(x,y)

Gdzie:

  • x - lista danych z osi X
  • y - lista danych z osi Y
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0.1, 2 * np.pi, 41)
y = np.exp(np.sin(x))

plt.stem(x, y, use_line_collection=True)
plt.show()

Domyślnie program tworzy jedną figurę (tak nazywany jest poszczególny obraz w matplotlib) i będzie ją nadpisywać tak długo jak długo nie wywołamy metody show. Pozwala nam to zawrzeć kilka wykresów na jednym obrazie.

#ZADANIE2
#Napiecie wyjsciowe Vce
Vce=np.array([0, 0.5, 1, 2, 3, 4, 5, 6, 7, 8])
#Prad wyjściowy Ic(mA) przy stałej wartości prądu wejściowego

Ib20=np.array([0.01, 1.15, 5.32, 6.11, 6.27, 6.14, 6.17, 6.98, 6.12, 5.87])
Ib30=np.array([0.01, 1.19, 8.28, 9.47, 9.55, 9.75, 8.57, 7.08, 6.53, 6.57])
Ib40=np.array([0.02, 1.21, 12.20, 12.26, 12.03, 12.82, 12.20, 7.24, 8.12, 8.86])

z3=np.polyfit(Vce,Ib20,6)
z4=np.polyfit(Vce,Ib30,5)
z5=np.polyfit(Vce,Ib40,6)

p_3=np.poly1d(z3)
p_4=np.poly1d(z4)
p_5=np.poly1d(z5)
xp=np.linspace(start=0.1,stop=7.8, num=100)

p3 = plt.plot(Vce,Ib20,color="aquamarine",linestyle='-',marker="s", alpha=0.5,label="Wartości odnotowane Ib = 20"+u'\u03bc'+"A")
p33 = plt.plot(xp,p_3(xp),'b:',alpha=0.3)
p4 = plt.plot(Vce,Ib30,color="teal",linestyle='-',marker="p", alpha=0.5,label="Wartości odnotowane Ib = 30"+u'\u03bc'+"A")
p44 = plt.plot(xp,p_4(xp),'b:',alpha=0.3)
p5 = plt.plot(Vce,Ib40,color="cyan",linestyle='-',marker="x", alpha=0.5,label="Wartości odnotowane Ib = 40"+u'\u03bc'+"A")
p55 = plt.plot(xp,p_5(xp),'b:',alpha=0.3,label="Krzywe dopasowania")
plt.subplots_adjust(wspace=20)
plt.legend(loc='lower right') #można dokreślić gdzie ma znajdować się legenda
plt.xlabel("Napięcie wejściowe Vce[V]")
plt.ylabel("Prąd wyjściowy Ic[mA] gdy Ib=const")
plt.show()

Jeżeli jednak chcemy rozbić nasze obliczenia na kilka różnych obrazów. Musimy zrozumieć koncepcje figur, osi oraz subplotów. Dotychczasowy zapis, który wykorzystywaliśmy jest tak naprawdę uproszczeniem poniższego zapisu.

plt.subplots(nrows=1, ncols=1)

Gdzie:

  • nrows - ile szeregów wykresów
  • ncols - ile kolumn wykresów

Metoda ta zwraca figurę, oraz osie (lub listę osi) w których będziemy rysować wykresy.

import numpy as np
import matplotlib.pyplot as plt


# First create some toy data:
x = np.linspace(0, 2*np.pi, 400)
y = np.sin(x**2)

# Create just a figure and only one subplot
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_title('Simple plot') #metody wykorzystywane w osiach różnią się nazwą, zamiast plt.title mamy ax.set_title

# Create two subplots and unpack the output array immediately
f, (ax1, ax2) = plt.subplots(1, 2, sharey=True)
ax1.plot(x, y)
ax1.set_title('Sharing Y axis')
ax2.scatter(x, y)

# Create four polar axes and access them through the returned array
fig, axs = plt.subplots(2, 2, subplot_kw=dict(polar=True))
axs[0, 0].plot(x, y)
axs[1, 1].scatter(x, y)
plt.show()

Całkowity obraz składa się z poszczególnych obrazów (figure), które z kolei składają się ze składowych/osi (axes). Na początku, może to nastręczać dużo problemów w rozumieniu. Zapis jest też trochę utrudniony, niektóre metody nazywają się inaczej. Zachęcam do przeczytania wyjaśnienia, np. tutaj:

https://medium.com/towards-artificial-intelligence/day-3-of-matplotlib-figure-axes-explained-in-detail-d6e98f7cd4e7

import matplotlib.pyplot as plt
import numpy as np

# funkcje wykorzystane w zadaniu

def y1(N):
    n = np.arange(0,N)
    phase = np.pi/4
    y = np.cos(2 * np.pi * n/N +phase)
    return y


def y2(N):
    n = np.arange(0,N)
    phase = 0
    y = 0.5 * np.cos(4 * np.pi*n/N + phase)
    return y


def y3(N):
    n = np.arange(0,N)
    phase = np.pi / 2
    y = 0.25 * np.cos( 8 * np.pi*n/N + phase)
    return y


def y4(y1, y2, y3):
    a = y1 + y2 + y3
    return a


if __name__ == "__main__":
    N = 32  # zmienna okreslająca ilość próbek

    # Wygenerowanie funkcji
    func1 = y1(N)
    func2 = y2(N)
    func3 = y3(N)
    func4 = y4(func1, func2, func3)

    # Część rzeczywista funkcji sinusoidalnych
    plt.figure().suptitle("Część rzeczywista funkcji sinusoidalnych")
    plt.subplot(2, 2, 1)
    plt.plot(func1)
    plt.xlabel("Nr próbki")
    plt.ylabel("Amplituda")
    plt.title(r'$y_1=\cos$(2$\pi \cdot \frac{n}{N}$+$\frac{\pi}{4}$)')
    plt.subplot(2, 2, 2)
    plt.plot(func2)
    plt.xlabel("Nr próbki")
    plt.ylabel("Amplituda")
    plt.title(r'$y_2=\frac{1}{2}\cos$(4$\pi \cdot \frac{n}{N}$)')
    plt.subplot(2, 2, 3)
    plt.plot(func3)
    plt.xlabel("Nr próbki")
    plt.ylabel("Amplituda")
    plt.title(r'$y_3=\frac{1}{4}\cos$(8$\pi \cdot \frac{n}{N}$ +$\frac{\pi}{2}$)')
    plt.subplot(2, 2, 4)
    plt.title(r'$y_4$ = $y_1+y_2+y_3$')
    plt.plot(func4)
    plt.xlabel("Nr próbki")
    plt.ylabel("Amplituda")
    plt.tight_layout()
    plt.show()
    

    # Wyliczenia transformat Fouriera oraz innych parametrów dla każdej funkcji
    fourier1 = 2*np.fft.fft(func1)/N
    real1 = fourier1.real
    imaginary1 = fourier1.imag
    angle1 = np.angle(fourier1)
    modulus1 = np.abs(fourier1)

    fourier2 = 2*np.fft.fft(func2)/N
    real2 = fourier2.real
    imaginary2 = fourier2.imag
    angle2 = np.angle(fourier2)
    modulus2 = np.abs(fourier2)

    fourier3 = 2*np.fft.fft(func3)/N
    real3 = fourier3.real
    imaginary3 = fourier3.imag
    angle3 = np.angle(fourier3)
    modulus3 = np.abs(fourier3)

    fourier4 = 2*np.fft.fft(func4)/N
    real4 = fourier4.real
    imaginary4 = fourier4.imag
    angle4 = np.angle(fourier4)
    modulus4 = np.abs(fourier4)


    # Transformata Fouriera cz. rzeczywista
    plt.figure().suptitle("Część rzeczywista Transformaty Fouriera")
    plt.subplot(2, 2, 1)
    plt.ylim(-0.05, 0.8)
    plt.stem(real1, use_line_collection=True)
    plt.hlines(np.abs(round(real1[1], 3)), 0, 32, colors="red", linestyles="dotted")
    plt.yticks((0,0.25,0.5, np.abs(round(imaginary1[1], 3))))
    plt.xlabel("Nr pasma częstotliwościowego")
    plt.ylabel("Amplituda")
    plt.title(r'$y_1=\cos$(2$\pi \cdot \frac{n}{N}$+$\frac{\pi}{4}$)')
    plt.subplot(2, 2, 2)
    plt.ylim(-0.05, 0.8)
    plt.yticks((0,0.250, np.abs(round(real2[2], 3)),0.707))
    plt.stem(real2, use_line_collection=True)
    plt.hlines(np.abs(round(real2[2], 3)), 0, 32, colors="orange", linestyles="dotted")
    plt.xlabel("Nr pasma częstotliwościowego")
    plt.ylabel("Amplituda")
    plt.title(r'$y_2=\frac{1}{2}\cos$(4$\pi \cdot \frac{n}{N}$)')
    plt.subplot(2, 2, 3)
    plt.ylim(-0.05, 0.8)
    plt.yticks((np.abs(round(real3[4], 3)),0.250,0.50,0.707))
    plt.stem(real3, use_line_collection=True)
    plt.hlines(np.abs(round(real3[4], 3)), 0, 32, colors="gold", linestyles="dotted")
    plt.xlabel("Nr pasma częstotliwościowego")
    plt.ylabel("Amplituda")
    plt.title(r'$y_3=\frac{1}{4}\cos$(8$\pi \cdot \frac{n}{N}$ +$\frac{\pi}{2}$)')
    plt.subplot(2, 2, 4)
    plt.ylim(-0.05, 0.8)
    plt.yticks((np.abs(round(real3[4], 3)), 0.25, np.abs(round(real2[2], 3)),np.abs(round(real1[1], 3)) ))
    plt.hlines(np.abs(round(real1[1], 3)), 0, 32, colors="red", linestyles="dotted")
    plt.hlines(np.abs(round(real2[2], 3)), 0, 32, colors="orange", linestyles="dotted")
    plt.hlines(np.abs(round(real3[4], 3)), 0, 32, colors="gold", linestyles="dotted")
    plt.title(r'$y_4$ = $y_1+y_2+y_3$')
    plt.stem(real4, use_line_collection=True)
    plt.xlabel("Nr pasma częstotliwościowego")
    plt.ylabel("Amplituda")
    plt.tight_layout()
    plt.show()

4. Boxplots - wykresy pudełkowe

Do danych giełdowych bardzo często wykorzystywane są tak zwane wykresy pudełkowe, świecowe. Zakres pudełka oznacza pierwszy (dolny) i trzeci (górny) kwartyl, które odpowiednio oznaczają, że 25% i 75% danych znajduje się poniżej tych wartości. Linią przecinającą pudło oznacza się gdzie występuje mediana wartości. Zaś wąsami pudła oznacza się maksymalne odchylenia wartości (minimum i maksimum), z ograniczeniem rozmiaru wąsów dla ich czytelności.

import numpy as np
import matplotlib.pyplot as plt

data = np.random.rand(50)
fig, ax = plt.subplots(1,2)
ax[0].boxplot(data, labels=["Próbka"])

ax[1].plot(data, "co:")

plt.show()


import numpy as np
import matplotlib.pyplot as plt

data = np.random.rand(50,10) # wygenerowanie 10 serii po 50 wartości z przedziału 0 - 1
plt.boxplot(data)
plt.title("Wykres pudełkowy")
plt.show()

5. Barplots - wykresy kolumnowe

Kolejnym wykresem są bardzo popularne wykresy kolumnowe, wykorzystywane do porównania danych dyskretnych. Każdy słupek w wykresie ma odpowiednią wysokość, która koresponduje proporcjonalnie do wartości danej zmiennej.

plt.bar(x, height, width=0.8)

Gdzie:

  • x - sekwencja koordynatów wykresu kolumnowego
  • height - sekwencja wysokości poszczególnych kolumn, lub pojedyncza wartość
  • width - szerokość kolumn (opcjonalne)
import numpy as npx
import matplotlib.pyplot as plt
 
# Make a fake dataset:
height = [3, 12, 5, 18, 45]
bars = ('A', 'B', 'C', 'D', 'E')
y_pos = np.arange(len(bars))
 
# Create bars
plt.bar(y_pos, height)
 
# Create names on the x-axis
plt.xticks(y_pos, bars)
 
# Show graphic
plt.show()

By wygenerować wykres kolumnowy poziomy należy użyć metody

plt.barh(y, height)
# libraries
import numpy as np
import matplotlib.pyplot as plt
 
# Make fake dataset
height = [3, 12, 5, 18, 45]
bars = ('A', 'B', 'C', 'D', 'E')
y_pos = np.arange(len(bars))
 
# Create horizontal bars
plt.barh(y_pos, height)
 
# Create names on the y-axis
plt.yticks(y_pos, bars)
 
# Show graphic
plt.show()
puchar_domow = {
    "Slytherin": { 
        "Obrona przed Czarną Magią": 100,
        "Eliksiry" : 200,
        "Transmutacja": 150,
        "Zielarstwo": 50
     },
     "Gryffindor":{
        "Obrona przed Czarną Magią": 120,
        "Eliksiry" : 180,
        "Transmutacja": 300,
        "Zielarstwo": 120
     },
     "Ravenclaw":
     {
        "Obrona przed Czarną Magią": 140,
        "Eliksiry" : 120,
        "Transmutacja": 130,
        "Zielarstwo": 150 
     },
     "Hufflepuff":
     {
        "Obrona przed Czarną Magią": 120,
        "Eliksiry" : 120,
        "Transmutacja": 120,
        "Zielarstwo": 120
     }
}
import matplotlib.pyplot as plt
import numpy as np

barWidth = 0.3

for index, dom in enumerate(puchar_domow):
  plt.bar(
      3*np.arange(len(list(puchar_domow[dom].keys())))+(index*barWidth), #musimy przesunąć kolumny tak by na siebie nie nachodziły
      list(puchar_domow[dom].values()),
      width = barWidth, 
      label=str(dom))

plt.xticks([0.5, 3.5, 6.5, 9.5], list(puchar_domow['Slytherin'].keys()))
plt.legend()
plt.show()


import matplotlib.pyplot as plt
import numpy as np

barWidth = 0.3

temp = []
temp.append(np.zeros(4))
for index, dom in enumerate(puchar_domow):
  if index == 0:
    continue
  temp.append(list(puchar_domow[list(puchar_domow.keys())[index-1]].values()))

sum_temp = np.zeros(4)
for index, dom in enumerate(puchar_domow):
  sum_temp += temp[index]
  plt.bar(
      list(puchar_domow[dom].keys()), 
      list(puchar_domow[dom].values()),
      width = barWidth, 
      label=str(dom),
      bottom=sum_temp) #musimy podać co będzie dołem kolejnych kolumn, żeby je skleić
plt.legend()
plt.show()

Bardzo popularnym wykresem kolumnowym jest histogram. Histogram to nic innego jak wizualizacja rozkładu jakiejś cechy.

plt.hist(x, bins=None)

Gdzie:

  • x - lista danych na podstawie, której chcemy wygenerować histogram
  • bins - na ile kolumn chcemy podzielić nasz histogram, domyślnie równa się liczbie unikatowych danych podanych w x. Może być liczbą, albo sekwencją liczb od których uwzględniamy kolejne kolumny

Wygenerujmy dane z rozkładu normalnego i wykreślmy ich histogram.

Źródło: https://matplotlib.org/3.1.1/gallery/pyplots/pyplot_text.html#sphx-glr-gallery-pyplots-pyplot-text-py

import numpy as np
import matplotlib.pyplot as plt

# Fixing random state for reproducibility
np.random.seed(19680801)

mu, sigma = 100, 15
x = mu + sigma * np.random.randn(10000)

# the histogram of the data
n, bins, patches = plt.hist(x, 50, facecolor='g', alpha=0.75)


plt.xlabel('Smarts')
plt.ylabel('Population')
plt.title('Histogram of IQ')
plt.text(60, 602.5, r'$\mu=100,\ \sigma=15$')
plt.grid(True)
plt.show()

Jeżeli zamiast liczby w populacji interesuje nas prawdopodobieństwo wystąpienia danej kolumny histogramu w metodzie ustawiamy pole density=True.

import numpy as np
import matplotlib.pyplot as plt

# Fixing random state for reproducibility
np.random.seed(19680801)

mu, sigma = 100, 15
x = mu + sigma * np.random.randn(10000)

# the histogram of the data
n, bins, patches = plt.hist(x, 50, density=True, facecolor='g', alpha=0.75)


plt.xlabel('Smarts')
plt.ylabel('Probability')
plt.title('Histogram of IQ')
plt.text(60, .025, r'$\mu=100,\ \sigma=15$')
plt.xlim(40, 160)
plt.ylim(0, 0.03)
plt.grid(True)
plt.show()

Do generowania histogramu dwuwymiarowego stosujemy polecenie

plt.hist2d(x, y, bins=10, density=False)

Gdzie:

  • x, y - wartości na osiach X,Y
  • bins - analogicznie do histogramu jednowymiarowego
  • density - analogicznie do histogramu jednowymiarowego

Utworzony wykres jest heat mapą danych

import matplotlib.pyplot as plt
import numpy as np
 
# create data
x = np.random.normal(size=50000)
y = x * 3 + np.random.normal(size=50000)
 
# Big bins
plt.hist2d(x, y, bins=(50, 50), cmap=plt.cm.jet)
plt.show()

6. Piecharts - wykresy kołowe

Dotarliśmy do ostatniego kręgu piekła jeżeli chodzi o wizualizację danych. Wykresy kołowe owiane złą sławą są z reguły niepotrzebne, a nasze dane możemy przedstawić w innej, lepszej formie. Jeżeli jednak uprzemy się na nie, je również można zrealizować w matplotlib.

import matplotlib.pyplot as plt

tasks = 'task_1', 'task_2', 'task_3', 'task_4', \
         'task_5', 'task_6', 'task_7', 'task_8', \
         'task_9', 'task_10', 'task_11', 'task_12', \
         'task_13', 'task_14', 'task_15', 'task_16', \
         'task_17', 'task_18', 'task_19'

time =[0.722539930138737,
     0.28336817817762494 + 0.5273803339805454,
     0.1135483211837709 + 0.13343579485081136,
     0.8881094930693507 + 0.3978329210076481 + 0.34098400291986763 + 0.4007001109421253 + 0.3937181669753045,
     3.1206228479277343 + 4.390893992036581 + 4.731932940194383 + 3.0876565920189023 + 15.17197903408669,
     0.052445481065660715 + 0.019446991849690676 + 0.019331110874190927 + 0.09747512708418071 + 0.060720112873241305,
     0.029143241932615638,
     0.7307675471529365,
     0.10479382984340191,
     0.4169424350839108,
     1.1366547809448093,
     1.5477838290389627,
     0.05833269190043211,
     0.12475891201756895,
     0.08382681896910071,
     0.0744338259100914,
     0.0875642818864435,
     0.11377762793563306,
     0.04675672412849963]

colors = ['red',
          'pink',
          'orange',
          'purple',
          'indigo',
          'blue',
          'lightblue',
          'cyan',
          'teal',
          'green',
          'lightgreen',
          'yellowgreen',
          'yellow',
          'gold',
          'orange',
          'darkorange',
          'brown',
          'gray',
          'darkslategray']

plt.figure(figsize=(15,  10))

patches = plt.pie(time,
                  colors=colors,
                  radius=1,
                  )

plt.legend(tasks, bbox_to_anchor=(1,1),
           fontsize=18)
plt.tight_layout()
plt.title("Czas trwania zadania")
plt.show()

Jedyną informację jaką jesteśmy w stanie wyciągnąć z naszego wykresu jest to, że istnieje jedno zadanie, którego czas trwania stanowi ok. 75% całego czasu. Z wykresu nie jesteśmy w stanie jednak wyczytać ile trwa poszczególne zadanie (możemy dodać tę informację, ale przy dużej liczbie małych fragmentów, będzie to nieczytelne). Dlatego tego typu wykresy mają tylko sens, przy małej liczbie unikatowych kategorii, na które chcemy dzielić nasze dane przy ich wizualizacji.

By poczytać więcej na ten temat zachęcam do lektury:

https://www.businessinsider.com/pie-charts-are-the-worst-2013-6?IR=T

Popularną alternatywą dla wykresu kołowego jest wykres kołowy z wcięciem w środku, tzw. pączek. By go zrobić należy do wykresu dodać białe koło i upozycjonować je na środku wykresu kołowego.

import matplotlib.pyplot as plt

tasks = 'task_1', 'task_2', 'task_3', 'task_4', \
         'task_5', 'task_6', 'task_7', 'task_8', \
         'task_9', 'task_10', 'task_11', 'task_12', \
         'task_13', 'task_14', 'task_15', 'task_16', \
         'task_17', 'task_18', 'task_19'

time =[0.722539930138737,
     0.28336817817762494 + 0.5273803339805454,
     0.1135483211837709 + 0.13343579485081136,
     0.8881094930693507 + 0.3978329210076481 + 0.34098400291986763 + 0.4007001109421253 + 0.3937181669753045,
     3.1206228479277343 + 4.390893992036581 + 4.731932940194383 + 3.0876565920189023 + 15.17197903408669,
     0.052445481065660715 + 0.019446991849690676 + 0.019331110874190927 + 0.09747512708418071 + 0.060720112873241305,
     0.029143241932615638,
     0.7307675471529365,
     0.10479382984340191,
     0.4169424350839108,
     1.1366547809448093,
     1.5477838290389627,
     0.05833269190043211,
     0.12475891201756895,
     0.08382681896910071,
     0.0744338259100914,
     0.0875642818864435,
     0.11377762793563306,
     0.04675672412849963]

colors = ['red',
          'pink',
          'orange',
          'purple',
          'indigo',
          'blue',
          'lightblue',
          'cyan',
          'teal',
          'green',
          'lightgreen',
          'yellowgreen',
          'yellow',
          'gold',
          'orange',
          'darkorange',
          'brown',
          'gray',
          'darkslategray']

plt.figure(figsize=(15,  10))

patches = plt.pie(time,
                  colors=colors,
                  radius=1,
                  )

plt.legend(tasks, bbox_to_anchor=(1,1),
           fontsize=18)
plt.tight_layout()
plt.title("Czas trwania zadania")

# dodaj koło na środku
my_circle=plt.Circle( (0,0), 0.7, color='white')
p=plt.gcf()
p.gca().add_artist(my_circle)
 

plt.show()
import matplotlib.pyplot as plt

tasks = 'task_1', 'task_2', 'task_3', 'task_4', \
         'task_5', 'task_6', 'task_7', 'task_8', \
         'task_9', 'task_10', 'task_11', 'task_12', \
         'task_13', 'task_14', 'task_15', 'task_16', \
         'task_17', 'task_18', 'task_19'

time =[0.722539930138737,
     0.28336817817762494 + 0.5273803339805454,
     0.1135483211837709 + 0.13343579485081136,
     0.8881094930693507 + 0.3978329210076481 + 0.34098400291986763 + 0.4007001109421253 + 0.3937181669753045,
     3.1206228479277343 + 4.390893992036581 + 4.731932940194383 + 3.0876565920189023 + 15.17197903408669,
     0.052445481065660715 + 0.019446991849690676 + 0.019331110874190927 + 0.09747512708418071 + 0.060720112873241305,
     0.029143241932615638,
     0.7307675471529365,
     0.10479382984340191,
     0.4169424350839108,
     1.1366547809448093,
     1.5477838290389627,
     0.05833269190043211,
     0.12475891201756895,
     0.08382681896910071,
     0.0744338259100914,
     0.0875642818864435,
     0.11377762793563306,
     0.04675672412849963]

colors = ['red',
          'pink',
          'orange',
          'purple',
          'indigo',
          'blue',
          'lightblue',
          'cyan',
          'teal',
          'green',
          'lightgreen',
          'yellowgreen',
          'yellow',
          'gold',
          'orange',
          'darkorange',
          'brown',
          'gray',
          'darkslategray']

plt.figure(figsize=(15,  10))

plt.bar(tasks, time, color=colors)
plt.tight_layout()
plt.title("Czas trwania zadania")
plt.ylabel("Czas (s)")
plt.show()

Od razu lepiej. Dla lepszej czytelności możemy zmienić skalę w której wyświetlamy nasze wykresy. Służą do tego polecenia

plt.xscale("skala")

dla osi X

plt.yscale("skala")

dla osi Y

Dopuszczalne wartości:

  • ‘linear’ - skala liniowa
  • ‘log’ - skala logarytmiczna
  • ‘symlog’ - skala logarytmiczna, symetryczna
  • ‘logit’ - skala logit
import matplotlib.pyplot as plt

tasks = 'task_1', 'task_2', 'task_3', 'task_4', \
         'task_5', 'task_6', 'task_7', 'task_8', \
         'task_9', 'task_10', 'task_11', 'task_12', \
         'task_13', 'task_14', 'task_15', 'task_16', \
         'task_17', 'task_18', 'task_19'

time =[0.722539930138737,
     0.28336817817762494 + 0.5273803339805454,
     0.1135483211837709 + 0.13343579485081136,
     0.8881094930693507 + 0.3978329210076481 + 0.34098400291986763 + 0.4007001109421253 + 0.3937181669753045,
     3.1206228479277343 + 4.390893992036581 + 4.731932940194383 + 3.0876565920189023 + 15.17197903408669,
     0.052445481065660715 + 0.019446991849690676 + 0.019331110874190927 + 0.09747512708418071 + 0.060720112873241305,
     0.029143241932615638,
     0.7307675471529365,
     0.10479382984340191,
     0.4169424350839108,
     1.1366547809448093,
     1.5477838290389627,
     0.05833269190043211,
     0.12475891201756895,
     0.08382681896910071,
     0.0744338259100914,
     0.0875642818864435,
     0.11377762793563306,
     0.04675672412849963]

colors = ['red',
          'pink',
          'orange',
          'purple',
          'indigo',
          'blue',
          'lightblue',
          'cyan',
          'teal',
          'green',
          'lightgreen',
          'yellowgreen',
          'yellow',
          'gold',
          'orange',
          'darkorange',
          'brown',
          'gray',
          'darkslategray']

plt.figure(figsize=(15,  10))

plt.bar(tasks, time, color=colors)
plt.tight_layout()
plt.title("Czas trwania zadania")
plt.ylabel("Czas (s)")
plt.yscale("log")
plt.show()

Jak widać mając te same dane, w zależności od tego co chcemy przekazać użyjemy różnych metod do ich wizualizacji. Bardzo ważną częścią pracy z danymi jest umiejętny dobór narzędzi do wizualizacji danych. Ludzie są wzrokowcami, często prawidłowe dane, przedstawione w nieprawidłowy sposób prowadzą do niewłaściwych wniosków.

Polecam poczytać o 5 rzeczach, na które warto zwrócić uwagę analizując wykresy

https://venngage.com/blog/misleading-graphs/

7. Polar - wykresy liczb urojonych

Do wykresów liczb zespolonych istnieje specjalny wykres zwany polar.

plt.polar(theta, r)

Gdzie:

  • theta - lista kątów
  • r - lista promieni

W konwencji tej 0 stopni oznacza liczbę rzeczywistą pozytywną, 180 stopni liczbę rzeczywistą negatywną, 90 stopni liczbę urojoną pozytywną, a 270 stopni liczbę urojoną ujemną. Wszystkie pozostałe stopnie to mieszanka liczb urojonych i liczb rzeczywistych czyli liczby zespolone.

Źródło: https://www.geeksforgeeks.org/plotting-polar-curves-in-python/

Kardioida (wykres sercowy)

\[r = a + a \cos{\theta}\]
import numpy as np 
import matplotlib.pyplot as plt 
import math 
  
  
# setting the axes 
# projection as polar 
plt.axes(projection = 'polar') 
  
# setting the length of  
# axis of cardioid 
a=4
  
# creating an array 
# containing the radian values 
rads = np.arange(0, (2 * np.pi), 0.01) 
   
# plotting the cardioid 
for rad in rads: 
    r = a + (a*np.cos(rad))  
    plt.polar(rad,r,'g.')  
  
# display the polar plot 
plt.show()

Spirala Archimedesa

\[r = \theta\]
import numpy as np 
import matplotlib.pyplot as plt 
  
  
# setting the axes 
# projection as polar 
plt.axes(projection = 'polar') 
  
# creating an array 
# containing the radian values 
rads = np.arange(0, 2 * np.pi, 0.001)  
  
# plotting the spiral 
for rad in rads: 
    r = rad 
    plt.polar(rad, r, 'g.') 
      
# display the polar plot 
plt.show()

Krzywa róży

\[r = a \cos({n \theta})\]
import numpy as np 
import matplotlib.pyplot as plt 
  
  
# setting the axes 
# projection as polar 
plt.axes(projection='polar') 
  
# setting the length 
# and number of petals 
a = 1
n = 4
  
# creating an array 
# containing the radian values 
rads = np.arange(0, 2 * np.pi, 0.001)  
  
# plotting the rose 
for rad in rads: 
    r = a * np.cos(n*rad) 
    plt.polar(rad, r, 'g.') 
   
# display the polar plot 
plt.show() 

8. Obsługa obrazów - PIL

Poza generowaniem własnych wykresów biblioteka matplotlib umożliwia wczytywanie i wyświetlanie obrazów.

Odpowiednio:

plt.imread(fname, format=None)

Gdzie:

  • fname - ścieżka do pliku na dysku, sama nazwa pliku jeżeli znajduje się w tym samym folderze co plik pythona, który uruchamiamy
  • format - format pliku, jeżeli nie jest sprecyzowany, domyślnie próbuje otworzyć plik w formatowaniu PNG
plt.imshow(X, cmap=None)

Gdzie:

  • X - obiekt PIL lub tablica o określonych rozmiarach:
    • (M, N)
    • (M, N, 3): obraz RGB, wartości rzeczywiste z zakresu 0-1, lub całkowite z zakresu 0-255
    • (M, N, 4): jak wyżej, z uwzględnieniem kanału przezroczystości alpha
  • cmap - paleta kolorów, dla obrazów czarnobiałym warto zdefiniować odpowiednią paletę kolorów

Matplotlib posiada wiele palet kolorystycznych, poniżej znajduje się ich lista:

https://matplotlib.org/examples/color/colormaps_reference.html

import matplotlib.pyplot as plt

from google.colab import drive #polecenia potrzebne tylko w arkuszu Google by załadować zdjęcia z dysku Google, nie używać u siebie

drive.mount('/content/drive')

filter_img = plt.imread('/content/drive/My Drive/Warsztaty/filter.jpg')
plt.imshow(filter_img)
Mounted at /content/drive





<matplotlib.image.AxesImage at 0x7f6509650630>

PIL (Python Imaging Library)

jest standardową biblioteką do przechowywania obrazów w Pythonie. Biblioteka ta jednak przestała być rozszerzana przez autora w 2011 i nie jest kompatybilna z obecną wersją Pythona. Z tego powodu powstał fork tej biblioteki, który nazywa się Pillow, który jest aktualny z obecną wersją Pythona, a przy okazji w dużej mierze kompatybilny z oryginalnym PILem. Ze względów semantycznych importuje się go jako PIL, mimo iż to co pracuje pod jego kopułą to w rzeczywistości pillow.

import PIL
import matplotlib.pyplot as plt

image = PIL.Image.open('/content/drive/My Drive/Warsztaty/filter.jpg')
plt.imshow(image) #działa tak samo, lecz obiekt PIL posiada o wiele więcej możliwości!
<matplotlib.image.AxesImage at 0x7f6508bc57f0>

Już wcześniej wspomnieliśmy, że numpy jest domyślnym sposobem przechowywania informacji w Pythonie jeżeli chodzi o analizę danych, operację wykonywane na nim są stosunkowo szybkie.

Z tego też powodu istnieje możliwość konwersji obiektów PIL do tablic n-wymiarowych numpy i odwrotnie. Logika jest taka, gdy potrzebujemy wysokiej funkcjonalności na obrazie - operujemy na obiekcie PIL, jeżeli potrzebujemy dokonać obliczeń - dokonujemy ich na macierzy numpy.

Przykładowe konwersje macierzy numpy do obiektu PIL:

PIL_image = Image.fromarray(np.uint8(numpy_image)).convert('RGB')
PIL_image = Image.fromarray(numpy_image.astype('uint8'), 'RGB')

Przykładowe konwersje obiektu PIL do macierzy numpy :

macierz = numpy.asarray(PIL.Image.open('obraz.jpg'))

W obu przypadkach ważne jest kodowanie obiektów, musimy dostosować by kodowanie w jednej wersji było kompatybilne do kodowania w drugiej wersji, najlepiej operować na kodowaniu np.uint8 czyli liczbach całkowitych z zakresu 0-255. Jeżeli jest jakiś problem z obrazem w naszym kodzie zawsze warto jest sprawdzić czy aby na pewno korzystamy z dobrego kodowania.

Macierze numpy są wykorzystywane również przez OpenCV z tego względu, jest to unikatowy sposób zapisu naszych obrazów. Warto opanować tę umiejętność.

Praca domowa

Spróbuj przygotować wybrany przez siebie wykres na podstawie jakichś danych zebranych w zestawieniu, raporcie, etc. Zbiór danych nie powinien być zbyt duży, ponieważ jeszcze nie opanowaliśmy jak pracować na dużych zbiorach. Porównaj przedstawienie tych samych danych na różnych wykresach. Jakie wykresy pasują do wizualizacji danego zagadnienia?

Przykładowe raporty:

  • https://www.theesa.com/esa-research/2020-essential-facts-about-the-video-game-industry/
  • https://polishgamers.com/
  • https://gs.statcounter.com/screen-resolution-stats/mobile/worldwide
  • https://stat.gov.pl/podstawowe-dane/
  • https://ec.europa.eu/eurostat/web/main/home

Przykładowe kody:

import matplotlib.pyplot as plt

age_group = '16-24', '25-34', '35-44', '45-55'

percentage = [24, 32, 29, 14]

colors = ['orangered',
          'gold',
          'lime',
          'dodgerblue',
          ]

plt.figure(figsize=(15, 10))

patches = plt.pie(percentage,
                  colors=colors,
                  autopct='%1.0f%%',
                  pctdistance=0.85,
                  textprops={'fontsize': 25})

plt.legend(age_group, bbox_to_anchor=(1, 1),
           fontsize=18)
plt.tight_layout()
# plt.title("Procentowy udział")

# dodaj koło na środku
my_circle = plt.Circle((0, 0), 0.7, color='white')
p = plt.gcf()
p.gca().add_artist(my_circle)

plt.show()

import matplotlib.pyplot as plt

age_group = '<18', '18-34', '35-54', '55-64', '>64'

percentage = [21, 38, 26, 9, 6]

colors = ['orangered',
          'gold',
          'lime',
          'dodgerblue',
          'darkorchid'
          ]

plt.figure(figsize=(15, 10))

patches = plt.pie(percentage,
                  colors=colors,
                  autopct='%1.1f%%',
                  pctdistance=0.85,
                  textprops={'fontsize': 25})

plt.legend(age_group, bbox_to_anchor=(1, 1),
           fontsize=18)
plt.tight_layout()
# plt.title("Procentowy udział")

# dodaj koło na środku
my_circle = plt.Circle((0, 0), 0.7, color='white')
p = plt.gcf()
p.gca().add_artist(my_circle)

plt.show()

import matplotlib.pyplot as plt

smartphone = [89, 95, 87, 93]
pc = [64, 76, 86, 88]
web = [59, 68, 82, 82]
console = [52, 65, 78, 67]
social = [57, 48, 78, 83]


plt.plot(smartphone, "co:", alpha=0.6, label="Urządzenia mobilne")
plt.plot(pc, "mv--", alpha=0.6, label="Komputery")
plt.plot(web, "bs-.", alpha=0.6, label="Przeglądarki internetowe")
plt.plot(console, "gp:", alpha=0.6, label="Konsole")
plt.plot(social, "rH--", alpha=0.6, label="Platformy społecznościowe")
plt.legend()
plt.xlabel("Przedział wiekowy")
plt.ylim(0,100)
plt.xticks(ticks=[0, 1, 2, 3], labels=["do 7 roku życia", "8-10 lat", "11-14 lat", "15-18 lat"])
plt.ylabel("Udział graczy korzystających z danej platformy")
plt.yticks(ticks=[0,20,40,60,80,100], labels=["0%","20%","40%","60%","80%","100%"])
plt.show()