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 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 á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.)
In [11]:
from plotly import *
from plotly.offline import *
init_notebook_mode()
In [2]:
import numpy as np
In [3]:
x_pontok = np.linspace(0,2*np.pi,10)
y_pontok = np.sin(x_pontok)
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 [4]:
trace_sin_gorbe = graph_objs.Scatter(x=x_pontok, y=y_pontok, mode='lines')
trace_sin_gorbe
Out[4]:
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 [5]:
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 [6]:
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 [7]:
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[7]:
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 [8]:
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 [9]:
figure_sin_gorbe = graph_objs.Figure(data=adatok_sin_gorbe, layout=layout_sin_gorbe)
figure_sin_gorbe
Out[9]:
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 [10]:
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 [11]:
from plotly.graph_objs import *
In [12]:
x_pontok_uj = np.linspace(0,2*np.pi,50)
y_pontok_sin = np.sin(x_pontok_uj)
y_pontok_cos = np.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ő.
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 [13]:
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)
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 [14]:
data_file = np.loadtxt('data/plotly_3D.txt')
x_tengely = data_file[:,0]
y_tengely = data_file[:,1]
z_tengely = data_file[:,2]
Ezek után már csak az ábrázolás van hátra. Most nem az eddig használt Scatter
objektumra van szükség, hanem ennek a 3D-s változatára, a Scatter3d
-re.
In [15]:
trace_3D = Scatter3d(x=x_tengely, y=y_tengely, z=z_tengely, mode='markers', marker = dict(size=2))
adatok_3D = [trace_3D]
layout_3D = Layout(width=900,height=500,scene=dict(aspectmode='manual', aspectratio = dict(x=0.2, y=1, z=2/3)))
fig_3D = Figure(data=adatok_3D, layout=layout_3D)
iplot(fig_3D)
Nagyítsuk, forgassuk kedvünkre az ábrát. Próbáljuk meg kitalálni, hogy mit jelenthet a Layout
objektumon belül szereplő aspectratio
kulcsszó.
Ábrázoljunk scatter pontokkal egy $r=3$ egység sugarú kört, a pontok színét aszerint választva, hogy melyik síknegyedben van a pont.
A kör ábrázolásához elsőként le kell gyártani azokat az $(x,y)$ koordinátapárokat, melyek majd a kört kirajzolják. Ennek a legegyszerűbb módja az alábbi paraméterezés:
$$x = r\cos(\phi)$$$$y = r\sin(\phi)$$Ahol $\phi$ az x-tengelytől mért szög. Így a $\phi$ értékeit egyenletesen megválasztva a $(0,2\pi)$ intervallumon, az $x$ és $y$ értékeit tartalmazó array-ek a fentiek szerint adódnak:
In [16]:
r = 3
phi = np.linspace(0,2*np.pi,1000)
x = r*np.cos(phi)
y = r*np.sin(phi)
Attól függően, hogy az adott $x$ és $y$ milyen előjelű, 4-féle kategóriába sorolhatók a pontok. (Másként fogalmazva attól függően, hogy a pont melyik síknegyedbe esik.) A tengelyeket mindig a tőlük az óramutató járásával megegyező irányba eső síknegyedhez soroljuk.
Hogy ezeket különböző színnel tudjuk ábrázolni, egy lehetséges megoldás, hogy négy különböző trace
objektumot generálunk az alábbiak szerint:
In [17]:
trace_1 = Scatter(x=x[(x<0) * (y<0)], y=y[(x<0) * (y<0)], mode='lines', marker=dict(color='red'))
trace_2 = Scatter(x=x[(x>=0) * (y<0)], y=y[(x>=0) * (y<0)], mode='lines', marker=dict(color='blue'))
trace_3 = Scatter(x=x[(x<0) * (y>=0)], y=y[(x<0) * (y>=0)], mode='lines', marker=dict(color='green'))
trace_4 = Scatter(x=x[(x>=0) * (y>=0)], y=y[(x>=0) * (y>=0)], mode='lines', marker=dict(color='orange'))
data = [trace_1, trace_2, trace_3, trace_4]
layout = Layout(title='Ez egy színes kör', width=500, height=500)
fig = Figure(data=data, layout=layout)
iplot(fig)