Introducere în vizualizarea datelor: Cum diferă clima de la munte față de cea de la câmpie?

În unele cazuri, precum cele din lecția anterioară, un răspuns numeric este de ajuns. Dar o imagine face cat o mie de cuvinte: cheia prezentării datelor este folosirea unor diagrame corespunzătoare.

Pentru vizualizare, vom folosi biblioteca Matplotlib. Vom continua analiza acelorlași date climatice, dar vom extinde analiza pe o perioadă mai lungă. De asemenea, vom introduce mai multe tipuri de vizualizări și vom descrie unele măsurători statistice.

Prelucrarea inițială a datelor este aproape identică cu cea din lecția trecută - va fi reprodusă cu minime detalii.

In [20]:
# Importăm bibliotecile pe care le vom folosi
import pandas as pd
import matplotlib.pyplot as plt
In [21]:
# Citim datale și le convertim la tipurile corecte
climatic_2016 = pd.read_csv('Data/RoGovClimatic/climrbsn2016.csv')
climatic_2016 = climatic_2016.astype({'ALT': 'float32', 'TMED': 'float32', 'TMIN': 'float32', 'TMAX': 'float32', 'R24': 'float32', 'DATCLIM': 'datetime64', 'CODST': 'category'})

# Separăm informațiile despre stațiile meteo
statii_meteo = climatic_2016[['CODST', 'ALT', 'LAT', 'LON']]
statii_meteo = statii_meteo.drop_duplicates().set_index('CODST')
climatic_2016 = climatic_2016.drop(columns=['ALT', 'LAT', 'LON'])

# Adaugăm numele stațiilor la tabelul creat anterior
nume_statii_str = '15015-Ocna Sugatag, 15020-Botosani, 15090-Iasi, 15108-Ceahlau Toaca, 15120-Cluj-Napoca, 15150-Bacau, 15170-Miercurea Ciuc, 15200-Arad, 15230-Deva, 15260-Sibiu, 15280-Varfu Omu, 15292-Caransebes, 15310-Galati, 15335-Tulcea, 15346-Ramnicu Valcea, 15350-Buzau, 15360-Sulina, 15410-Drobeta Turnu Severin, 15420-Bucuresti-Baneasa, 15450-Craiova, 15460-Calarasi, 15470-Rosiorii de Vede, 15480-Constanta'

# Împărțim string-ul într-o listă de string-uri
nume_statii_list = [x.split('-', 1) for x in nume_statii_str.split(', ')]
nume_statii = pd.DataFrame(nume_statii_list, columns=['CODST', 'Nume']).astype({'CODST': 'int64'}).set_index('CODST')

statii_meteo = statii_meteo.join(nume_statii, on='CODST')
statii_meteo.index = statii_meteo.index.astype('category')

Care a fost temperatura minimă în 2016, înregistrată în fiecare stație meteorologică?

În lecția trecută, am aflat că temperatura minimă din anul 2016 a fost înregistrată de stația meteo de pe Vârfu Omu. Ar fi interesant să comparăm care a fost minima înregistrată de fiecare stație.

Pentru asta, trebuie să grupăm (conceptual) observațiile după stația meteorologică în care au fost făcute și să căutăm valoarea minimă în aceste grupuri. Gruparea este facută de funcția groupby, ce returnează un obiect de tip DataFrameGroupBy. El conține informații despre modul în care liniile unui dataset sunt grupate. După grupare, se pot aplica diferite funcții de agregare, precum sum, min, max, mean, etc (vezi documentația pentru DataFrameGroupBy și metodele oferite). Aceste metode sunt aplicate pe fiecare coloană din fiecare grup - trebuie să selectăm coloana TMIN, apoi să facem un join cu tabelul cu informațiile statiilor:

In [22]:
statii_meteo_minime = statii_meteo.join(climatic_2016.groupby('CODST').min().TMIN)

