Bizonyára sokan találkoztak már azzal a problémával, hogy a készülő publikációjukhoz egy olyan speciális ábrára lett volna szükségük, aminek előállítására az Excel vagy a Word nem volt képes. Ilyen esetekre kínál megoldást az R ggplot2 csomagja.

Maga a ggplot2 alapcsomag és az arra épülő számos kiegészítő csomag egy olyan vizualizációs ökoszisztémát alkot, amely egy szabványosított interfészen keresztül programozottan, ugyanakkor a végletekig személyre szabható módon teszi lehetővé a különböző diagramok, ábrák elkészítését. Ennek komplex bemutatására nyilvánvalóan nem lehet elegendő egyetlen poszt és erre nem is törekszem. Csakúgy, mint a karácsonyfa-diagramnál, most is egy gyakorlati példán keresztül szeretném érzékeltetni a ggplot2 sokoldalúságát.

A feladat jelen esetben egy olyan oszlopdiagram-szerű ábra előállítása, amelyen az egyes oszlopok egy-egy valóságban is létező könyv gerincének megjelenését imitálják, emellett arányosak a kérdéses könyvek fizikai méretével.

Ennek a posztnak a lényege azonban nem a konkrét ábra, hanem a gyakorlási lehetőség. A ggplot2 első megközelítésre sajnos baromira bonyolultnak tűnik. A működése és annak finomságai csak úgy sajátíthatók el készségszinten, ha az ember folyamatosan kísérletezget vele. Könyvespolc-diagramot általában nem készítünk, de a megoldás összetevőit a későbbiekben más, értelmes munkák során hasznosítani lehet.

A könyvespolc tartalma

Mielőtt a technikai részletekbe belevágnánk, elsőként tekintsük tehát át, hogy konkrétan milyen könyvekről van szó! Az elkészítendő virtuális könyvespolcra olyan munkákat válogattam ki, amelyeket a nyár folyamán szeretnék majd elolvasni.

A könyvespolc tartalma

A következő nyolc könyvről van szó:

  • Mivel a saját kutatási területem a magyar főnemesség társadalomtörténetéhez kapcsolódik, így az első két könyv emiatt érdekes a számomra. Ebből a szempontból Szemethy Tamás: Katonabárók és hivatalnok grófok. Új arisztokraták a 18. századi Magyarországon, illetve Melkovics Tamás: A reformkori főrendi ellenzék és 1839-40-es zászlóbontása című munkájára esett a választásom.

  • A következő két kötet a Hajnal István Kör augusztusi konferenciájára való felkészülés miatt kell nekem, ahol az ókígyósi Wenckheim-kastély vendégkönyvéből kibontakozó hálózatokról tartok majd előadást. Emiatt érzem fontosnak Szilágyi Adrienn: Az uradalom elvesztése. Nemesi családok a 19. századi Békés megyében, valamint Sisa József: Kastélyépítészet és kastélykultúra Magyarországon. A historizmus kora című kötetét.

  • Ha csak úgy olvasgatok, akkor általában a 20. századi magyar történelemre esik a választásom. Ebből a korszakból két egri kollégám közelmúltban megjelent könyvét szemeltem ki magamnak: Valuch Tibor: Mindennapi történeteink. Válogatott társadalomtörténeti tanulmányok, valamint Fábián Máté: Egy fajvédő főispán a Horthy-korszakban. Borbély-Maczky Emil 1887–1945 című monográfiáját.

  • Végezetül két módszertani jellegű munka is helyet kapott a listán. Az információk vizuális ábrázolásának elrettentő példáiba és jó gyakorlataiba vezeti be az olvasót Alberto Cairo a How Charts Lie. Getting Smarter about Visual Information című kötete, míg a tudománymetria újszerű megközelítésével találkozhatunk Dashun Wang és Barabási Albert László A tudomány tudománya címet viselő könyvében.

