SAMSUNG_022024C Advertisement SAMSUNG_022024C Advertisement SAMSUNG_022024C Advertisement

ML v Pythone 6 – animované grafy a ich export ako video

0

predchádzajúcej časti sme ukázali nákladné možnosti názorného zobrazovania údajov pomocou grafov. Ešte názornejšie sú však animované grafy. Animácie je možné exportovať ako videá do súborov .MP4, alebo animovaný GIF. Animácia okrem názornosti umožňuje lepšie pochopiť dynamické procesy, alebo vývoj určitého trendu. V závere článku ukážeme postup vytvorenia obľúbeného vodorovného stĺpcového grafu v ktorom sa menia nielen hodnoty, ale aj usporiadanie.

Postup vytvorenia príkladov aj animácia je v krátkom videu

Najskôr ukážeme ako nakreslíme statický graf a uložíme ho na Google Disk ako obrázok vo formáte PNG. Hodnoty sú pre jednoduchosť definované priamo v kóde

import matplotlib.pyplot as plt
from google.colab import drive
 
data_lst = [27, 33, 35, 30, 24, 28, 21]
fig, ax = plt.subplots()
ax.plot(data_lst)
ax.set_xlabel('deň')
ax.set_ylabel('Teplota (*C)')
ax.set_title('Teplota za posledných 5 dní')
drive.mount('/content/drive')
fig.savefig('/content/drive/My Drive/MojeData/teplota.png')
 
Objekt Figure si môžeme predstaviť ako kontejner na zapuzdrenie grafických objektov, statických aj animovaných. Zaujímavejší by bol určite animovaný graf
from random import randint
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from google.colab import drive
 
# prázdne zoznamy pre hodnoty X a Y
x = []
y = []
 
# funkcia na zobrazenie jednej snímky animácie
def animacia(i):
    pt = randint(1,9)
    x.append(i)
    y.append(pt)
 
    # osi
    ax.clear()
    ax.plot(x, y)
    ax.set_xlim([0,20])
    ax.set_ylim([0,10])
 
ani = FuncAnimation(fig, animacia, frames=20, interval=500, repeat=False)
# uloženie videa
drive.mount('/content/drive')
ani.save('/content/drive/My Drive/MojeData/animovany_graf.mp4',
          writer = 'ffmpeg', fps = 5)

Alebo animácia vykresľovania sinusovky

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from google.colab import drive
 
x = np.linspace(0, 10)
y = np.sin(x)
 
fig, ax = plt.subplots()
line, = ax.plot(x, y)
 
# funkcia na zobrazenie jednej snímky animácie
def animacia(num, x, y, line):
    line.set_data(x[:num], y[:num])
    return line,
 
ani = FuncAnimation(fig, animacia, len(x), interval=100, fargs=[x, y, line], blit=True)
drive.mount('/content/drive')
ani.save('/content/drive/My Drive/MojeData/sinus1.mp4',
          writer = 'ffmpeg', fps = 25)
 
# ani.save('animation_drawing.gif', writer='imagemagick', fps=60)

funkcia plot() vráti n-ticu s jedným prvkom. Pridaním čiarky do cieľa priradenia požiadate Python, aby rozbalil návratovú hodnotu a priradil ju

Postup vytvorenia animácie:

  • definujeme rozmery okna v ktorom sa bude vykresľovať animácia
  • inicializujeme premennú ciara, ktorá bude obsahovať súradnice x a y vykresľovaného grafu. Tieto sú pri inicializácii prázdne, pretože údaje súradníc sa počas animácie budú meniť kvôli animácii.
  • vytvoríme animačnú funkciu animate(i), kde i je číslo snímky a definujeme funkciu pre hodnotu v osi y
  • vytvorenie animácie, ktorá zobrazí 200 snímok za sekundu v intervale 20 mikrosekund.
  • uloženie videa na Disk Google

Ukážeme animáciu posunu sinusovky v osi X

from matplotlib import pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
from google.colab import drive
 
# inicializácia
fig = plt.figure()
 
# 1. definovanie okna
okno = plt.axes(xlim =(0, 4),
                ylim =(-2, 2))
 
# 2. premenná ciara
ciara, = okno.plot([], [], lw = 3)
 
# dáta budú obsahovať hodnoty x a y
def init():
    ciara.set_data([], [])
    return ciara,
 