statii_meteo_minime
Out[22]:
ALT LAT LON Nume TMIN
CODST
15015 503.000000 47.776944 23.940556 Ocna Sugatag -16.600000
15020 161.000000 47.735556 26.645556 Botosani -17.400000
15090 74.290001 47.163333 27.627222 Iasi -17.100000
15108 1897.000000 46.977500 25.950000 Ceahlau Toaca -23.799999
15120 410.000000 46.777778 23.571389 Cluj-Napoca -14.700000
15150 174.000000 46.557778 26.896667 Bacau -14.700000
15170 661.000000 46.371389 25.772500 Miercurea Ciuc -22.200001
15200 116.589996 46.133611 21.353611 Arad -13.200000
15230 240.000000 45.865000 22.898889 Deva -16.000000
15260 443.000000 45.789444 24.091389 Sibiu -22.600000
15280 2504.000000 45.445833 25.456667 Varfu Omu -24.000000
15292 241.000000 45.416667 22.229167 Caransebes -13.900000
15310 69.000000 45.473056 28.032222 Galati -14.600000
15335 4.360000 45.190556 28.824167 Tulcea -16.000000
15346 237.000000 45.088889 24.362778 Ramnicu Valcea -14.100000
15350 97.000000 45.132778 26.851667 Buzau -15.800000
15360 12.690000 45.162222 29.726944 Sulina -13.100000
15410 77.000000 44.626389 22.626111 Drobeta Turnu Severin -11.900000
15420 90.000000 44.510556 26.078056 Bucuresti-Baneasa -22.000000
15450 192.000000 44.310278 23.866944 Craiova -18.100000
15460 18.719999 44.205833 27.338333 Calarasi -19.100000
15470 102.150002 44.107222 24.978611 Rosiorii de Vede -23.100000
15480 12.800000 44.213889 28.645556 Constanta -11.700000

Datele numerice sunt interesante, dar greu comparat vizual. Ar fi de preferat să avem o vizualizare a acestor date. În cazul nostru, ideal ar fi să folosim un barplot.

Acest tip de vizualizare este ideală pentru a compara vizual valori similare ale mai multor categorii (tipul de date category ne dă un indiciu). Marele său avantaj este usurința de a fi înțeles - nu necesită nicio explicație.

In [23]:
# Creem o figură Matplotlib
plt.figure(figsize=(30,5))

# Generăm barchart-ul
# Pe axa orizontală, vrem numele stațiilor, iar pe axa verticală, temperaturile minime
plt.bar(statii_meteo_minime.Nume, statii_meteo_minime.TMIN)

# Setăm titlul și descrierea axelor
plt.xlabel('Stația meteorologică')
plt.ylabel('Temperatura în grade Celsius')
plt.title('Temperatura minimă înregistrată în 2016 în stațiile meteorologice importante din România');

Acum se poate compara foarte ușor temperatura minimă, doar privind această imagine.

In [24]:
# Creem o figură matplotlib
plt.figure()

# Generăm barchart-ul
# Pe axa orizontală, vrem numele stațiilor, iar pe axa verticală, temperaturile minime
plt.pie(statii_meteo_minime.TMIN.abs(), labels=statii_meteo_minime.Nume)
plt.title('Temperatura minimă înregistrată în 2016 în stațiile meteorologice importante din România');

O altă vizualizare comună (poate prea comună) este un pie chart. Deși poate fi folosită oriunde poate fi folosit un barchart, foarte rar este preferabilă: oamenii compară mult mai ușor lungimi decât arii. Este foarte greu, în pie chart-ul de mai sus, să comparăm valorile înregistrate în stațiile din Miercurea Ciuc, Roșiorii de Vede și Sibiu. Așa că, oricând vreți să folosiți un pie chart, folosiți un bar chart :).

Cu toate astea, nimic nu e perfect. Trebuie tot timpul să înțelegem avantajele și dezavantajele unui lucru. Cu un bar chart nu putem compara cu ușurintă valori extreme. Haideți să încercăm un exemplu sintetic:

In [25]:
plt.figure(figsize=(10,5))

plt.bar(['A', 'B', 'C', 'D'], [1200, 1210, 1220, 50000])
plt.title('Un barchart cu valori extreme - date sintetice')
Out[25]:
Text(0.5, 1.0, 'Un barchart cu valori extreme - date sintetice')

Întrucât valoarea din coloana D este foarte mare, relativ la diferența dintre coloanele A, B și C, le putem compara cu ușurintă. Evident, sunt situații în care diferența lor este neglijabilă, și este importantă doar relația lor față de D, caz în care această vizualizare transmite mesajul perfect. Dar dacă suntem interesați în diferențele lor, trebuie să găsim alte soluții.

Există vreo legatură între temperatura minimă și altitudine?

Pentru a răspunde la această întrebare, vom folosi tabelul statii_meteo_minime. Trebuie să vizualizăm relația dintre coloanele ALT și TMIN, două coloane cu valori numerice. Dacă folosim bar chart-ul atunci când vrem să vizualizăm valori numerice și valori categorice, pentru vizualizarea relației dintre două seturi de valori numerice putem folosi un scatter plot:

In [26]:
plt.figure(figsize=(10,5))