Ezeket a köteteket akarom tehát elhelyezni a virtuális könyvespolcon. Itt külön is szeretnék köszönetet mondani Szemethy Tamásnak, aki nemcsak készségesen válaszolt a könyve fizikai méreteit firtató furcsa kérdéseimre, hanem pár nappal később meg is ajándékozott egy példánnyal. Így már fizikailag is felkerülhetett a könyvespolcomra. 😁 És persze ugyancsak köszönöm Fábián Máté, Szilágyi Adrienn és Valuch Tibor korábbi hasonló gesztusát is.

A diagram technikai megvalósítása

Egy ggplot2-vel készült ábra felépítését talán annak analógiájára lehet a legjobban elképzelni, mintha átlátszó fóliákra rajzolnánk vagy írnánk, majd ezeket a fóliákat egy adott sorrendben egymásra rétegeznénk. Ebben a rendszerben a fentebb lévő rétegek rajzolatai értelemszerűen kitakarhatják az alattuk lévő rétegek egyes részleteit. A diagram végső megjelenése a rétegek összességéből bontakozik ki.

A lényegét tekintve a megvalósítás abból áll majd, hogy fokozatosan újabb és újabb rétegeket adok a diagramhoz, egészen addig, amíg el nem érem a kívánt vizuális hatást.

A továbbiakban öt lépésben fogok végig menni a diagram technikai megvalósításának folyamatán.

ggplot bookshelf

A diagram megvalósításának lépései (kattintásra nagyítható)

Az alábbiakban R nyelven (v4.0.3) írt kódot használok a feladat végrehajtásához. A magyarázó szövegek közé ékelt fekete kódblokkok tartalmát az RStudio-ban egymás alá illesztve elvileg bárki által reprodukálható az itt bemutatott műveletsor. A kódblokkok # kezdetű sorai pusztán magyarázó funkcióval bírnak, ezekre a program futtatásakor nincs szükség.

Elsőként tekintsük át, hogy milyen csomagokra lesz szükségünk és töltsük be ezeket!

  • A ggplot2 teszi lehetővé a tulajdonképpeni diagram létrehozását.

  • Az extrafont csomaggal a számítógépünkre feltelepített betűtípusok összességéből választhatjuk ki azokat, amelyekkel feliratozni akarjuk a könyvek gerincét. (A ggplot2 alapértelmezés szerint egy minimalista sans-serif betűtípust használ.) Első használatkor a font_import() paranccsal kell indítani, aminek végrehajtása több percig is eltarthat. A későbbiekben viszont erre már nincsen szükség.
    Megjegyzendő, hogy az általam készített ábrával pontosan megegyező kinézethez természetesen az olvasó gépén is telepítve kell legyenek a kódban említett betűtípusok. Amennyiben ez nem így lenne, akkor a megfelelő helyeken át kell írni a kódot a rendelkezésre álló fontoknak megfelelően!

  • Végül a png és a grid csomag a PNG típusú képek beolvasását teszi lehetővé.

# A szükséges csomagok betöltése.
# A legelső használat előtt az install.packages("...") utasítással telepíteni
# kell ezeket. A ... helyére az adott csomag neve írandó.
library(ggplot2)
library(extrafont)
# font_import() 
# Csak az első alkalommal futtatandó! Ekkor a # jelet ki kell venni a sor elejéről!
library(png)
library(grid)

Hozzuk létre a munkakönyvtárat is, majd töltsük le ide és csomagoljuk ki a furcsa-diagramok-2-konyvespolc.rar fájlt! Ebben 4 darab PNG kiterjesztésű képet találunk.

# Ide a saját munkakönyvtárunk elérését kell beírni!
setwd("C:/Munkakönyvtár")

1. A könyvek gerincének megrajzolása

Ahogy a fenti ábrán látható, a könyvek vizuális megjelenésének alapját színes négyszögek alkotják. Ezeket a geom_rect() réteggel tudjuk hozzáadni a diagramunkhoz. Bemenetként egy adatkeretet vár tőlünk, amelynek mezői a réteg paramétereiként funkcionálnak majd.

