Ábrakészítés a plotly modul segítségével

Az ábrakészítéshez természetesen az eddig használt matplotlib modul mellett számos másik függvénycsomag is létezik. A lent röviden bemutatott plotly modul előnye, hogy az alapbeállításokat használva is elegáns és interaktív ábrákat tudunk készíteni.

A plotly szintaxisa az eddigiektől azonban némiképp eltér, ennek a rövid bemutatására törekszünk néhány példán keresztül.

A plotly offline és online üzemmódja

A plotly alapvetően egy webes felület, melyen egy ingyenes regisztráció és bejelentkezés után mindenkinek lehetősége nyílik adatok feltöltésére, azok feldolgozására interaktív ábrák formájában, majd a legyártott ábrák megosztására. Ez a módszer jelentősen megkönnyítheti egy csoporton belül a kollaborációt, hiszen a csoporttagok nemcsak az ábrákhoz férnek hozzá, hanem magukhoz az adatokhoz és az ábrákat legyártó kódrészletekhez is. Így ha valaki csak a vonalak színét szeretné egy ábrán megváltoztatni, nem kell e-mailben megkérnie az ábra eredeti gyártóját, hogy ezt tegye meg, hanem a kód átírásával saját maga is megoldhatja.

Néha azonban a vizsgált adatok természetüknél fogva nem tölthetők fel egy publikus tárhelyre, a privát tárhely használatához pedig Pro accountra van szükség, ami értelemszerűen nem ingyenes. Gyakran előfordul olyan probléma is, hogy a feldolgozni kívánt adatfájlok annyira nagyok, hogy feltöltésük (illetve bármilyen mozgatásuk) nem praktikus. Ilyen és ehhez hasonló esetekben szükség lenne egy "lokális" ábrakészítő opcióra, mellyel nem az adatok mennek a plotlyhoz, hanem a plotly jön az adatokhoz. Ennek a megoldására készült a Plotly Offline verziója, mellyel az ábrák és adatok nem egy központi szerveren tárolódnak, hanem a lokális gépen, illetve notebookban. A lenti példákban ezt az offline verziót használjuk. A plotly online dokumentációjában részletes leírást találhatunk találunk a csomag által kínált ábra készítő rutinokról.

(A plotly ábrák generálása emellett nemcsak a mostanra megszokott Python nyelven lehetséges, hanem akár R-ben vagy JavaScriptben is. Ezekről az alábbiakban nem ejtünk szót.)

A plotly modul importálása és az offline verzió függvényeinek betöltése

In [1]:
# a szokásos pylab import
%pylab inline 
# plotly specifikus importok és inicializálás
from plotly import *
from plotly.offline import *
init_notebook_mode()
Populating the interactive namespace from numpy and matplotlib
IOPub data rate exceeded.
The notebook server will temporarily stop sending output
to the client in order to avoid crashing it.
To change this limit, set the config variable
`--NotebookApp.iopub_data_rate_limit`.

Egyszerű ábra készítése

Adatok gyártása

Mint ahogy a matplotlib modulnál is láttuk, minden ábra generálásához elsőként az adatok betöltésére vagy legyártására van szükség. Nézzük a szokásos példánkat: ábrázoljuk a $sin(x)$ függvényt $0$ és $2π$ között.

In [2]:
x_pontok = linspace(0,2*np.pi,10)
y_pontok = sin(x_pontok)

Az ábra elkészítése

A plotly kicsit másképp viselkedik, mint amit a matplotlib-nél láttunk. Itt egy ábrát alapvetően különféle objektumokkal és egymásba ágyazott dict-ekkel definiálhatunk. Az ábra minden egyes jellemzőjéhez (pl. színek, rács, az adatok, stb.) tartozik egy kulcs-érték páros.

