În urmă cu un an scriam despre ggplot2, cel mai popular pachet pentru vizualizarea datelor cu limbajul R. Funcționalitățile acestuia pot fi extinse cu alte pachete, care funcționează ca niște extensii. Este și cazul gganimate, pachet care la începutul acestui an a fost publicat în CRAN, arhiva oficială de pachete pentru R.
În acest articol voi prezenta gganimate și voi trece printr-un studiu de caz în care voi importa date din analytics, le voi prelucra în R, le vom vizualiza cu ggplot2 și apoi voi anima vizualizarea cu gganimate, la finalul articolului fiind disponibil și codul sursă pentru toate aceste etape.
Istoric
ggplot2 a apărut în 2005, dar au mai trecut 9 ani până s-a ajuns la versiunea 1.0.0. O schimbare fundamentală a venit în decembrie 2015, când s-a lansat versiunea 2.0.0, care a introdus conceptul de extensii, adică alte pachete, care extind funcționalitățile sale. În iulie 2018 s-a lansat ggplot2 3.0.0, ocazie cu care au fost introduse mai multe concepte noi, printre care tidy evaluation, simple features sau calculated aesthetics. Cea mai recentă versiune existentă în acest moment este 3.1.0.
R a avut, de asemenea, un an impotant în 2018, când s-a lansat versiunea 3.5, despre care se spune că ar fi cea mai importantă schimbare de la apariția limbajului. Pe lângă îmbunătățirile de performanță, trebuie menționată și introducerea unor meta-date pentru vectori, care fac operațiile cu aceștia mult mai rapide (lucru util în special la vectorii care conțin foarte multe date). Trecerea la această versiune (sau la una mai nouă – cea mai recentă fiind momentan 3.5.2), va necesita și pachete updatate.
gganimate a ajuns în CRAN doar la începutul acestui an, însă are o istorie mai lungă. A fost lansat de David Robinson la începutul lui 2016, deci la puțin timp după ce a apărut posibilitatea de a crea extensii de ggplot2. Proiectul a fost apoi preluat de Thomas Lin Pedersen, care l-a rescris complet. Așadar, codul pentru animații gganimate scris în urmă cu un an nu mai funcționează cu versiunea curentă. Am așteptat ca acest pachet să fie publicat în CRAN pentru a scrie despre el, întrucât înainte de asta instalarea lui era un coșmar și dădea foarte multe erori, lucru pe care îl știu din proprie experiență.
Cum funcționează gganimate
La fel ca ggplot2, și gganimate se bazează pe gramatica graficii, framework-ul lui Leland Wilkinson din 1999. De altfel, gganimate mai este numit și gramatica animațiilor.
Să vedem cele mai importante componente și funcții din gganimate:
- Tranziții – funcțiile de tipul
transition_*()
definesc datele care vor fi folosite în animații și cum se vor comporta în acest timp - Vizualizări – funcțiile
view_*()
definesc cum vom vedea animațiile; în general, ne putem juca cu axele x și y, astfel încât să se schimbe și partea din grafic pe care o vedem în timpul animației; acestea trebuie folosite cu grijă pentru că pot fi foarte dezorientative pentru audiență - Umbre – în pofida numelui, funcțiile de forma
shadow_*()
nu se ocupă de o simplă umbră pentru elementele din grafic, ci funcționează ca o memorie, astfel încât datele din cadrele anterioare să apară în continuare în grafic (se poate vedea în exemplul din acest articol cum punctele anterioare rămân și când apar altele noi) - Intrări / ieșiri – acestea sunt niște concepte noi care definesc cum să apară noile date și cum să dispară cele vechi, practic animând și aceste acțiuni, cu ajutorul funcțiilor
enter_*()
/exit_*()
ease_aes()
– o funcție specială care definește cum se vor comporta diferite elemente estetice în timpul tranzițiilor
gganimate va avea grijă să creeze suficiente cadre pentru ca tranziția să fie una lină. La finalul articolului găsiți detalii despre animația prezentată aici.
Animațiile suportă și inserarea de texte din pachetul glue, adică lângă graficul animat putem avea și un text care se schimbă odată cu imaginea (de exemplu, să schimbe data pentru care sunt afișate informațiile odată cu animația). Efectul este unul de impact dacă se folosește în titlu.
Studiu de caz
Observăm că datele sunt prima componentă atât pentru ggplot2, cât și pentru gganimate, lucru absolut firesc. Ca de obicei, am preferat să folosesc date importate din Google Analytics. Pentru importarea acestora în R, avem la dispoziție pachetul googleAnalyticsR.
Importul datelor din Google Analytics
În acest caz am folosit contul Demo oferit de Google. Aici apare prima problemă, pentru că Google nu oferă acces API pentru acest cont, motiv pentru care vom primi eroare. Din documentația Google:
Nu-i nimic, avem ocazia să vedem și altă metodă pentru importul datelor din analytics, mai ales că pe cea prin API am descris-o deja pas cu pas în articolul menționat anterior!
Google Analytics ne dă posibilitatea să creăm un raport customizat, exact cu datele de care avem nevoie, pe care apoi să îl exportăm în diverse formate. Pentru prelucrarea datelor este de preferat formatul CSV. Așa începe fișierul meu:
După cum se poate citi pe rândul 4, am exportat datele pentru ultimii 2 ani (2017 și 2018), deci valoarea medie a comenzii și numărul tranzacțiilor pentru 723 de zile consecutive.
Am importat datele din acest fișier în R folosind funcția read.csv
din pachetul de bază (adică nu am mai încărcat un alt pachet suplimentar pentru asta, chiar dacă unele au ceva mai multe opțiuni). R funcționează mai bine cu mai puține pachete încărcate, pentru că la rândul lor acestea pot apela alte fișiere, așa că am încărcat pachete suplimentare doar acolo unde a fost nevoie.
În ultimul argument al acestei funcții, am precizat că vreau să ignore primele 6 linii. În imaginea de mai sus se poate vedea că informațiile de acolo nu ne interesează.
Curățarea și prelucrarea datelor
În acest moment, avem tabelul de date în R, însă datele sunt departe de forma dorită pentru a le putea utiliza.
Curățarea și prelucrarea datelor sunt etapele care le ocupă cel mai mult timp specialiștilor în analytics și data science. Când afli asta, probabil că acest job nu ți se mai pare la fel de sexy ca atunci când citeai de modelarea datelor și machine learning 🙂 Desigur, este vorba despre cei care fac analize avansate și care pot folosi mai multe surse de date, nu de cei care stau doar în interfața de Google Analytics (eventual și a celorlalte produse din suită).
O primă problemă cu datele noastre o reprezintă modul în care Google denumește una dintre coloane: „Avg..Order.Value”. Toate acele puncte din interiorul denumirii ne pot pune probleme mai târziu, așa că încep curățarea prin a redenumi această coloană mai simplu, „AOV”. Pentru redenumirea coloanei am folosit funcția colnames()
, disponibilă tot în pachetul de bază.
Analizând tabelul nostru, observăm că a interpretat coloana cu datele ca fiind de tipul numeric (mai exact, valori întregi), așa că următorul pas este să schimbăm asta în dată, de formatul an (4 cifre)-lună-zi. Sunt multe moduri de a face asta, eu am folosit funcția parse_date_time()
din pachetul lubridate, pentru a face totul dintr-o singură operație.
În pasul următor voi crea valori diferite pentru an, lună și zi, pe lângă cea de dată, pentru că poate o să vreau să le folosesc pe acestea separat (pentru consistență cu celelalte nume de coloane, am folosit denumirile în engleză Year, Month, Day – majuscula este importantă pentru a le diferenția de funcțiile cu aceleași nume, funcții pe care le-am folosit chiar la aceste operații). În același pas, am decis să transform Year și Month în tipul factor, adică fiecare dintre cei 2 ani și fiecare dintre cele 12 luni să fie tratate drept categorie în funcție de care să pot afișa celelalte date.
Se poate merge mai departe și se pot adăuga coloane noi în tabel cu valorile noi create, dar pentru ce vreau eu să fac este suficient ce avem deja. Încerc să reduc la minim această etapă, pentru că subiectul acestui articol este totuși vizualizarea datelor. Dar înainte să ajungem acolo mai avem un singur pas la curățare…
Uitându-ne la tabelul nostru inițial, vedem cum valorile de la AOV încep cu semnul „$”. Din acest motiv, sunt interpretate greșit. Este o ocazie să folosim RegEx cu funcția gsub()
pentru a înlocui acel caracter cu… nimic (cam așa s-ar citi sintaxa respectivă). În aceeași linie de cod, vom transforma apoi valorile respective în numerice.
Grafice statice în ggplot2
De regulă, vizualizarea începe cu un grafic simplu, făcut pentru ca analistul să își facă o părere despre datele cu care lucrează. Așadar, voi crea un grafic cu evoluția în timp a celor 2 indicatori pe care îi avem în tabel: AOV și Transactions (numarul tranzacțiilor).
Ambii vor fi reprezentați cu linii și nu vreau să introduc la moment culori, forme sau alte elemente care ar complica graficul. Totuși, pentru a îi deosebi în imagine, voi folosi tipuri diferite de linii: pentru AOV voi avea linie tip arie, cu o ușoară transparență ca să se vadă peste aceasta și linia tranzacțiilor.
Observăm că ambii indicatori au scăzut în ultima jumătate a anului 2018. Există chiar o corelație între cei doi, așa că mai departe voi analiza mai atent doar unul dintre ei (mai exact, AOV).
Pentru a compara în funcție de diferite intervale de timp, voi afișa câte un punct pentru fiecare zi. Pentru a vedea ușor anii, fiecare dintre cei 2 ani va avea punctele cu altă culoare. Lunile (1 – 12) vor fi pe axa x. Vedeți acum de ce aveam nevoie să am anii și lunile de tipul categoric.
Putem vedea și mai bine trendul anterior, cu toate acele puncte albastre în partea de jos (ceea ce înseamnă AOV scăzut). Dar vedem în același timp că partea de sus a imaginii nu prea este folosită, în timp ce în partea de jos punctele sunt îngrămădite.
Pentru a corecta asta, voi filtra toate valorile AOV peste 300 și voi schimba proporțiile graficului (inițial, avea o formă verticală inserat în articol și vreau să schimb acest lucru). Rezultatul l-am denumit grafic_static și poate fi văzut mai jos.
ggplot2 ne va atenționa că am șters valorile pentru 12 zile, dar 12 din 723 chiar sunt irelevante și acest lucru ne va ajuta să le vedem pe celelalte mai bine. În cod voi lăsa doar ultima versiune a graficului, dar prima poate fi creat dacă se șterg ultimele două funcții, responsabile de limitele descrise anterior.
Se vede și mai clar acum că anul 2018 a început mai bine decât precedentul, dar lucrurile s-au schimbat din mai – iunie pentru restul anului.
Grafic animat în gganimate
Ajungem la cel mai așteptat pas: animația vizualizării de date.
Pentru asta, trebuie doar să apelăm graficul făcut anterior (grafic_static) și să îi adăugăm funcțiile dorite din gganimate.
Am folosit transition_reveal()
la nivel de zi, pentru ca punctele să apară în ordinea zilelor (toate pentru zilele de 1, apoi toate pentru zilele de 2 etc.).
Următoarea funcție este shadow_mark()
, care ne permite să păstrăm punctele anterioare în grafic, pe măsură ce animația evoluează. Aceeași funcție face ca unele puncte să se miște mai încet, creând efectul că ar fi lăsate în urmă.
În final, am apelat enter_grow()
, care face ca elementele din grafic să pară că ar crește. Efectul nu este foarte vizibil în cazul punctelor, dar arată mai bine decât fără el.
Pentru că tema blogului redimensionează imaginea, voi pune link și spre animație așa cum a fost ea exportată din R (refresh dacă browser-ul nu vă permite să reveniți altfel la articol).
La o privire atentă se pot vedea cele 31 de momente în care apar punctele (câte unul pentru fiecare zi a lunii).
De notat că am vrut să țin lucrurile simple. De exemplu, nu am umblat deloc la modul în care arată elementele din vizualizări. Totul poate fi customizat, însă eu le-am lăsat pe toate așa cum sunt ele implicit. ggplot2 și gganimate oferă mult mai multe posibilități de a face vizualizări de date interesante, dar dacă există interes voi reveni cu alte articole pe acest subiect.
Un grafic ca acesta are în jur de 100 de cadre. La mine au fost create cu o viteza de ~3 fps. Graficele mai complexe durează și mai mult, de exemplu animațiile bazate pe hărți pot fi de 10 ori mai încete. Dar acesta este un aspect care va fi îmbunătățit în următorul an la gganimate.
Codul sursă
Așa cum promiteam la început, voi finaliza articolul cu codul folosit pentru a face operațiile de mai sus, de la import și curățare date, la grafice statice și în final la animație.
# Încărcare pachete folosite
library(lubridate)
library(ggplot2)
library(gganimate)
# Import date
aov_data <- read.csv(file='Analytics 1 Master View Daily AOV & Transactions 20170101-20181231.csv', header = TRUE, sep = ',', skip = 6)
# Curățare și prelucrare date
colnames(aov_data)[2] <- 'AOV'
aov_data$Date <- parse_date_time(aov_data$Date, "Ymd")
Year <- as.factor(year(aov_data$Date))
Month <- as.factor(month(aov_data$Date))
Day <- day(aov_data$Date)
aov_data$AOV <- as.numeric(gsub("\\$", "", aov_data$AOV))
# Grafice statice
grafic_initial <- ggplot(aov_data, aes(x= Date, y = AOV)) + geom_area(alpha = 0.3) + geom_line(aes(y = Transactions))
grafic_initial
grafic_static <-ggplot(aov_data, aes(x= Month, y = AOV, color = Year)) + geom_point(alpha = 0.5) + ylim(0, 300) + coord_fixed(ratio = 0.02)
grafic_static
# Grafic animat
grafic_animat <- grafic_static + transition_reveal(Day)+ shadow_mark() + enter_grow()
grafic_animat