Paraméter Magyarázat
xmin a négyszög bal alsó sarkának x koordinátája (numerikus érték)
ymin a négyszög bal alsó sarkának y koordinátája (numerikus érték)
xmax a négyszög jobb felső sarkának x koordinátája (numerikus érték)
ymax a négyszög jobb felső sarkának y koordinátája (numerikus érték)
fill a négyszög kitöltésének színe* (karakterlánc)
color a négyszög körvonalának színe* (karakterlánc)

A könnyebb áttekinthetőség érdekében az alábbiakban minden könyvnek önálló adatkeretet hozok létre. Ezeket az rbind() függvénnyel fűzöm össze a rect elnevezésű adatkeretté.

rect <- rbind(
  szemethy = data.frame(
    "xmin" = 0,
    "ymin" = 0,
    "xmax" = 30,
    "ymax" = 243,
    "fill" = c("#4f6573",NA),
    "color" = c(NA,"#000000")
  ),
  melkovics = data.frame(
    "xmin" = 30,
    "ymin" = c(0,16,0),
    "xmax" = 55,
    "ymax" = c(240,224,240),
    "fill" = c("#65442a","#f1d4a7",NA),
    "color" = c(NA,NA,"#000000")
  ),
  szilagyi = data.frame(
    "xmin" = 55,
    "ymin" = c(0,60,0),
    "xmax" = 80,
    "ymax" = c(245,90,245),
    "fill" = c("#828284","#ffffff",NA),
    "color" = c(NA,NA,"#000000")
  ),
  sisa = data.frame(
    "xmin" = 80,
    "ymin" = 0,
    "xmax" = 110,
    "ymax" = 250,
    "fill" = c("#294072",NA),
    "color" = c(NA,"#000000")
  ),
  valuch = data.frame(
    "xmin" = 110,
    "ymin" = c(0,153,170,0),
    "xmax" = 125,
    "ymax" = c(238,170,238,238),
    "fill" = c("#422f28","#9b332a","#ffffff",NA),
    "color" = c(NA,NA,NA,"#000000")
  ),
  fabian = data.frame(
    "xmin" = 125,
    "ymin" = 0,
    "xmax" = 142,
    "ymax" = c(200,70,200),
    "fill" = c("#ffffff","#664d48",NA),
    "color" = c(NA,NA,"#000000")
  ),
  cairo = data.frame(
    "xmin" = c(142,142,142,152,145,155,142),
    "ymin" = c(0,115,115,115,110,110,0),
    "xmax" = c(164,164,148,158,151,161,164),
    "ymax" = c(115,243,121,127,131,117,243),
    "fill" = c("#01b1ec","#02a2dc","#017eac","#017eac","#df202f","#ffd124",NA),
    "color" = c(NA,NA,NA,NA,NA,NA,"#000000")
  ),
  dashunbarabasi = data.frame(
    "xmin" = 164,
    "ymin" = 0,
    "xmax" = 194,
    "ymax" = 230,
    "fill" = c("#241f23",NA),
    "color" = c(NA,"#000000")
  )
)

A geom_rect() réteg első, data nevű paramétere a rect néven elmentett adatkeret. A vizualizálandó elemek megjelenítésének tulajdonságait a mapping paraméterben, az aes() (aesthetic) függvénybe csomagolva kell megadnunk. Itt a réteg által elvárt, a fenti táblázatban bemutatott paraméterekhez hozzárendeljük az adatkeret megfelelő – történetesen azonos nevet viselő – mezőit. Magát a diagramot a konyvespolc nevű változóban raktározzuk el.

konyvespolc <- ggplot() +
  geom_rect(data = rect, mapping = aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = fill, color = color), show.legend = F)

Mielőtt továbbmennénk, érdemes kitérni a színek megadásának módjára. Ez elsőre talán egy kicsit bonyolultnak fog hangozni, de ez az ára a diagramok teljes körű testreszabhatóságának. (Amit ebben a posztban éppen csak kóstolgatunk.)