Ezeket a jellemzőket a plotly két kategóriába sorolja, a trace-ekbe és a layout-ba. A trace-ek olyan objektumok, melyek egy adatsort írnak le az ábrán, például egy Scatter vagy egy Heatmap objektum. Egy ábrán természetesen több trace is lehet, ha például kétféle adatsort is ábrázolni szeretnénk. A trace-ek emellett ábrákon belül is kombinálhatók, egyetlen ábrán megjeleníthetünk elszórt mérési pontokat és oszlopdiagramot is. A layout jellemzők pedig az ábra egészére vonatkozó formázási utasítások, például az ábra címe, a háttér színe, a tengelyfeliratok, illetve további annotációk (szövegek).

Az ábrakészítésben a plotly dokumentációja sok segítséget ad.


1. Trace-ek legyártása

Egy ábra definiálásakor tehát elsőként az adatokból le kell gyártanunk a megfelelő trace-eket. A konkrét példában egy olyan ábrát szeretnénk, ahol a fenti y_pontok array az x_pontok array függvényében van ábrázolva, és a pontok folytonos vonallal vannak összekötve. Ezt az alábbi utasítással tehetjük meg:

In [3]:
trace_sin_gorbe = graph_objs.Scatter(x=x_pontok, y=y_pontok, mode='lines')
trace_sin_gorbe
Out[3]:
{'mode': 'lines',
 'type': 'scatter',
 'x': array([ 0.        ,  0.6981317 ,  1.3962634 ,  2.0943951 ,  2.7925268 ,
         3.4906585 ,  4.1887902 ,  4.88692191,  5.58505361,  6.28318531]),
 'y': array([  0.00000000e+00,   6.42787610e-01,   9.84807753e-01,
          8.66025404e-01,   3.42020143e-01,  -3.42020143e-01,
         -8.66025404e-01,  -9.84807753e-01,  -6.42787610e-01,
         -2.44929360e-16])}

Itt tehát legyártottunk egy "gráf objektumot", mely most éppen Scatter típusú. A kiíratásból láthatjuk, hogy bár ezt, mint egy objektumot definiáltuk, valójában egy olyan dict, melyben a 'type' kulcshoz a 'scatter' érték tartozik. Tehát tulajdonképpen az alábbi módszer is működne a fenti trace definiálására, az objektumként való megadás csak a kényelmünket szolgálja.

In [4]:
trace_sin_gorbe = {'mode': 'lines',
                  'type' : 'scatter',
                  'x': x_pontok,
                  'y': y_pontok}

Arra viszont figyeljünk, hogy ha a dict-ként való megadást választjuk, akkor a kulcs-érték párokat kettősponttal válasszuk el, a gráf objektumként való megadás esetén pedig egyenlőségjellel.

A mode kulcshoz írt 'lines' azt jelenti, hogy az adatpontokat vonallal szeretnénk az ábrán összekötni. Ha csak be szeretnénk szórni a pontokat az ábrára, akkor ide írjuk a 'markers' kifejezést.

Végül tegyük be az adatok_sin_gorbe listába a legyártott trace objektumokat. Mivel ebben a példában csak egyetlen trace-re volt szükség, így ez elhagyható lenne, de több trace esetén az összeset célszerű egy listába összefűzni:

In [5]:
adatok_sin_gorbe = [trace_sin_gorbe]

2. A layout definiálása

Adjunk az ábránknak címet, a tengelyekre pedig rakjunk tengelyfeliratot. Ezt a Layout objektum specifikálásával tehetjük meg az alábbiak szerint:

In [6]:
layout_sin_gorbe = graph_objs.Layout(title='Ez az ábra címe',
                                    xaxis=graph_objs.XAxis(title='x'), 
                                    yaxis=graph_objs.YAxis(title='sin(x)'))
layout_sin_gorbe
Out[6]:
{'title': 'Ez az ábra címe',
 'xaxis': {'title': 'x'},
 'yaxis': {'title': 'sin(x)'}}

A Layout objektum xaxis változója egy XAxis objektum, melyben már a title változó értéke közvetlenül megadható. Hasonlóan járunk az yaxis változó esetén is. Látjuk azonban, hogy csakúgy mint a trace esetén, ezek az objektumok itt is helyettesíthetők egymásba ágyazott dict-ekkel:

In [7]:
layout_sin_gorbe = {'title': 'Ez az ábra címe',
                  'xaxis': {'title': 'x'},
                  'yaxis': {'title': 'sin(x)'}}

3. A Figure objektum legyártása

Miután az összes szükséges objektum elkészült (az adatokhoz a megfelelő trace-ek, az ábra formázásához pedig a Layout), ezeket összefűzve definiálhatjuk a Figure objektumot:

In [8]:
figure_sin_gorbe = graph_objs.Figure(data=adatok_sin_gorbe, layout=layout_sin_gorbe)
figure_sin_gorbe
Out[8]:
{'data': [{'mode': 'lines',
   'type': 'scatter',
   'x': array([ 0.        ,  0.6981317 ,  1.3962634 ,  2.0943951 ,  2.7925268 ,
           3.4906585 ,  4.1887902 ,  4.88692191,  5.58505361,  6.28318531]),
   'y': array([  0.00000000e+00,   6.42787610e-01,   9.84807753e-01,
            8.66025404e-01,   3.42020143e-01,  -3.42020143e-01,
           -8.66025404e-01,  -9.84807753e-01,  -6.42787610e-01,
           -2.44929360e-16])}],
 'layout': {'title': 'Ez az ábra címe',
  'xaxis': {'title': 'x'},
  'yaxis': {'title': 'sin(x)'}}}

A fentiekhez hasonlóan ez is "csak" egy egymásba ágyazott dict objektum lesz, melyet a hagyományos úton is definiálhattunk volna.


4. Az ábra kirajzoltatása

Az így elkészített Figure objektum már ábrázolható:

In [9]:
iplot(figure_sin_gorbe)

Összefoglalva:

A fentiek talán bonyolultnak tűnnek, de valójában néhány sorban el tudjuk készíteni a fenti ábrát. Láthattuk, hogy a gráfobjektumok definiálása mindig a graph_objs almodul használatával történik. Érdemes tehát az egész almodul összes függvényét importálni, így többé nem kell kiírnunk a graph_objs. részletet.

A fenti ábra persze nagyon "szögletes" egy valódi sin-görbének, de ezen könnyen segíthetünk: növeljük meg a mintavételezési pontok számát és ábrázoljuk újra, most azonban a cos-görbével együtt. Ekkor már két trace objektumot kell gyártanunk.

In [10]:
from plotly.graph_objs import *
In [11]:
x_pontok_uj = linspace(0,2*np.pi,50)
y_pontok_sin = sin(x_pontok_uj)
y_pontok_cos = cos(x_pontok_uj)
trace_sin_gorbe = Scatter(x=x_pontok_uj, y=y_pontok_sin, mode='lines')
trace_cos_gorbe = Scatter(x=x_pontok_uj, y=y_pontok_cos, mode='lines')
adatok = [trace_sin_gorbe, trace_cos_gorbe]
layout_sin_gorbe = Layout(title='Ez az új ábra címe',
                          xaxis=XAxis(title='x'), 
                          yaxis=YAxis(title='sin(x), cos(x)'))
figure_uj = Figure(data=adatok, layout=layout_sin_gorbe)
iplot(figure_uj)

Milyen szép sima görbéket kaptunk! Ellenőrizzük, hogy ez valóban így van-e! Próbáljunk az ábra jobb felső sarkában lévő gombokkal ráközelíteni egy-egy csúcsra. Látható, hogy ilyen skálán még ezek a görbék is szögletesek.

A plotly nagy előnye az ilyen jellegű interaktív nézegetési lehetőség. Ha egy ábrán nagyon sok mérési pont van, akkor csak kellően ráközelítve tudjuk őket megkülönböztetni egymástól. A jobb oldalon a trace 0 és trace 1 feliratok melletti vonalakra kattintva az aktuális görbe az ábráról ideiglenesen eltűntethető, majd ismételt kattintással újra megjeleníthető.

Oszlopdiagram készítése

Ha a fenti ábrát nem vonalakkal szeretnénk elkészíteni, hanem mondjuk a cos függvényt oszlopdiagrammal, csak a trace típusán kell változtatnunk:

In [12]:
trace_sin_gorbe = Scatter(x=x_pontok_uj, y=y_pontok_sin, mode='lines')
trace_cos_gorbe = Bar(x=x_pontok_uj, y=y_pontok_cos)
adatok = [trace_sin_gorbe, trace_cos_gorbe]
layout_sin_gorbe = Layout(title='Ez az oszlopos ábra címe',
                          xaxis=XAxis(title='x'), 
                          yaxis=YAxis(title='sin(x), cos(x)'))
figure_oszlop = Figure(data=adatok, layout=layout_sin_gorbe)
iplot(figure_oszlop)

3D-s ábra készítése

A matplotlib-es példák során megvizsgáltuk, hogy 3D-s görbéket, illetve felületeket hogyan lehet ábrázolni. Lássunk most pár példát arra, hogy a plotly segítségével hogyan tudunk a matplotlib-nél jóval reszponzívabb térbeli ábrákat készíteni. Első példaként készítsünk itt is egy spirált:

In [13]:
t=linspace(0,2*pi,100) # mintavételezési pontok és 3D koordináták elkészítése
xp=cos(3*t)            # a spirál paraméteres egyenlete alapján
yp=sin(3*t)
zp=t

Most nem az eddig használt Scatter objektumra van szükség, hanem ennek a 3D-s változatára, a Scatter3d-re.

In [14]:
trace = Scatter3d(x=xp, y=yp, z=zp, mode='lines') # Adatok csoportosítása trace-be
adatok = [trace]
layout3d=Layout(title='Spirál')
fig_3D = Figure(data=adatok,layout=layout3d) # ábra objektum létrehozása
iplot(fig_3D) # ábra megjelenítése

A következő háromdimenziós ábrához az ábrázolni kívánt pontok koordinátáit a plotly_3D.txt szövegfájlban találjuk. Az adatok innen származnak, hasonló ábrák készítéséhez hasznos adatfájlok itt találhatók.

Az adatfájlban az első oszlop az x, a második az y, a harmadik pedig a z koordináta. Elsőként olvassuk be az adatokat. Ehhez a loadtxt függvényt használjuk, mely a numpy modulban található.

In [15]:
data_file = loadtxt('data/plotly_3D.txt')
x_tengely = data_file[:,0]
y_tengely = data_file[:,1]
z_tengely = data_file[:,2]

Hasonlóan a spirál elkészítéséhez itt is definiáljuk a trace, layout és ábra objektumokat. Vajon milyen hatásai vannak a különböző opcióknak amiket a Scatter3d-nek és a Layout-nak megadunk ?

In [17]:
# trace objektum létrehozása
trace_3D = Scatter3d(x=x_tengely, y=y_tengely, z=z_tengely, mode='markers', marker = dict(size=2))
adatok_3D = [trace_3D]
#layout objektum létrehoozása
layout_3D = Layout(width=900,height=500,scene=dict(aspectmode='manual', aspectratio = dict(x=0.2, y=1, z=2/3)))
#ábra létrehozása és megjelenítése
fig_3D = Figure(data=adatok_3D, layout=layout_3D)
iplot(fig_3D)

Az utolsó példában azt vizsgáljuk meg, hogy a plotly segítségével hogyan ábrázolhatunk térbeli felületeket. Ehhez gyártsunk le először is egy egyszerű adatsort, ami egy kétdimenziós Gauss-görbét $z=e^{-x^2-y^2}$ mintavételez:

In [18]:
x,y=meshgrid(linspace(-5,5,50),linspace(-5,5,50)) # mintavételezés az x,y síkban
z=exp(-x**2-y**2) # z koordináták az x és y függvényében

A használt függvények és objektum létrehozó rutinok, melyeket az ábrák gyártásához használtunk, számos jól kigondolt alapértelmezett paraméterrel rendelkeznek, ezért ha csak gyorsan akarunk egy adatsort ábrázolni akkor az alábbi minimális egysoros konstrukció alkalmazható:

In [19]:
iplot(Figure(data=[Surface(x=x,y=y,z=z)]))