# Generăm scatter plot-ul
# Pe axa orizontală, vrem altitudinea stațiilor, iar pe axa verticală, temperaturile minime
plt.scatter(statii_meteo_minime.ALT, statii_meteo_minime.TMIN)

# Setăm titlul și descrierea axelor
plt.xlabel('Altitudinea stației meteorologice în metri')
plt.ylabel('Temperatura în grade Celsius')
plt.title('Temperatura minimă înregistrată în 2016 în stațiile meteorologice importante din România');

Fiecare punct în acest plot este o stație meteorologică, pe axa orizontală avem altitudinea stației și pe axa verticală avem temperatura minimă înregistrată. Întotdeauna aveți grijă ca ilustrațiile să aibă un titlu clar, descrieri pentru axe și unități de masură! Haideți să adăugăm și niște etichete pe puncte, pentru a înțelege căror stații corespund.

In [27]:
# Creem o figură matplotlib
plt.figure(figsize=(10,5))

# Generăm scatter plot-ul
# Pe axa orizontală, vrem altitudinea stațiilor, iar pe axa verticală, temperaturile minime
plt.scatter(statii_meteo_minime.ALT, statii_meteo_minime.TMIN)

for i, txt in enumerate(statii_meteo_minime.Nume):
    plt.annotate(txt, (statii_meteo_minime.ALT[i], statii_meteo_minime.TMIN[i]))

# Setăm titlul și descrierea axelor
plt.xlabel('Altitudinea stației meteorologice în metri')
plt.ylabel('Temperatura în grade Celsius')
plt.title('Temperatura minimă înregistrată în 2016 în stațiile meteorologice importante din România');

Este greu de spus dacă există o legatură clară doar privind graficul. Se pare că stațiile la altitudini mari au înregistrat temperaturi mai mici, în timp ce stațiile de la altitudini mici au înregistrat o paletă largă de temperaturi.

Imaginați-vă cum ar fi graficul dacă nu ar fi stațiile de pe Ceahlău și Vârful Omu. Probabil am trage o concluzie diferită. A ne baza concluzia pe doar 2 puncte ar fi o decizie pripită: se poate ca aceste 2 locuri să fi experimentat condiții mai aprige din alte motive, nu datorită altitudinii.

În lecția următoare vom încerca să determinăm dacă există vreo legatură între cele două valori folosind metode statistice.

Cât de mult fluctuează temperatura într-o stație meteo?

Am analizat și comparat temperaturile minime înregistrate. Haideți acum să analizăm fluctuația temperaturii maxime zilnice. Această valoare este relevantă pentru că ne sugerează cât de mare este diferența dintre anotimpuri într-un anumit loc, unde vara putem atinge maxime cu valori mari, în timp ce iarna, zilele sunt mult mai reci.

Până acum am văzut cum să vizualizăm perechi de valori, în care una era numerică și una cateogrică sau ambele numerice. Acum vrem să observăm schimbarea unei valori, a temperaturii maxime zilnice, în timp. Acest tip de date se numește time series data, date temporale. Există mai multe vizualizări ce pot fi folosite, inclusiv bar chart. Haideți să încercăm să folosim un bar chart pentru a compara evoluția maximei zilnice în două stații meteorologice, de la Roșiorii de Vede și de pe Vârfu Omu.

Ca de obicei, vom începe prin a extrage datele:

In [39]:
statii_meteo[(statii_meteo.Nume == 'Rosiorii de Vede') | (statii_meteo.Nume == 'Varfu Omu')]
Out[39]:
ALT LAT LON Nume
CODST
15280 2504.000000 45.445833 25.456667 Varfu Omu
15470 102.150002 44.107222 24.978611 Rosiorii de Vede

După ce selectăm cele două stații de interes, putem folosi metoda merge pentru a adăuga restul datelor. Metodele merge și join sunt foarte similare, folosite pentru a "combina" tabele. Le vom explora pe ambele în detaliu într-o lecție viitoare. Pentru moment, vom separa datele celor doua statii în două tabele diferite.

In [40]:
statii = statii_meteo[(statii_meteo.Nume == 'Rosiorii de Vede') | (statii_meteo.Nume == 'Varfu Omu')]

omu_rdv_merge = climatic_2016.merge(statii, on='CODST')

climatic_rdv = omu_rdv_merge[omu_rdv_merge.Nume == 'Rosiorii de Vede']
climatic_omu  = omu_rdv_merge[omu_rdv_merge.Nume == 'Varfu Omu']
In [10]:
# Creem o figură matplotlib, cu două grafice (subplots) alăturate orizontal
fig, (ax1, ax2) = plt.subplots(1, 2, sharex=True, figsize=(15,8))