A fill és a color paraméterekben látszólag direktben vannak megadva a színek, hiszen a háttérben álló adatkeret fill és color mezői így tartalmazzák ezeket, hexadecimális formátumban. Valójában azonban az aes() függvény számára nem a konkrét színek az érdekesek. Itt egy kategóriaképzésről van szó, ahol az egymástól különböző értékek önálló kategóriákat alkotnak. A kategorizáláshoz történetesen a színek kódjait használtam fel, de egyébként bármilyen más karakterlánccal is működne a dolog. A ggplot2 az egyforma értékekhez tartozó vizuális elemeket egyformára, a különbözőeket különbözőre fogja színezni. A fill és a color mezők tartalma ennyit és nem többet jelent.

Hogy pontosan milyen színnel akarjuk látni az ábrázolandó elemeket, azt a scale_fill_manual() és a scale_color_manual() rétegek hozzáadásával tudjuk manuálisan szabályozni. Itt a values paraméterben egy névvel ellátott vektorban kell megadni, hogy az adatkeretünk fill és color mezőiben szereplő értékek milyen színeknek felelnek meg. Vagyis minden egyes értékhez hozzárendelünk egy-egy konkrét színt. Jelen esetben saját magát.

colors <- unique(na.omit(c(rect$fill, rect$color)))
colors <- setNames(colors, colors)

Ezt követően már összerakhatjuk a diagramunk első részét, amelyet lefuttatva a munkafolyamatot áttekintő ábra 1. részén látható eredményt kapjuk. (Kapnánk, ha lefuttatnánk.)

konyvespolc <- konyvespolc +
  scale_fill_manual(values = colors) +
  scale_color_manual(values = colors)

2. A vonalas díszítmények hozzáadása

Két könyv gerincén vonalas díszítmények is vannak. Ezeket alapvetően a geom_segment() rétegben, ugyancsak egy adatkeret tartalmára támaszkodva lehetne felvarázsolni a diagramra. Nálam azonban valami miatt nem szerette ezt a megoldást a ggplot2. Ezért végül az annotate() függvény segítségével, az adatokat ennek paramétereiként direktben megadva helyeztem fel a vonalakat a diagramra.

Az R nyelv igen rugalmas. Ugyanazt a dolgot általában többféle módon is megvalósíthatjuk.

A függvény első paramétere a “segment” karakterlánc, utalva arra, hogy a geom_segment() réteget helyettesíti. A többi paraméter jelentése leolvasható az alábbi táblázatból.

Paraméter Magyarázat
x a vonal kezdetének x koordinátája (numerikus érték)
y a vonal kezdetének y koordinátája (numerikus érték)
xend a vonal végének x koordinátája (numerikus érték)
yend a vonal végének y koordinátája (numerikus érték)
color a vonal színe (karakterlánc)
linetype a vonal típusa (karakterlánc)
size a vonal vastagsága (numerikus érték)

Az annotate() függvény paraméterei egyedi értékként és vektorként is megadhatók. Utóbbival egyszerre több vonalat is definiálni tudunk egyazon függvényen belül.

Én az alábbiakban két darab függvényt használok, hogy egyértelműen elkülöníthető legyen egymástól a két érintett könyv. Az elsőben két pontvonalat, a másodikban pedig egyetlen sima vonalat adok a konyvespolc néven elmentett ábrához.

konyvespolc <- konyvespolc +
  annotate("segment", 
           x = 30,
           y = c(18,222),
           xend = 55,
           yend = c(18,222),
           color = "#65442a",
           linetype = "dotted",
           size = 1.3) +
  annotate("segment",
           x = 130,
           y = 122,
           xend = 137,
           yend = 122,
           color = "#000000",
           linetype = "solid",
           size = 1
  )

3. A raszteres díszítmények hozzáadása

A következő lépésben a könyvek gerincét díszítő logókat helyezem fel az ábrára. A jelenlegi feladattól elvonatkoztatva ez azt jelenti tehát, hogy PNG kiterjesztésű raszteres képeket is hozzá tudunk adni egy diagramhoz.