# 3. animačná funkcia
def animate(i):
    x = np.linspace(0, 4, 1000)
    y = np.sin(2 * np.pi * (x - 0.01 * i))
    ciara.set_data(x, y)
    return ciara,
 
# 4. animácia
anim = FuncAnimation(fig, animate, init_func = init,
                     frames = 200, interval = 20, blit = True)
 
# 5. uloženie videa
drive.mount('/content/drive')
anim.save('/content/drive/My Drive/MojeData/animacia1.mp4',
          writer = 'ffmpeg', fps = 30)

Animácia nám umožní lepšie pochopiť niektoré metódy, ktoré sa dajú graficky zobraziť. Napríklad Visvalingam-Whyatt algoritmus umožňuje zjednodušiť zložité lomené čiary znižovaním počtu vrcholov pri zachovaní tvaru lomenej čiary a minimalizácii skreslenia. Táto technika sa využíva napríklad v kartografii. Zjednodušením lomených čiar môžeme zvýšiť výkon vykresľovania a zlepšiť interpretovateľnosť údajov.

Algoritmus vyhodnocuje dôležitosť každého vrcholu na základe zmeny v oblasti, ktorá by nastala, keby bol daný vrchol odstránený. Najskôr sa vypočíta plocha každého trojuholníka tvoreného tromi po sebe nasledujúcimi vrcholmi v lomenej čiare. Odstránenie vrcholov s najmenšími plochami spôsobí najmenšiu zmenu celkového tvaru krivky. Výsledná zjednodušená lomená čiara pozostávajúca zo zostávajúcich vrcholov je zjednodušená reprezentácia pôvodnej krivky, pričom si zachováva jej podstatné tvarové charakteristiky. Pozrite si výsledok - animovaný graf. Sledujte obrázok dlhšie, spočiatku sú zmeny menej nápadné.

import math
from shapely.geometry import Polygon
from shapely.geometry import LineString
 
 
fig, ax = plt.subplots(1, 1)
fig.set_size_inches(20,5)
fig.tight_layout(rect=[0, 0.03, 1, 0.95])
 
# údaje pre lomenú čiaru
point_list = [(0,3), (1,0), (2,1), (3, 2), (5, 3),
              (7,4), (8,4), (10,3), (11,1), (12,1),
              (15,2), (17, 3), (18, 3), (20, 2)]
 
 
def visvalingam_whyatt(point_list, required_points):
    # budeme modifikovať kópiu údajov
    points = point_list.copy()
 
    if len(points) == required_points:
        return points
 
    # výpočet trojuholníkov pre jednotlivé vrcholy
    areas = []
    for index, point in enumerate(points):
        if index == 0 or index == len(points)-1:
            areas.append(math.inf)
        else:
            p1 = points[index-1]
            p2 = point
            p3 = points[index+1]
            polygon = Polygon([p1, p2, p3])
            areas.append(polygon.area)
    min_area = min(areas)
    remove_index = areas.index(min_area)
 
    # odstránenie trojuholníka s najmenšou plochou
    points.pop(remove_index)
    if len(points) == required_points:
        return points
    else:
        # rekurzívne volanie pre upravený zoznam vrcholov
        return visvalingam_whyatt(
            points, required_points)
 
def animate(i):
    ax.clear()
    simplified_list = visvalingam_whyatt(
        point_list, len(point_list)-i)
    original = LineString(point_list)
    simplified = LineString(simplified_list)
    ax.plot(*original.xy, color='red',
            label='original', marker='o')
    ax.plot(*simplified.xy, color='blue',
            linestyle='dashed', label='zjednodušenie', marker='o')
    ax.set_title(
        'Visvalingam Whyatt algoritmus - \
        Eliminované {} vrcholov'.format(i), fontsize=20)
    ax.legend()
 
ani = FuncAnimation(fig, animate, frames=len(point_list)-1,
                    interval=500, repeat=False)
plt.close()
 
from matplotlib.animation import PillowWriter
drive.mount('/content/drive')
ani.save("/content/drive/My Drive/MojeData/visvalingam_whyatt1.gif",
         dpi=300, writer=PillowWriter(fps=1))

Najlepšie sme si nechali na koniec. Určite ste videli efektné animované stĺpcové grafy ako sa niečo menilo v priebehu času. Napríklad zárobky najbohatších ľudí na svete, počet predaných albumov spevákov a podobne. Ukážeme animovaný stĺpcový graf najľudnatejších miest v priebehu histórie, pričom grafy budú usporiadené od najväčšieho počtu obyvateľov zostupne. Dokážeme to na 35 riadkov kódu (ak nepočítame komentáre), pričom tretina riadkov je tam kvôli vzhľadu grafu. Tu je ukážka pre kratší časový interval 1990 - 2000