# Pentru fiecare din cele două grafice (numite 'axes', de către Matplotlib), generăm un barchart
ax1.bar(climatic_rdv.DATCLIM, climatic_rdv.TMAX)
ax2.bar(climatic_omu.DATCLIM, climatic_omu.TMAX)

fig.align_labels()

# Setăm titlul și descrierea axelor pentru fiecare grafic
ax1.set_xlabel('Data')
ax1.set_ylabel('Temperatura în grade Celsius')
ax1.set_title('Roșiorii de Vede')
ax1.set_ylim(-20, 40)

ax2.set_xlabel('Data')
ax2.set_ylabel('Temperatura în grade Celsius')
ax2.set_title('Vârfu Omu')
ax2.set_ylim(-20, 40)

# Setăm titlul întregii figuri
fig.suptitle('Temperatura maximă zilnică înregistrată în 2016 în stația meteorologică din Roșiorii de Vede');

Observați că am desenat graficele puțin diferit. În celelalte exemple am folosit o stare globală, pusă la dispoziție de matplotlib. Am creat figura în acest spațiu global și am desenat pe ea. Aici, trebuie să specificam unde (în ce axis) să fie plasat graficul. Trebuie de asemenea să setăm manual limitele axei verticale, atfel graficele nu vor fi corelate.

Se poate observa că în Rosiorii de Vede, valorile temperaturilor sunt între aproximativ -10 și 40 de grade, în timp ce pe Vârfu Omu, intervalul este mai mic, între -20 și 20 de grade. Pe de altă parte, putem vedea că temperaturi negative sunt în relativ puține zile în Roșiorii de Vede, în timp ce în Vârfu Omu, toată paleta de valori termice pare reprezentată mai bine.

Așadar, în Roșiorii de Vede, intervalul termic mai larg se datorează doar câtorva zile cu temperaturi extreme, sau chiar există o fluctuație mai mare a temperaturii? Cu siguranță nu putem răspunde doar examinând graficele de mai sus.

Pentru a studia cât de variate sunt temperaturile observate de o stație meteorologică, nu suntem interesați în a observa datele temporal, ca în vizualizarea de mai sus. Ne interesează doar de câte ori a fost înregistrată o anumită valoare. Ce încercăm să aflăm aici este dacă valorile înregistrate cel mai des sunt relativ puține, sau sunt foarte multe valori termice observate la fel de des.

Putem vizualiza de câte ori apare fiecare valoare - liceenii ce studiază informatică probabil au auzit de aceast concept ca vector de apariții - într-o histogramă:

In [11]:
# Creem o figură matplotlib, cu două grafice (subplots) alăturate orizontal
fig, (ax1, ax2) = plt.subplots(1, 2, sharex=True, figsize=(15,8))

# Pentru fiecare din cele două grafice (numite axes, de către Matplotlib), generăm un barchart
ax1.hist(climatic_rdv.TMAX, bins=30)
ax2.hist(climatic_omu.TMAX, bins=30)

fig.align_labels()

# Setăm titlul și descrierea axelor pentru fiecare grafic
ax1.set_ylabel('Număr de apariții')
ax1.set_xlabel('Temperatura în grade Celsius')
ax1.set_title('Roșiorii de Vede')

ax2.set_ylabel('Număr de apariții')
ax2.set_xlabel('Temperatura în grade Celsius')
ax2.set_title('Vârfu Omu')

# Setăm titlul întregii figuri
fig.suptitle('Temperatura maximă zilnică înregistrată în 2016 în stația meteorologică din Roșiorii de Vede');

Graficele de mai sus sunt histograme: un tip special de barchart, în care fiecare coloană reprezintă numărul de apariții ale valorii respective. Întrucât paleta de valori poate fi mare (în cazul nostru, în care temperaturile au valori reale, chiar inifinită), ea este împărțită în mai multe intervale (bins), și noi vizualizăm cât de des au fost înregistrate valori din fiecare bin. Întrucât paleta de valori (intervalul dintre minim și maxim) este mai mare în cazul stației din Roșiorii de Vede și noi am ales același număr de 30 de bins pentru ambele grafice, putem observa coloane mai groase (intervale mai mari pentru fiecare bin). În cazul acesta, nu va reprezenta o problema. Haideți să vedem ce putem întelege din această vizualizare.