Elsőként olvassuk be az R-be a munkakönyvtárunkba korábban letöltött és kicsomagolt 4 darab képet a png és a grid csomag segítségével.

szemethy <- readPNG("szemethy.png")
szemethy <- rasterGrob(szemethy, interpolate = T)

sisa <- readPNG("sisa.png")
sisa <- rasterGrob(sisa, interpolate = T)

szilagyi1 <- readPNG("szilagyi1.png")
szilagyi1 <- rasterGrob(szilagyi1, interpolate = T)

szilagyi2 <- readPNG("szilagyi2.png")
szilagyi2 <- rasterGrob(szilagyi2, interpolate = T)

Ezeket az annotate_custom() függvénnyel adjuk hozzá a konyvespolc változóban tárolt diagramhoz. Itt nem használhatunk vektorokat, minden képet külön kell megadni. A függvény első paramétere a beolvasott képet tartalmazó változó neve, a többi magyarázata pedig a táblázatban látható.

Paraméter Magyarázat
xmin a kép bal alsó sarkának x koordinátája (numerikus érték)
ymin a kép bal alsó sarkának y koordinátája (numerikus érték)
xmax a kép jobb felső sarkának x koordinátája (numerikus érték)
ymax a kép jobb felső sarkának y koordinátája (numerikus érték)

konyvespolc <- konyvespolc +
  annotation_custom(szemethy, 
                    xmin = 4, 
                    ymin = 20, 
                    xmax = 25, 
                    ymax = 35) +
  annotation_custom(szilagyi1, 
                    xmin = 58, 
                    ymin = 63, 
                    xmax = 77, 
                    ymax = 87) +
  annotation_custom(szilagyi2, 
                    xmin = 60, 
                    ymin = 228, 
                    xmax = 75, 
                    ymax = 235) +
  annotation_custom(sisa, 
                    xmin = 90, 
                    ymin = 15, 
                    xmax = 100, 
                    ymax = 27)

4. A feliratok hozzáadása

A feliratok esetében sajnos ugyanaz a helyzet, mint a vonalas díszítményeknél: a geom_text() réteggel és a hátterében lévő adatkerettel valami miatt nem volt képes megbírkózni a rendszer és folyton szétesett az ábra. Ezért most is az annotate() függvényt fogom használni. Ennek paraméterei vektorosan is megadhatók. Ezt a tulajdonságot kihasználva, a feliratok számától függetlenül könyvenként egy darab függvényt definiálok.

A függvény első paramétere a “text” karakterlánc, a továbbiak pedig az alábbi táblában felsorolva szerepelnek.

Paraméter Magyarázat
x a szöveg elhelyezkedésének x koordinátája (numerikus érték)
y a szöveg elhelyezkedésének y koordinátája (numerikus érték)
label maga a szöveg (karakterlánc)
hjust a szöveg vízszintes igazítása (numerikus érték)
vjust a szöveg függőleges igazítása (numerikus érték)
angle a szöveg elforgatásának szöge (numerikus érték)
family a szöveg betűtípusa (karakterlánc)
size a szöveg mérete (numerikus érték)
fontface a szöveg kiemelésének módja (karakterlánc)
color a szöveg színe (karakterlánc)

A szöveg igazításánál általában 0 és 1 közötti értékeket használunk, ahol a két szélsőség a bal és a jobb oldalra, illetve a fentre és a lentre való igazítást jelenti. 0.5 esetén a szöveg közepe pontosan a paraméterként megadott koordinátára esik. Ugyanakkor használhatunk az intervallumból kilógó értékeket is, amivel nagyobb mértékű eltolást lehet elérni.

A family paraméternek csak akkor van hatása, ha az extrafont csomagot betöltöttük. A fontface paraméter esetén a “plain”, “bold”, “italic” és “bold.italic” értékeket használhatjuk.