Inšpiráciou pre tento príklad je článok https://pratapvardhan.com/blog/bar-chart-race-python-matplotlib/  Údaje vo formáte CSV si môžete stiahnuť tu a uložiť na váš Disk Google. Údaje majú štruktúru:

name,group,year,value,subGroup,city_id,lastValue,lat,lon
Agra,India,1575,200,India,Agra - India,200,27.18333,78.01667

nás budú zaujímať len atribúty usecols=['name', 'group', 'year', 'value']). Príklad je okomentovaný vo videu

import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import matplotlib.animation as animation
from google.colab import drive
 
# načítanie údajov
drive.mount('/content/drive')
df = pd.read_csv('/content/drive/My Drive/MojeData/Populacia.csv', usecols=['name', 'group', 'year', 'value'])
 
# farby pre jednotlivé kontinenty
colors = dict(zip(
    ['India','Europe','Asia','Latin America','Middle East','North America','Africa'],
    ['#adb0ff', '#ffb3ff', '#90d595', '#e48381', '#aafbff', '#f7bb5f', '#eafb50']
))
 
# mapovanie medzi mestom (name) a zemepisnou oblasťou (group) podľa ktorej sa určuje farba
group_lk = df.set_index('name')['group'].to_dict()
# veľkosť zobrazovanej plochy
fig, ax = plt.subplots(figsize=(15, 8))
 
def animacia(year):
    # utriedenie miest v príslušnom roku
    dff = df[df['year'].eq(year)].sort_values(by='value', ascending=True)
    ax.clear()
    ax.barh(dff['name'], dff['value'], color=[colors[group_lk[x]] for x in dff['name']], edgecolor='black')
    dx = dff['value'].max() / 200
    ax.set_xlim([0, 40000])  # maximum v osi X 40 miliónov
    # textový popis vodorovného stĺpca (London, Europe, 10000)
    for i, (value, name) in enumerate(zip(dff['value'], dff['name'])):
        ax.text(value-dx, i, name, size=14, weight=600, ha='right', va='bottom')
        ax.text(value-dx, i-0.25, group_lk[name], size=10, color='#444444', ha='right',
                va='baseline')
        ax.text(value+dx, i, f'{value:,.0f}',  size=14, ha='left',  va='center')
    # Rok veľkým fontom vpravo a popisy grafu
    ax.text(1, 0.4, year, transform=ax.transAxes, color='#777777', size=46, ha='right', weight=800)
    ax.text(0, 1.06, 'Populácia (v tisícoch)', transform=ax.transAxes, size=12, color='#777777')
    # Popis osi X
    ax.xaxis.set_major_formatter(ticker.StrMethodFormatter('{x:,.0f}'))
    ax.xaxis.set_ticks_position('top')
    ax.tick_params(axis='x', colors='#777777', labelsize=12)
    ax.set_yticks([])
    ax.margins(0, 0.01)
    ax.grid(which='minor', axis='x', linestyle='-')
    ax.set_axisbelow(True)
    ax.text(0, 1.1, 'Najľudnatejšie mestá sveta 1900 - 2018',
            transform=ax.transAxes, size=24, weight=600, ha='left')
    plt.box(False)
 
ani = animation.FuncAnimation(fig, animacia, frames=range(1900, 2019))
ani.save('/content/drive/My Drive/MojeData/mesta_populacia2.mp4',
          writer = 'ffmpeg', fps = 15)

 

V budúcom pokračovaní ukážeme inštaláciu lokálnych vývojárskych nástrojov

Strojové učenie v Pythone 1 – prostredie Google Colab

Strojové učenie v Pythone 2 – knižnica Pandas na prácu s údajmi

ML v Pythone 3 – export a import údajov vo formáte CSV a Excel

Strojové učenie v Pythone 4 – práca s údajmi

ML v Pythone 5 – vizualizácia údajov pomocou grafov

Zobrazit Galériu

Luboslav Lacko

Všetky autorove články
Python strojove ucenie machine learning Pandas matplotlib animacia

Pridať komentár

Mohlo by vás zaujímať

Mohlo by vás zaujímať