Am spus că ne interesează să înțelegem cât de mult variază temperaturile în timpul anului. De exemplu, în cadrul stației de pe Vârfu Omu, știm că temperaturile sunt cuprinse între:

In [12]:
print('Temperatura maximă zilnică înregistrată pe Vârfu Omu variază între %.1f și %.1f grade Celsius' % (climatic_omu.TMAX.min(), climatic_omu.TMAX.max()))
Temperatura maximă zilnică înregistrată pe Vârfu Omu variază între -19.7 și 17.2 grade Celsius

Aceste limite sunt utile pentru a înțelege fenomenele extreme, dar nu înțelegem exact clima de acolo: ar putea fi -19.7 grade Celsius în 364 de zile, și 17.2 grade Celsius doar într-o zi. Sigur, acesta e un exemplu extrem, dar ideea fundamentală este aceeași: valorile extreme nu oferă informații decât despre cazurile extreme.

În acest caz, am putea raporta și media. Sigur, ar da informații despre cum este vremea în medie:

In [13]:
print('Temperatura maximă zilnică înregistrată pe Vârfu Omu variază între %.1f și %.1f grade Celsius, cu o medie de %.1f grade Celsius' % (climatic_omu.TMAX.min(), climatic_omu.TMAX.max(), climatic_omu.TMAX.mean()))
Temperatura maximă zilnică înregistrată pe Vârfu Omu variază între -19.7 și 17.2 grade Celsius, cu o medie de 1.0 grade Celsius

Întâmplător, media este aproximativ la jumătatea intervalului dat de extreme. Acest lucru nu se întâmplă în cazul stației din Roșiorii de Vede, de exemplu.

Dar ne oferă aceste numere o idee destul de bună despre vremea de pe Vârfu Omu? Cu siguranță nu, am putea avea de-a face cu o vreme extremă: foarte cald, alternând cu foarte rece, și să obținem o medie rezonabilă. Sau, am putea avea adesea valori în jurul mediei. Cum sunt valorile termice distribuite?

La aceasta întrebare, răspunsul poate fi dat observând o histogramă. Din histograma stației de pe Vârfu Omu, putem observa că valori apropiate mediei se întâlnesc des, în timp ce în Roșiorii de Vede, avem șanse ceva mai mari să nimerim într-o zi toridă, la 30 de grade Celsius, sau într-o zi mai degrabă răcoroasă la 10 grade Celsius, decât într-o zi medie, la 18 grade Celsius. Spunem că temperaturile din Roșiorii de Vede nu doar că au un interval mai mare, dar au și o variație mai mare, în timp ce în Vârfu Omu temperaturile sunt adesea în jurul mediei, cu o variație mică.

Există și o metodă de a determina numeric variația, ca media pătratelor distanțelor dintre valorile individuale și media lor. Altfel spus,

$$ \mu = \sum_{i = 1}^N x_i $$$$ \sigma^2 = \frac{\sum_{i = 1}^N (x_i - \mu)^2}{N} $$

unde $\mu$ reprezintă media celor $N$ observații $x_i$, iar $\sigma^2$ este variația lor.

Haideți să calculăm variația temperaturii din cele două stații:

In [14]:
print('Variația temperaturilor de pe stația Roșiorii de Vede: %.2f' % (climatic_rdv.TMAX.var()))
print('Variația temperaturilor de pe stația Vârfu Omu: %.2f' % (climatic_omu.TMAX.var()))
Variația temperaturilor de pe stația Roșiorii de Vede: 128.82
Variația temperaturilor de pe stația Vârfu Omu: 59.68

Așa cum era de așteptat, avem o variație mai mare a temperaturii în Roșiorii de Vede.

Dacă nu vă întrebați de ce am notat variația cu $\sigma^2$ în loc de o simplă variabilă, recitiți cu atenție :). Nu există o notație standard pentru variație, pentru că nu este folosită direct. Dacă ne gândim la unitațile de măsură a valorilor noastre, dacă $x_i$ sunt temperaturi în grade Celsius, media lor, $\mu$, este tot în grade Celsius, dar variația lor este...în grade Celsius la pătrat. Nu avem o intuiție pentru aceasta unitate de măsură, și în niciun caz nu putem să le comparăm cu ușurință, așa că vom raporta în schimb deviația standard: rădăcina pătrată a variației, notată, evident, cu $\sigma$.

Haideți să calculăm deviația standard pentru cele două seturi de date:

In [15]:
print('Deviația standard a temperaturilor de pe stația Roșiorii de Vede: %.2f' % (climatic_rdv.TMAX.std()))
print('Deviația standard a temperaturilor de pe stația Vârfu Omu: %.2f' % (climatic_omu.TMAX.std()))
Deviația standard a temperaturilor de pe stația Roșiorii de Vede: 11.35
Deviația standard a temperaturilor de pe stația Vârfu Omu: 7.73

O variație mai mare este adesea, dar nu întotdeauna, însoțită de valori extreme mai mari. Cu toate acestea, trebuie să considerăm valorile extreme ca atare: fenomene extreme. Media și deviația standard oferă o imagine mult mai clară asupra unei distribuții, a tipului de valori pe care este mai probabil să le întâlnim. În cazul nostru, vrem să înțelegem distribuția temperaturilor pentru a înțelege clima unui loc.

Comparând deviațiile standard, este clar că în Roșiorii de Vede temperaturile variază mai mult în timpul anului. Dar ce ne spune valoarea de 11.35? Așa cum vom vedea în lecțiile viitoare, când vom studia mai în detaliu distribuțiile datelor, deviația standard indică faptul că aproximativ 34% din temperaturile anuale sunt cuprinse între $\mu$ și $\mu + \sigma$ și alte 34% din date între $\mu - \sigma$ și $\mu$. În cazul nostru, înseamnă ca în peste 3 treimi din an, temperaturile din Roșiorii de Vede vor fi între 6.6 și 29.3 grade. Trebuie să împachetăm și haine groase, dar nu chiar așa multe! Ne așteptăm la temperaturi negative în cel mult 15% din an, adică 55 de zile. Nici măcar o iarnă întreagă :(.

Sigur, aceste estimări le puteam face direct din date - dar vedeți cât de mult putem sintetiza informația doar raportând media și deviația standard?

Haideți să înțelegem mai bine ce ne spun aceste două valori despre distribuția unor numere.

Atunci când privim o histogramă a unor date, practic observăm distribuția acelor date, cât de des apare fiecare valoare. În unele cazuri, este greu să analizăm distribuția unor date bazându-ne pe datele exacte - am vrea să avem un model matematic. Din fericire, există modele matematice pentru majoritatea distribuțiilor comune. Adesea când analizăm distribuția unor date, primul pas va fi să alegem un astfel de model matematic. Este important să înțelegem că orice model vine cu anumite prezumții, iar rezultatul analizei depinde de cât de bine sunt respectate acele prezumții.

Cea mai importantă distribuție este distribuția Gaussiană sau normală. Este numită normală deoarece majoritatea datelor respectă acest tip de distribuție. Haideți să o vizualizăm să înțelegem de ce (pentru această vizualizare, vom folo1si 2 biblioteci noi - nu este important să înțelegeți detaliile codului pentru moment, doar discutăm rezultatele).

In [16]:
# Numpy este o bibliotecă pentru operații numerice și algebră liniară
import numpy as np

# Calculăm valoarea unei distribuții normale cu media *mean* și deviația standard *std* în punctul *x*
# Citește în continuare pentru a înțelege această formulă
def gaussian(x, mean, std):
    return 1/(std * np.sqrt(2 * np.pi)) * np.exp(-1/2 * ((x-mean)/std)**2)

# Setăm un interval în care vom evalua distribuția
x_min = 0.0
x_max = 16.0

# Setăm o medie și o deviație standard pentru distribuție
mean = 8.0 
std = 2.0

# Calculăm 100 de puncte, egal separate în intervalul x_min, x_max
x = np.linspace(x_min, x_max, 100)

# Calculăm valoarea unei distribuții normale cu media _mean_ și deviația standard _std_
y = gaussian(x, mean, std)

# Construim graficul funcției
plt.plot(x, y)

# Desenăm linii ajutătoare
plt.grid()

# Setăm titlul și numele axelor
plt.title('O distribuție normală, cu media 8 și deviația standard 2',fontsize=10)

plt.xlabel('x')
plt.ylabel('Distribuția Normală');

Așa cum am spus, vom explica detaliile din cod în curând. Până atunci, haideți să privim forma generală a distribuției. Are media valorilor 8, unde este și valoarea maximă, iar lațimea distribuției este dată de deviația standard. Observați că mare parte din aria de sub grafic este între $x=6$ și $x=10$.

Aceasta distribuție este atât de comună pentru că apare natural în multe măsurători - putem observa că histogramele temperaturilor au valori mari spre medie, iar spre extreme scade numarul de observații (deși la Roșiorii de Vede maximul nu este la medie).

Atunci când am calculat deviația standard și media, și am descris distribuția temperaturilor folosind aceste două valori, am presupus că urmează o distribuție normală și am descris distribuția normală definită de aceste valori. Forma distribuției este controlată doar de aceste două valori, numite parametrii.

Dar cum calculăm valoarea distribuției într-un punct? Nimic mai simplu - ea este doar o funcție:

$$ N(x) = \frac{1}{\sigma \sqrt{2 \pi}} e ^ {-\frac{1}{2} (\frac{x-\mu}{\sigma})^2} $$

Valoarea lui $N(x)$ reprezintă probabilitatea de a observa valoarea $x$. Luați o pauză de cateva minute să reflectați asupra acestui concept. Asta înseamnă că valorile din jurul mediei sunt mai probabile decât cele mai depărtate de medie.

Putem vizualiza cât de bine modelează o astfel de distribuție datele noastre:

In [17]:
# Creem o figură matplotlib, cu două grafice (subplots) alăturate orizontal
fig, (ax1, ax2) = plt.subplots(1, 2, sharex=True, figsize=(15,8))

# Generăm histogramele ca probabilități
# Acum nu arătăm numărul de apariții, ci probabilitatea aparițiilor
# Aceste valori sunt calculate ca numărul aparițiilor unei observații împărțit la numărul total de observații
# Funcțiile care descriu probabilitatea de a observa o anumită valoare se numesc densități probabilistice (en: probability density function)
# De aici și parametrul *density*
ax1.hist(climatic_rdv.TMAX, bins=30, density=True)
ax2.hist(climatic_omu.TMAX, bins=30, density=True)


# Calculăm 100 de puncte, egal separate în intervalul min, max
x_rdv = np.linspace(climatic_rdv.TMAX.min(), climatic_rdv.TMAX.max(), 100)
x_omu = np.linspace(climatic_omu.TMAX.min(), climatic_omu.TMAX.max(), 100)

y_rdv = gaussian(x_rdv, climatic_rdv.TMAX.mean(), climatic_rdv.TMAX.std())
y_omu = gaussian(x_omu, climatic_omu.TMAX.mean(), climatic_omu.TMAX.std())

ax1.plot(x_rdv, y_rdv)
ax2.plot(x_omu, y_omu)

fig.align_labels()

# Setăm titlul și descrierea axelor pentru fiecare grafic
ax1.set_ylabel('Număr de apariții')
ax1.set_xlabel('Temperatura în grade Celsius')
ax1.set_title('Roșiorii de Vede')

ax2.set_ylabel('Număr de apariții')
ax2.set_xlabel('Temperatura în grade Celsius')
ax2.set_title('Vârfu Omu')

# Setăm titlul întregii figuri
fig.suptitle('Temperatura maximă zilnica înregistrată în 2016 în stația meteorologică din Roșiorii de Vede');

Funcțiile descriu probabilitatea de a observa o anumită valoare se numesc densități probabilistice (en: probability density function). $N(x)$ descrie o densitate probabilistică. Acest tip de funcții trebuie să aibă urmatoarele proprietăți:

  • Trebuie să fie pozitive: evident, nu putem vorbi despre probabilități negative
  • Trebuie să se integreze la 1 pe intervalul $(-\infty, \infty)$: o generalizare a conceptului că suma probabilităților mai multor evenimente trebuie să fie 1. Factorul $\frac{1}{\sigma \sqrt{2 \pi}}$ din fața distribuției Gaussiene este un factor de normalizare pentru a garanta această proprietate.

Pe lângă histogramă, putem vizualiza distribuția unor numere într-un box (and whiskers) plot.

In [18]:
# Creem o figură matplotlib, cu două grafice (subplots) alăturate orizontal
fig = plt.figure(figsize=(8,8))

# Obținem axele active din figură
# Am fi putut desena folosind starea globală a bibliotecii,
# folosind apeluri precum `plt.boxplot`, în loc să desenăm
# direct în axe
ax = fig.gca()

# Creem un boxplot pentru ambele date
ax.boxplot([climatic_rdv.TMAX, climatic_omu.TMAX])

# Setăm titlul și descrierea axelor pentru fiecare grafic
ax.set_ylabel('Temperatura în grade Celsius')
ax.set_xlabel('Stația meteorologică')
ax.set_xticklabels(['Roșiorii de Vede', 'Vârfu Omu'])
ax.set_title('Roșiorii de Vede')

# Setăm titlul întregii figuri
ax.set_title('Temperatura maximă zilnică înregistrată în 2016');

Acest tip de vizualizare este compus dintr-un dreptunghi (box), și două linii verticale de o parte și de alta a sa, numite whiskers. Linia portocalie din interiorul dreptunghiului reprezintă mediana datelor. Mediana este un concept complementar mediei - pentru a o calcula, sortăm datele și alegem valoarea din mijloc. Cu datele astfel sortate, putem alege orice punct - de exemplu, putem alege punctul care separă datele în 25% de o parte și 75% în partea cealaltă. În cazul nostru, ar fi a $366 / 100 * 25 = 91$-a valoare din cele sortate. Aceasta este cunoscută ca 25-percentile.

Box-ul cuprinde 50% din date, aflate între 25 și 75-percentiles, iar capetele celor două whiskers reprezintă valorile extreme.

Astfel, putem observa că în Roșiorii de Vede, în jumatate din an s-au înregistrat temperaturi între 10 și 28 de grade Celsius, de exemplu.

Acum putem întelege toate valorile raportate de funcția describe, ce descrie datele noastre:

In [19]:
climatic_rdv.TMAX.describe()
Out[19]:
count    366.000000
mean      17.953005
std       11.350060
min      -12.700000
25%        9.400000
50%       18.300000
75%       28.275000
max       37.599998
Name: TMAX, dtype: float64

Am studiat media și deviația standard (aici, mean și std) mai devreme. Valorile pentru 25%, 50%, 75% sunt percentiles, ce sunt vizualizate și în boxplot-ul de mai sus. De exemplu, putem vedea că în 75% din zile, temperaturile au fost mai mici de 28 de grade Celsius. Percentile-ul la 50% este mediana.

Am studiat pana acum 2 metode complementare de a descrie datele: prin medie și deviație standard, ce pot fi vizualizate prin histogramă, sau prin mediană și alte percentiles, ce sunt vizualizate în box and whiskers plots. Vom reveni asupra lor și asupra meritelor fiecărei metode în lecțiile următoare.

Este foarte important să înțelegem cui adresăm o analiză. Dacă vorbim de publicul larg, anumite metrici precum deviația standard sunt greu de înțeles, va trebui să ne rezumăm doar la medie. În schimb, putem raporta intuitiv că am observat o variație mai mare. Pe de altă parte, un public tehnic trebuie să înțeleagă toate detaliile din spatele datelor și cum au influențat ele concluzia.

Vizualizări greșite

Înainte de a încheia, haideți să vedem cum vizualizările pot distorsiona datele sau induce în eroare cititorii. Petreceți câteva minute încercând să înțelegeți care e problema cu fiecare.

Anaconda

Dacă nu v-ați dat seama, graficul este inversat, cu originea în coltul din stânga sus. Această modificare este neintuitivă - datele ar trebui să fie evidente, fără să peterecem timp să înțelegem cum sunt poziționate axele.

Anaconda

În această imagine din cadrul unei campanii electorale, bar chart-ul cu rezultatele poll-ului exagerează diferența dintre primul și restul candidaților. În realitate, diferența dintre primul și al doilea candidat este la jumatate față de cea dintre al doilea și al treilea candidat. Așa-i că nu v-ați dat seama cât de apropiați sunt primii doi candidați, deși numerele sunt afișate?

Anaconda

În acest grafic publicat de Ministerul Sanatății în Brazilia, se încearcă a se arăta prin linia portocalie tendința descrescătoare a cazurilor de COVID-19. Priviți cu atenție datele - numărul de cazuri pare să fie destul de constant, cu o mică fluctuație, nicidecum în descreștere.

Anaconda

În acest grafic se compară diferența mediei înălțimilor femeilor din diferite țări. Este practic un barchart, în care coloanele au fost înlocuite cu un simbol. Problema este că, intuitiv, nu mai comparăm lungimi, ci arii. Media din India pare mult mai mică decât cea din Latvia, când în realitate vorbim despre o diferență de 5 inch - 12.7 cm.

Exercițiu

Cum au fost temperaturile înregistrate de stațiile meteo Vârfu Omu și Roșiorii de Vede în 1999 față de 2016?

Ar trebui să raportați diferențele numerice, să creați vizualizări reprezentative ce compară temperaturile din cele două zone și să trageți concluzii. De asemenea, ar trebui să comparați atât mediile zilnice, cât și maximele și minimele zilnice. Gândiți-vă cum puteți prezenta aceste lucruri într-un mod concis unui public tehnic.

Datele pentru temperaturile din 1999 pot fi găsite în Data/RoGovClimatic/climrbsn1999.csv.