konyvespolc <- konyvespolc +
  annotate("text", 
           x = 15,
           y = c(85,160), 
           label = c("Szemethy Tamás","KATONABÁRÓK ÉS\nHIVATALNOK GRÓFOK"), 
           hjust = 0.5,
           vjust = 0.5,
           angle = 90,
           family = c("Calibri","Cambria"),
           size = 10,
           fontface = c("plain","bold"),
           color = "#ffffff") +
  annotate("text", 
           x = 42.5,
           y = 210, 
           label = "Melkovics Tamás\nA REFORMKORI FŐRENDI ELENZÉK\nÉS 1839-40-ES ZÁSZLÓBONTÁSA", 
           hjust = 1,
           vjust = 0.5,
           angle = 90,
           family = "Cambria",
           size = 7,
           color = "#000000") +
  annotate("text", 
           x = 67.5,
           y = c(14,100), 
           label = c("Szilágyi Adrienn","Az uradalom elvesztése"), 
           hjust = 0,
           vjust = 0.5,
           angle = 90,
           family = c("Calibri","Rockwell"),
           size = c(8,10),
           fontface = "bold",
           color = "#ffffff") +
  annotate("text", 
           x = 95,
           y = 40, 
           label = "Sisa József     ~::~     KASTÉLYÉPÍTÉSZET ÉS KASTÉLYKULTÚRA MAGYARORSZÁGON", 
           hjust = 0,
           vjust = 0.5,
           angle = 90,
           family = "Cambria",
           size = 8,
           color = "#e0de97") +
  annotate("text", 
           x = 117.5,
           y = c(35,80),
           label = c("Valuch Tibor","Mindennapi történeteink"),
           hjust = 0,
           vjust = 0.5,
           angle = 90,
           family = "Bahnschrift",
           size = 9,
           color = c("#ffffff","#9b332a")) +
  annotate("text", 
           x = 133.5,
           y = c(42,76,130),
           label = c("FÁBIÁN MÁTÉ","EGY FAJVÉDŐ FŐISPÁN\nA HORTHY-KORSZAKBAN","BORBÉLY-MACZKY EMIL (1887-1945)"),
           hjust = 0,
           vjust = 0.5,
           angle = 90,
           family = "Arial Nova Light",
           size = 5,
           color = c("#ffffff","#000000","#000000")) +
  annotate("text", 
           x = 153,
           y = c(30,30,185,80),
           label = c("Cairo","Alberto","How Charts Lie","Getting Smarter about\nVisual Information"),
           hjust = 0.5,
           vjust = c(1.2,-0.2,0.5,0.5),
           angle = 270,
           family = "Calibri",
           size = c(12,12,22,7),
           fontface = "bold",
           color = "#ffffff") +
  annotate("text", 
           x = 179,
           y = c(75,75,185,185,15),
           label = c("DASHUN WANG","BARABÁSI ALBERT-LÁSZLÓ","A TUDOMÁNY","TUDOMÁNYA","L"),
           hjust = 0.5,
           vjust = c(-0.2,1.2,-0.2,1.2,0.5),
           angle = c(90,90,90,90,0),
           family = "Arial Narrow",
           size = c(10,10,17,17,13),
           fontface = c("bold","bold","bold","bold","plain"),
           color = c("#ffffff","#ffffff","#07b3db","#07b3db","#ffffff"))

5. A diagram feliratainak felhelyezése és formázása

Végezetül a diagramot ellátom néhány felirattal és megformázom azokat.

konyvespolc <- konyvespolc +
  labs(x = "A könyvespolc szélessége (mm)", 
       y = "A könyvespolc magassága (mm)",
       caption = "https://aprogramozotortenesz.hu/") +
  theme(
    axis.text = element_text(size = 12),
    axis.title = element_text(size = 15),
    plot.caption = element_text(size = 15, face = "bold")
  )

Magát a diagramot a konyvespolc változóban tároltuk el. Ezt a konzolba beírva elővarázsolhatjuk a kész ábrát. Íme!

konyvespolc

ggplot bookshelf

Egy furcsa diagram (kattintásra nagyítható)

Kellemes nyarat kívánok mindenkinek! 🕶️🌞🚣